Lesen einer vollständigen Zeile in einer For-Schleife mit Leerzeichen


130

Ich versuche, eine forSchleife für Akte laufen zu lassen und ich möchte vollständige Zeile anzeigen. Stattdessen wird nur das letzte Wort angezeigt. Ich möchte die komplette Linie.

for j in `cat ./file_wget_med`

do
echo $j

done

Ergebnis nach dem Lauf:

Found.

Hier sind meine Daten:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.

Antworten:


179

forDie Schleife wird geteilt, wenn Leerzeichen, Tabulatoren oder Zeilenumbrüche angezeigt werden. Daher sollten Sie IFS (Internal Field Separator) verwenden :

IFS=$'\n'       # make newlines the only separator
for j in $(cat ./file_wget_med)    
do
    echo "$j"
done
# Note: IFS needs to be reset to default!

5
Achten Sie bei IFS auf einfache Anführungszeichen, da IFS = $ "\ n" auch "nn" Zeichenfolgen aufteilt. Ich fand set IFS zuverlässiger als kompliziertere Syntax oder Funktionen.
erm3nda

23
anscheinend ist es ratsam, unset IFSdanach, damit andere befehle nicht in der gleichen shell beeinflusst werden.
Boris Däppen

2
Was bedeutet das $in IFS=$'\n'?
Ren

2
$Lässt Zeilenumbrüche in der Zeichenfolge funktionieren. Dies sollte auch local IFS=$'\n'innerhalb einer Funktion erfolgen.
Andy Ray

1
@Renn, die $'...'bekannt ist " ANSI-C Quoting "
αғsнιη

82

forLoops werden standardmäßig in Leerzeichen (Leerzeichen, Tabulator, Zeilenvorschub) aufgeteilt. Die einfachste Möglichkeit, eine Zeile gleichzeitig zu bearbeiten, besteht darin, while readstattdessen eine Schleife zu verwenden, die in Zeilenumbrüche aufgeteilt wird:

while read i; do echo "$i"; done < ./file_wget_med

Ich würde erwarten, dass Ihr Befehl ein Wort pro Zeile ausspuckt (das ist passiert, als ich es mit einer eigenen Datei ausprobiert habe). Wenn etwas anderes passiert, bin ich mir nicht sicher, was es verursachen könnte.


8
Dies ist auch eine gute Alternative zu dem for i in `ls`; do echo $1; done, durch die Ausgabe an den während Befehl kochend: ls|while read i; do echo $i; done. Dies funktioniert bei Datei- / Ordnernamen mit Leerzeichen, ohne dass Umgebungsvariablen geändert werden müssen. Sehr gute Antwort.
Jishi

Die erste und die letzte Zeile wurden in der
Zwischenzeit

@jishi Da es in erster Linie für die Anzeige entwickelt wurde und auf die Größe des Terminalfensters und dergleichen anspricht, lsgilt es als nicht sicher für die Verwendung mit |(dem Pipe-Operator). findist flexibler und sicherer für diesen Zweck.
SeldomNeedy

3
Dies ist eine großartige Antwort. Ich habe damit eine Liste, die ich hatte, in PLIST-Einträge für eine iOS-App umgewandelt, die ich auf Mac OS X entwickelt habe. Ich habe die Dateieingabe durch Piping des Clipboads mit pbpaste ->pbpaste | while read t; do echo "<string>$t</string>"; done
Big Rich ersetzt.

3
ls -1kann verwendet werden |, um eine unformatierte Ausgabe pro Zeile zu gewährleisten
AndreyS Scherbakov

19
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Geprüftes Arbeiten, nicht der eine Liner, den Sie wahrscheinlich wollen, aber es ist nicht möglich, dies elegant zu tun.


1
eine hier-Zeichenfolge! eleganteste aller anderen Lösungen IMO
Eliran Malka

5

Hier ist eine kleine Erweiterung von Mitchell Curries Antwort, die mir aufgrund des geringen Umfangs der Nebenwirkungen gefällt, bei denen es nicht erforderlich ist, eine Variable festzulegen:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"

1
Sie könnten entfernen -name '*'und es wäre das gleiche, nein?
wjandrea

1

Ich würde es so schreiben:

cat ./file_wget_med | while read -r j
do
    echo $j
done

da es am wenigsten Änderungen im Originalskript erfordert (außer für die verwendete Lösung IFS, aber es modifiziert das bashVerhalten nicht nur für diese Schleifensteuerungsanweisung).


1
Rohr und catsind hier unnötig. whileSchleife akzeptiert Umleitung ganz gut, weshalb dies normalerweise geschrieben wird alswhile IFS= read -r line; do...done < input.txt
Sergiy Kolodyazhnyy

0

Mapfile ist eine bequeme Methode, um Zeilen aus einer Datei in ein indiziertes Array zu lesen. Es ist nicht so portabel wie read, aber etwas schneller. Durch die Verwendung der for-Schleife vermeiden Sie das Erstellen einer Subshell.

#!/bin/bash

mapfile -t < file.txt

for line in "${MAPFILE[@]}"; do
    echo $line
done

Beachten Sie bei der Verwendung von Pipelines, dass die while-Schleife in eine Subshell umgewandelt wird. Änderungen innerhalb der while-Schleife wie Variablen werden nicht an den äußeren Teil des Skripts weitergegeben.

Beispiel:

#!/bin/bash

a=0
printf %s\\n {0..5} | while read; do
  ((a++))
done
echo $a # 'a' will always be 0.

(Bessere Lösung):

#!/bin/bash

b=0
while read; do
  ((b++))
done < <(printf %s\\n {0..5})

echo $b # 'b' equal to 6 (works as expected).

-1

Dandalf kam einer funktionellen Lösung sehr nahe, aber man sollte NIEMALS versuchen, das Ergebnis unbekannter Eingabemengen (dh find ~/.gdfuse -name '*') Variablen zuzuweisen ! Oder zumindest versuchen, so etwas über eine Array-Variable zu tun; wenn Sie darauf bestehen, so faul zu sein! Also, hier ist Dandalfs Lösung, die ohne das gefährliche Manöver auskommt. und das alles in einer zeile

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'

Hey @odoncaoa, ich habe versucht, den Befehl find ohne doppelte Anführungszeichen auszuführen, aber dadurch werden alle Ergebnisse als eine Zeile angezeigt. Ich bin verwirrt über die "unbekannten Mengen an Input" mit diesem Befehl und den damit verbundenen Gefahren. Können Sie ein Beispiel für die Gefahren geben und wie sie theoretisch auftreten könnten? Ich möchte ernsthaft nicht faul sein, ich fühle mich nur in anderen Programmiersprachen wohler.
Dandalf
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.