2013-10-04 8 views
6

Ich versuche for in der Shell zu verwenden, um über Dateinamen mit Leerzeichen zu iterieren. Ich las in einem stackoverflow question, dass IFS=$'\n' verwendet werden könnte und das scheint gut zu funktionieren. Ich lese dann in a comment to one of the answers, dass es IFS=$(echo -e '\n') verwenden könnte, um es für andere Shells als bash kompatibel zu machen.

Das ehemalige funktioniert, aber das letztere nicht. Der Kommentar wird mehrmals aktualisiert. Ich habe die Ausgabe überprüft und die Variable scheint nicht den Zeilenumbruch zu enthalten.

#!/bin/sh 

IFS=$(echo -e '\n') 
echo -n "$IFS" | od -t x1 
for file in `printf 'one\ntwo two\nthree'`; do 
    echo "Found $file" 
done 

echo 

IFS=$'\n' 
echo -n "$IFS" | od -t x1 
for file in `printf 'one\ntwo two\nthree'`; do 
    echo "Found $file" 
done 


Die Ausgabe zeigt die Feldtrennzeichen zum ersten fehlt:

0000000 
Found one 
two two 
three 


Doch ist die richtige auf der zweiten:

0000000 0a 
0000001 
Found one 
Found two two 
Found three 


fand ich eine beantworteten Frage, die kein Duplikat meiner Frage sein kann:
linux - When setting IFS to split on newlines, why is it necessary to include a backspace? - Stack Overflow

Gibt es eine Gefahr zu tun, was dort besprochen ist, IFS=$(echo -en '\n\b')? Denn das fügt dem IFS einen \b hinzu (ich überprüfte). Kann ein \b in einem Dateinamen auftreten? Ich möchte nicht, dass mein Code versehentlich einen Dateinamen aufteilt.

Haben Sie auch Empfehlungen, wie Sie die Wiederherstellung von IFS nach der for-Schleife richtig handhaben können? Soll ich es in einer temporären Variable speichern oder IFS und die for-Anweisung in einer Subshell ausführen? (Und wie das tun?) Vielen Dank

Antwort

6

aktualisiert - meinen pseudo-Kommentar zu einer echten Antwort zu ändern.

Ich denke, this answer sollte das Verhalten erklären, das Sie sehen. Speziell die Befehlsersetzungsoperatoren $() und Backticks entfernen nachfolgende Zeilenumbrüche von der Befehlsausgabe. Die direkte Zuweisung in Ihrem zweiten Beispiel führt jedoch keine Befehlsersetzung durch und funktioniert daher wie erwartet.

So habe ich Angst zu sagen, dass ich denke, dass der upvoted Kommentar, auf den Sie sich beziehen, falsch ist.


Ich denke, der sicherste Weg zur Wiederherstellung von IFS ist, es in eine Subshell zu setzen. Alles, was Sie tun müssen, ist die entsprechenden Befehle in Klammern gesetzt ():

(
    IFS=$'\n' 
    echo -n "$IFS" | od -t x1 
    for file in `printf 'one\ntwo two\nthree'`; do 
     echo "Found $file" 
    done 
) 

Natürlich eine Subshell verursacht eine kleine Verzögerung aufrufen, so dass Leistung berücksichtigt werden muss, wenn diese viele Male wiederholt werden soll.


Als eine beiseite, seien Sie sehr vorsichtig, Dateinamen können sowohl \ b und \ n enthalten. Ich denke nur die einzigen Zeichen, die sie nicht enthalten können, sind Schrägstrich und \ 0. Zumindest das, was es dazu sagt wikipedia article.

$ touch $'12345\b67890' 
$ touch "new 
> line" 
$ ls --show-control-chars 
123467890 new 
line 
$ 
4

Da Zeilenumbrüche durch Befehlsersetzung entfernt werden, wie @DigitalTrauma richtig geschrieben hat, sollten Sie eine andere, POSIX-konforme Methode verwenden.

IFS=' 
' 

schreibt einen Zeilenumbruch und weist ihn ihm zu. Einfach und effektiv.

Wenn Sie keine Aufgaben mögen, die sich über zwei Zeilen erstrecken, können Sie alternativ printf verwenden.

IFS="$(printf '\n')" 
+0

Vielen Dank für die Informationen werde ich, dass der Geist in halten – user2672807

+0

Bitte Ihr zweites Beispiel entfernen, da es nicht sauber ist: 'IFS = "$ (printf '\ n +')"' '$ echo - n "$ IFS" | xxd' '00000000: 0a2b' <- Dies fügt auch' + 'zu IFS hinzu. –

+0

@TomHale Ich habe das + entfernt, das nicht benötigt wurde –