it-swarm.com.de

Wie kann man die letzte Zeile in einer Datei mit "while read" (Bash) lesen, wenn am Ende der Datei keine neue Zeile steht?

Angenommen, ich habe das folgende Bash-Skript:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

Ich habe festgestellt, dass bei Dateien ohne Zeilenumbruch am Ende die letzte Zeile effektiv übersprungen wird.

Ich habe nach einer Lösung gesucht und diese gefunden :

Wenn das Lesen stattdessen das Ende der Datei erreicht von Zeilenende liest es in der Daten und ordnen Sie es den Variablen zu, aber es wird mit einem Nicht-Null-Status beendet . Wenn Ihre Schleife aufgebaut ist "while Read; do stuff; done

Statt den Lese-Exit zu testen, __. Status direkt, testen Sie eine Flagge und haben Der Lesebefehl setzt dieses Flag von innerhalb des Schleifenkörpers. Dieser Weg unabhängig vom Status der Lesevorgänge, der Der gesamte Körper der Schleife wird ausgeführt, weil read war nur einer der Befehle in der Schleife wie jeder andere, kein entscheidender Faktor, ob die Schleife überhaupt laufen.

DONE=false
until $DONE ;do
read || DONE=true
# process $REPLY here
done < /path/to/file.in

Wie kann ich diese Lösung umschreiben, damit sie sich genau wie die while-Schleife verhält, die ich zuvor hatte, d. H.

43
Mathias Bynens

In Ihrem ersten Beispiel gehe ich davon aus, dass Sie aus stdin lesen. Um dasselbe mit dem zweiten Codeblock zu tun, müssen Sie lediglich die Umleitung und das Echo $ REPLY entfernen:

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done
13
netcoder

Ich verwende folgendes Konstrukt:

while IFS= read -r LINE || [[ -n "$LINE" ]]; do
    echo "$LINE"
done

Es funktioniert so ziemlich alles außer Nullzeichen in der Eingabe:

  • Dateien, die mit leeren Zeilen beginnen oder enden
  • Zeilen, die mit Leerzeichen beginnen oder enden
  • Dateien, die keinen abschließenden Zeilenumbruch haben
31
Adam Bryzak

grep mit while-Schleife verwenden:

while IFS= read -r line; do
  echo "$line"
done < <(grep "" file)

Bei Verwendung von grep . anstelle von grep "" werden die leeren Zeilen übersprungen.

Hinweis: 

  1. Bei Verwendung von IFS= bleibt die Zeileneinrückung erhalten.

  2. Sie sollten fast immer die Option -r mit read verwenden.

  3. Dateien ohne Zeilenumbruch sind keine Standard-Unix-Textdateien.

4
Jahid

Verwenden Sie anstelle von read GNU Coreutils wie tee , cat usw.

von stdin

readvalue=$(tee)
echo $readvalue

aus Datei

readvalue=$(cat filename)
echo $readvalue
2
prabhakaran9397

Das grundlegende Problem hierbei ist, dass read den Fehlerlevel 1 zurückgibt, wenn EOF auftritt, selbst wenn die Variable immer noch korrekt eingespeist wird.

So können Sie den errorlevel von read direkt in Ihrer Schleife verwenden, andernfalls werden die letzten Daten nicht analysiert. Aber du könntest das tun:

eof=
while [ -z "$eof" ]; do
    read SCRIPT_SOURCE_LINE || eof=true   ## detect eof, but have a last round
    echo "$SCRIPT_SOURCE_LINE"
done

Wenn Sie eine sehr solide Methode zum Analysieren Ihrer Zeilen wünschen, sollten Sie Folgendes verwenden:

IFS='' read -r LINE

Erinnere dich daran:

  • NUL-Zeichen werden ignoriert
  • wenn Sie echo verwenden, um das Verhalten von cat nachzuahmen, müssen Sie einen echo -n erzwingen, wenn EOF erkannt wird (Sie können die Bedingung [ "$eof" == true ] verwenden.)
1
vaab

@ Netcoders Antwort ist gut, diese Optimierung beseitigt störende Leerzeilen und erlaubt auch, dass die letzte Zeile keinen Zeilenumbruch hat, wenn das Original so war.

DONE=false
NL=
until $DONE ;do
if ! read ; then DONE=true ; NL='-n ';fi
echo $NL$REPLY
done

Ich habe eine Variante davon verwendet, um zwei Funktionen zu erstellen, die das Piping von Text ermöglichen, der ein '[' enthält, um grep glücklich zu machen. (Sie können andere Übersetzungen hinzufügen)

function grepfix(){
    local x="[email protected]";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\[/\\\[}
      done
    else
      echo "${x//\[/\\\[}"
    fi
 }


 function grepunfix(){
    local x="[email protected]";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\\\[/\[}
      done
    else
      echo "${x//\\\[/\[}"
    fi
 }

(bestanden - da $ 1 Pipe aktiviert, sonst werden nur Argumente übersetzt)

0
unsynchronized

Dies ist das Muster, das ich verwendet habe:

while read -r; do
  echo "${REPLY}"
done
[[ ${REPLY} ]] && echo "${REPLY}"

Das funktioniert, weil die while-Schleife auch dann endet, wenn der "test" der read mit einem Nicht-Null-Code endet. read füllt immer noch die eingebaute Variable $REPLY (oder welche Variablen Sie mit read zuweisen).

0
Olli K