PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : BASH: spaces in dateinamen



juepi
20-04-2008, 10:55
Hallo Leute,

Ich hab' da ein Problem dass mich jetzt schon seit einiger Zeit wurmt, und da es eigentlich nichts aussergewöhnliches ist, nehme ich fast an, dass es eine relativ einfache Lösung gibt.
Man nehme folgendes script:

#!/bin/bash
for file in `find . -name '*.txt'`
do
cat $file
done

wenn ich jetzt vom find eine Datei mit Leerzeichen im Namen übergeben bekomme ("file with spaces.txt") passiert mir folgendes:
bash test.sh
cat: ./file: No such file or directory
cat: with: No such file or directory
cat: spaces.txt: No such file or directory

Was mir im ersten Ansatz auch logisch erscheint, weil die Leerzeichen nicht backslash-escaped wurden, also das ganze folgendermassen umgebaut:

#!/bin/bash
for file in `ls -b1 *.txt`
do
cat $file
done

Ergibt aber immer noch folgenden Output:
bash test.sh
cat: file\: No such file or directory
cat: with\: No such file or directory
cat: spaces.txt: No such file or directory

Kann mir das jemand erklären, bzw. eine Lösung verraten?
Vielen Dank im voraus,

lg,
Jürgen

P.s.: das $file unter double quotes zu stellen beim cat bringt leider auch nix :(

ContainerDriver
20-04-2008, 12:30
Hallo,

das Problem tritt in der for-Schleife auf. Die Shell nimmt das hintere Argument (in diesem Fall `find . -name '*.txt'`) und teilt den String jeweils am IFS (http://en.wikipedia.org/wiki/Internal_Field_Separator) auf. Für jeden Teilstring wird dann die for-Schleife durchlaufen.
Da der IFS standardmäßig auf " " (Leerzeichen) steht, wird ein Dateiname mit Leerzeichen aufgetrennt.
Man könnte das lösen, indem man den IFS auf Newline setzt:


#!/bin/bash
IFS="
"
for file in `find . -name '*.txt'`
do
cat $file
done
.

Gruß, Florian

core
20-04-2008, 16:07
Wieso denn überhaupt mit find oder ls arbeiten?

Versuch's mal so:


for file in *txt;
do cat "$file";
done

Beispiel:


jg@hermes:~/tmp$ echo 1 >file.txt
jg@hermes:~/tmp$ echo 1 >file\ with\ spaces.txt
jg@hermes:~/tmp$ echo 1 >file\ with\ \"spaces\&quotes\".txt
jg@hermes:~/tmp$ ls -l
insgesamt 12
-rw-r--r-- 1 jg jg 2 2008-04-20 16:30 file.txt
-rw-r--r-- 1 jg jg 2 2008-04-20 16:30 file with "spaces&quotes".txt
-rw-r--r-- 1 jg jg 2 2008-04-20 16:30 file with spaces.txt
jg@hermes:~/tmp$ for file in *txt; do cat "$file"; done
1
1
1




Oder alternativ dazu, nur find ohne shellscript:


$ find . -name '*txt' -exec cat "{}" \;
... macht genau das

juepi
20-04-2008, 19:37
danke für eure Hilfe, script funktioniert jetzt wie gewünscht :)

lg,
Jürgen

jan61
27-04-2008, 12:24
Moin,


Wieso denn überhaupt mit find oder ls arbeiten?

weil Deine Variante nur Dateien im aktuellen Verzeichnis findet, der find aber rekursiv? Und ls hat gegenüber Deiner Schleife einen Vorteil: Wenn keine *.txt-Datei existiert, wird die ls-Schleife nicht ausgeführt, in Deiner Version einmal mit dem (nicht existierenden) Dateinamen "*.txt".

Es gibt mehrere Varianten, das Problem zu umgehen. ContainerDriver hat eine davon genannt, Du mit der find-exec-Version eine 2. Sonst gäbe es z. B. noch:

find ... -print | while read f; do cat "$f"; done
find ... -print0 | xargs -0 cat
find ... -printf "cat \"%p\"\n" | shDie read-Schleife funktioniert deshalb, weil read immer bis zum Zeilenende liest, die 2. Variante gibt die Dateinamen nicht per Linefeed getrennt aus, sondern mit \0 - xargs mit der -0-Option erkennt das. Die Variante hat den Vorteil, dass nicht je Datei ein eigener cat gestartet wird, kann aber nicht mit jedem Kommando angewendet werden. Die 3. Variante baut per printf (nur GNU-find!) die Kommandozeile cat "datei" für jeden Treffer auf und übergibt sie dann einer Shell zur Ausführung.

Jan

P.S.: Bei der Variante von ContainerDriver sollte man dran denken, die ursprüngliche IFS-Variable zu sichern und nach der Schleife zurückzusetzen, wenn das Script noch weitergeht, um unliebsame Überraschungen zu vermeiden.