it-swarm.com.de

Warum gehen Zeilenumbruchzeichen bei der Befehlssubstitution verloren?

Ich habe eine Textdatei mit dem Namen links.txt, die so aussieht

link1
link2
link3

Ich möchte diese Datei Zeile für Zeile durchlaufen und in jeder Zeile eine Operation ausführen. Ich weiß, dass ich dies mit der while-Schleife tun kann, aber da ich lerne, habe ich mir überlegt, eine for-Schleife zu verwenden. Ich habe tatsächlich eine Befehlsersetzung wie diese verwendet

a=$(cat links.txt)

Dann benutzte die Schleife so

for i in $a; do ###something###;done

Auch so etwas kann ich machen

for i in $(cat links.txt); do ###something###; done

Jetzt ist meine Frage, wenn ich die Ausgabe des Befehls cat in eine Variable a eingesetzt habe, werden die neuen Zeilenzeichen zwischen link1 link2 und link3 entfernt und durch Leerzeichen ersetzt

echo $a

ausgänge

link1 link2 link3

und dann habe ich die for-Schleife verwendet. Wird bei einer Befehlsersetzung immer eine neue Zeile durch Leerzeichen ersetzt?

Grüße

35
user3138373

Die Zeilenumbrüche gingen verloren, weil die Shell nach der Befehlsersetzung Feldaufteilung ausgeführt hatte.

In POSIX Befehlsersetzung Abschnitt:

Die Shell erweitert die Befehlssubstitution, indem sie den Befehl in einer Subshell-Umgebung ausführt (siehe Shell-Ausführungsumgebung) und die Befehlssubstitution (Befehlstext plus das einschließende "$ ()" oder Backquotes) durch die Standardausgabe des Befehls ersetzt Sequenzen von einem oder mehreren Zeichen am Ende der Ersetzung. Eingebettete Zeichen vor dem Ende der Ausgabe dürfen nicht entfernt werden. Sie können jedoch als Feldtrennzeichen behandelt und während der Feldaufteilung entfernt werden, abhängig vom Wert des IFS und dem gültigen Angebot . Wenn die Ausgabe Null-Bytes enthält, ist das Verhalten nicht angegeben.

Standardwert IFS (zumindest in bash):

$ printf '%q\n' "$IFS"
$' \t\n'

In Ihrem Fall setzen Sie nicht IFS oder verwenden doppelte Anführungszeichen, sodass Zeilenumbrüche beim Aufteilen von Feldern entfernt werden.

Sie können Zeilenumbrüche beibehalten, indem Sie beispielsweise IFS auf leer setzen:

$ IFS=
$ a=$(cat links.txt)
$ echo "$a"
link1
link2
link3
28
cuonglm

Zeilenumbrüche werden an einigen Stellen ausgetauscht, da es sich um Sonderzeichen handelt. Um sie zu behalten, müssen Sie sicherstellen, dass sie immer interpretiert werden, indem Sie Anführungszeichen verwenden:

$ a="$(cat links.txt)"
$ echo "$a"
link1
link2
link3

Da ich bei der Bearbeitung der Daten Anführungszeichen verwendet habe, werden die Zeilenumbrüche (\n) wurde immer von der Shell interpretiert und blieb daher. Wenn Sie irgendwann vergessen, sie zu verwenden, gehen diese Sonderzeichen verloren.

Das gleiche Verhalten tritt auf, wenn Sie Ihre Schleife für Zeilen verwenden, die Leerzeichen enthalten. Zum Beispiel angesichts der folgenden Datei ...

mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

Die Ausgabe hängt davon ab, ob Sie Anführungszeichen verwenden oder nicht:

$ for i in $(cat links.txt); do echo $i; done
mypath1/file
with
spaces.txt
mypath2/filewithoutspaces.txt

$ for i in "$(cat links.txt)"; do echo "$i"; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

Wenn Sie keine Anführungszeichen verwenden möchten, gibt es eine spezielle Shell-Variable, mit der Sie das Shell-Feldtrennzeichen (IFS) ändern können. Wenn Sie dieses Trennzeichen auf das Zeilenumbruchzeichen setzen, werden die meisten Probleme behoben.

$ IFS=$'\n'; for i in $(cat links.txt); do echo $i; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

Der Vollständigkeit halber hier ein weiteres Beispiel, das nicht auf der Ersetzung der Befehlsausgabe beruht. Nach einiger Zeit stellte ich fest, dass diese Methode von den meisten Benutzern aufgrund des Verhaltens des Dienstprogramms read als zuverlässiger angesehen wurde.

$ cat links.txt | while read i; do echo $i; done

Hier ist ein Auszug aus der Manpage von read:

Das Lese-Dienstprogramm muss eine einzelne Zeile von der Standardeingabe lesen.

Da read zeilenweise eingegeben wird, können Sie sicher sein, dass es nicht unterbrochen wird, wenn ein Leerzeichen angezeigt wird. Führen Sie einfach die Ausgabe von cat durch eine Pipe, und es wird gut über Ihre Zeilen iterieren.

Bearbeiten: Ich kann anderen Antworten und Kommentaren entnehmen, dass die Leute bei der Verwendung von cat ziemlich zurückhaltend sind. Wie jasonwryan in seinem Kommentar sagte, besteht eine bessere richtige Methode zum Lesen einer Datei in Shell darin, die Stream-Umleitung zu verwenden ( <), wie Sie in val0x00ffs Antwort hier sehen können . Da die Frage jedoch nicht "wie man eine Datei in der Shell-Programmierung liest/verarbeitet" lautet, ist meine Antwort konzentriert sich mehr auf das Verhalten der Anführungszeichen und nicht auf den Rest.

40
John WH Smith

Um meine Betonung hinzuzufügen, iterieren for Schleifen über Wörter. Wenn Ihre Datei ist:

one two
three four

Dann wird vier Zeilen ausgegeben:

for Word in $(cat file); do echo "$Word"; done

Gehen Sie folgendermaßen vor, um die Zeilen einer Datei zu durchlaufen:

while IFS= read -r line; do
    # do something with "$line" <-- quoted almost always
done < file
6
glenn jackman

Sie können read aus bash verwenden. Suchen Sie auch nach dem mapfile

while read -r link
  do
   printf '%s\n' "$link"
  done < links.txt

Oder mit Mapfile

mapfile -t myarray < links.txt
for link in "${myarray[@]}"; do printf '%s\n' "$link"; done
4