ubuntuusers.de

echo frißt Tabs am Zeilenende

Status: Gelöst | Ubuntu-Version: Ubuntu GNOME 20.04 (Focal Fossa)
Antworten |

Doc_Symbiosis

Avatar von Doc_Symbiosis

Anmeldungsdatum:
11. Oktober 2006

Beiträge: 4453

Wohnort: Göttingen

Hallo,

ich habe eine Datei test2, die Tabs vor dem Zeilenende hat. Alle weiteren Tabs werden ordentlich geschrieben, nur die Tabs vor dem Zeilenende werden einfach weggeworfen.

Ich frage mich, warum dem so ist. Mein Beispiel:

echo -e "a\t\t" > test1
while read line; do echo "$line" >> test2; done < test1

In der Datei test2 sind die Tabs am Zeilenende dann nicht mehr vorhanden. Wie bekomme ich es hin, dass die Schleife auch die Tabs am Zeilenende mitnimmt?

Und nicht wundern, dass das so umständlich gelöst ist. Da passiert eigentlich noch erheblich mehr in der while-Schleife, ist jetzt nur runtergebrochen auf das Grundproblem...

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11269

Wohnort: München

Das echo in der zweiten Zeile wirft die Tabs weg - da musst du ebenfalls den Schalter -e nutzen und read trennt den Text am IFS auf, daher musst du den explizit auf \n setzen, wenn du Dateien zeilenweise verarbeiten willst - so sollte es z.B. klappen:

1
while IFS='\n' read -r line; do echo -e "$line" >> test2; done < test1

Oder man nimmt eine Skriptsprache, in der es leichter ist Whitespace zu erhalten...

Doc_Symbiosis

(Themenstarter)
Avatar von Doc_Symbiosis

Anmeldungsdatum:
11. Oktober 2006

Beiträge: 4453

Wohnort: Göttingen

Ah super, so klappt es. Danke Dir!

Ich war tatsächlich auch kurz davor, das ganze in Python zu schreiben 😛

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13219

seahawk1986 schrieb:

[...] read trennt den Text am IFS auf, daher musst du den explizit auf \n setzen, wenn du Dateien zeilenweise verarbeiten willst - so sollte es z.B. klappen:

1
while IFS='\n' read -r line; do echo -e "$line" >> test2; done < test1

Das stimmt nicht: read liest immer bis zum Zeilenende ("One line is read from the standard input" aus der bash Manpage). Und read liest den kompletten Rest der Zeile in die letzte Variable, deren Namen übergeben wurde ("If there are more words than names, the remaining words and their intervening delimiters are assigned to the last name." ebd). Wenn man wie im ursprünglichen Code nur einen Namen übergibt, landet die gesamte Zeile dort und auch die Anzahl der Leerzeichen innerhalb der Zeile bleibt erhalten.

Man muss auch nicht den Schalter -e bei echo benutzen, denn der bewirkt lediglich, dass Sequenzen wie "\t", "\n", "\r" u.ä. (zwei Zeichen!) im übergebenen String bei der Ausgabe in Tab, Newline, Carriage Return usw. umgewandelt werden. Damit würde man sogar den ursprünglichen Inhalt verfälschen. Tabs usw., die in der Zeichenkette enthalten sind, werde mit und ohne -e genau so ausgegeben. Zum Vergleich habe ich noch einmal printf mit genutzt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ printf '\ta\tb\t\nx   y\n' >file
$ cat file
        a       b
x   y
$ od -t x1c file
0000000  09  61  09  62  09  0a  78  20  20  20  79  0a
         \t   a  \t   b  \t  \n   x               y  \n
0000014
$ while read line; do echo "$line"; done <file
a       b
x   y
$ while read line; do printf '%s|\n' "$line"; done <file
a       b|
x   y|
$ while read line; do echo "$line"; done <file | od -t x1c
0000000  61  09  62  0a  78  20  20  20  79  0a
          a  \t   b  \n   x               y  \n
0000012
$ while read line; do printf '%s|\n' "$line"; done <file | od -t x1c
0000000  61  09  62  7c  0a  78  20  20  20  79  7c  0a
          a  \t   b   |  \n   x               y   |  \n
0000014

Für das Entfernen von Whitespace am Ende der Zeile ist das Word Splitting verantwortlich ("split into words as described above under Word Splitting" ebd). Und deshalb muss man den IFS auf "leer" setzen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ while IFS= read line; do echo "$line"; done <file
        a       b
x   y
$ while IFS= read line; do printf '%s|\n' "$line"; done <file
        a       b       |
x   y|
$ while IFS= read line; do echo "$line"; done <file | od -t x1c
0000000  09  61  09  62  09  0a  78  20  20  20  79  0a
         \t   a  \t   b  \t  \n   x               y  \n
0000014
$ while IFS= read line; do printf '%s|\n' "$line"; done <file | od -t x1c
0000000  09  61  09  62  09  7c  0a  78  20  20  20  79  7c  0a
         \t   a  \t   b  \t   |  \n   x               y   |  \n
0000016

Doc_Symbiosis

(Themenstarter)
Avatar von Doc_Symbiosis

Anmeldungsdatum:
11. Oktober 2006

Beiträge: 4453

Wohnort: Göttingen

Hm, ich mache des jetzt tatsächlich lieber in Python. Mit dem leeren IFS funktioniert es zwar fast, aber leider werden dann Backslashe entfernt. Sind einfach doch zu viele unterschiedliche Zeichen in den Daten, die ich da habe.

Danke jedenfalls für eure Hinweise!

Doc_Symbiosis

(Themenstarter)
Avatar von Doc_Symbiosis

Anmeldungsdatum:
11. Oktober 2006

Beiträge: 4453

Wohnort: Göttingen

Nur noch kurz zur Verdeutlichung:

$ od -t x1c test1.txt 
0000000  72  5c  66  0a
          r   \   f  \n
0000004

Dann mache ich

while IFS= read line ; do echo "$line" >> test2.txt; done < test1.txt

Und dabei kommt folgendes raus:

$ od -t x1c test2.txt 
0000000  72  66  0a
          r   f  \n
0000003

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13219

Doc_Symbiosis schrieb:

Hm, ich mache des jetzt tatsächlich lieber in Python. Mit dem leeren IFS funktioniert es zwar fast, aber leider werden dann Backslashe entfernt.

Dagegen nimmst Du einfach die Option -r von read.

Doc_Symbiosis schrieb: [...]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ od -t x1c file 
0000000  72  5c  66  0a
          r   \   f  \n
0000004
$ IFS= read line <file; echo "$line" | od -t x1c
0000000  72  66  0a
          r   f  \n
0000003
$ IFS= read -r line <file; echo "$line" | od -t x1c
0000000  72  5c  66  0a
          r   \   f  \n
0000004
Antworten |