wake-up-neo.com

Wie liest man aus einer Datei oder einem Standard in Bash?

In Perl wird der folgende Code aus der in den Befehlszeilenargs angegebenen Datei oder aus dem Befehl stdin gelesen:

while (<>) {
   print($_);
}

Das ist sehr bequem. Ich möchte nur wissen, was der einfachste Weg ist, um aus Dateien oder Stdin in Bash zu lesen.

195
Dagang

Die folgende Lösung liest aus einer Datei, wenn das Skript .__ mit dem Dateinamen als ersten Parameter $1 aufgerufen wird, ansonsten von der Standardeingabe. 

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

Die Ersetzung ${1:-...} nimmt $1 an, wenn ansonsten definiert Der Dateiname der Standardeingabe des eigenen Prozesses wird verwendet.

324
Fritz G. Mehner

Die einfachste Lösung besteht darin, stdin mit einem zusammenführenden Umleitungsoperator umzuleiten:

#!/bin/bash
less <&0

Stdin ist der Dateideskriptor Null. Das obige sendet die Eingabe, die an Ihr Bash-Skript geleitet wird, in den Standardwert von less.

Lesen Sie mehr über die Umleitung von Dateideskriptoren .

89
Ryan Ballantyne

Hier ist der einfachste Weg:

#!/bin/sh
cat -

Verwendungszweck:

$ echo test | sh my_script.sh
test

Um der Variablen ein stdin zuzuweisen, können Sie Folgendes verwenden: STDIN=$(cat -) oder einfach STDIN=$(cat), da kein Operator erforderlich ist (gemäß @ mklement0 comment ).


Verwenden Sie das folgende Skript, um jede Zeile der Standardeingabe zu analysieren:

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done

Um aus der Datei oder stdin zu lesen (falls Argument nicht vorhanden ist), können Sie es erweitern auf:

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")

Anmerkungen:

- read -r - Behandle ein Backslash-Zeichen nicht auf besondere Weise. Betrachten Sie jeden Backslash als Teil der Eingabezeile.

- Ohne IFS einzustellen, werden standardmäßig die Folgen von Space und Tab am Anfang und Ende der Zeilen werden ignoriert (abgeschnitten).

- Verwenden Sie printf anstelle von echo, um zu vermeiden, dass leere Zeilen gedruckt werden, wenn die Zeile aus einem einzelnen -e, -n oder -E besteht. Es gibt jedoch eine Problemumgehung, indem env POSIXLY_CORRECT=1 echo "$line" verwendet wird, der Ihr externes GNU echo ausführt, das dies unterstützt. Siehe: Wie kann ich "-e" ausgeben?

Siehe: Wie wird stdin gelesen, wenn keine Argumente übergeben werden? at stackoverflow SE

63
kenorb

Ich denke, das ist der direkte Weg:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

-

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

-

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5
13
Amir Mehler

Die Lösung echo fügt neue Zeilen hinzu, wenn IFS den Eingabestrom unterbricht. @ fgms Antwort kann etwas geändert werden:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
13
David Souther

Die Perl-Schleife in der Frage liest die Dateinamenargumente in der Befehlszeile aus all oder aus der Standardeingabe, wenn keine Dateien angegeben sind. Die Antworten, die ich alle sehe, scheinen eine einzelne Datei oder Standardeingabe zu verarbeiten, wenn keine Datei angegeben ist.

Obwohl oft genau als UUOC (Useless Use of cat) verspottet, gibt es Zeiten, in denen cat das beste Werkzeug für den Job ist.

cat "[email protected]" |
while read -r line
do
    echo "$line"
done

Der einzige Nachteil besteht darin, dass eine Pipeline erstellt wird, die in einer Sub-Shell ausgeführt wird, sodass Variablenzuweisungen in der while-Schleife nicht außerhalb der Pipeline verfügbar sind. Die bash-Möglichkeit ist: Substitution bearbeiten :

while read -r line
do
    echo "$line"
done < <(cat "[email protected]")

Dadurch wird die while-Schleife in der Hauptshell ausgeführt, sodass auf in der Schleife gesetzte Variablen außerhalb der Schleife zugegriffen werden kann.

6

Das Verhalten von Perl kann mit dem im OP angegebenen Code keine oder mehrere Argumente annehmen. Wenn ein Argument ein einzelner Bindestrich - ist, wird dies als stdin verstanden. Außerdem ist es immer möglich, den Dateinamen mit $ARGV..__ zu erhalten. Keine der bisher gegebenen Antworten ahmt Perls Verhalten in dieser Hinsicht wirklich nach. Hier ist eine reine Bash-Möglichkeit. Der Trick besteht darin, exec entsprechend zu verwenden.

#!/bin/bash

(($#)) || set -- -
while (($#)); do
   { [[ $1 = - ]] || exec < "$1"; } &&
   while read -r; do
      printf '%s\n' "$REPLY"
   done
   shift
done

Dateiname ist in $1 verfügbar.

Wenn keine Argumente angegeben sind, setzen wir - künstlich als ersten Positionsparameter. Wir wiederholen dann die Parameter. Wenn ein Parameter nicht - ist, leiten wir die Standardeingabe mit exec von Dateiname um. Wenn diese Umleitung erfolgreich ist, führen wir eine Schleife mit einer while-Schleife durch. Ich verwende die Standardvariable REPLY, und in diesem Fall müssen Sie IFS nicht zurücksetzen. Wenn Sie einen anderen Namen wünschen, müssen Sie IFS so zurücksetzen (es sei denn, Sie möchten das nicht und wissen, was Sie tun):

while IFS= read -r line; do
    printf '%s\n' "$line"
done
4
gniourf_gniourf

Genauer...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file
2
Sorpigal

Bitte versuchen Sie den folgenden Code:

while IFS= read -r line; do
    echo "$line"
done < file
2
Webthusiast

Code ${1:-/dev/stdin} wird nur das erste Argument verstehen.

ARGS='$*'
if [ -z "$*" ]; then
  ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
   echo "$line"
done
1

Ich habe alle obigen Antworten kombiniert und eine Shell-Funktion erstellt, die meinen Bedürfnissen entspricht. Dies ist von einem Cygwin-Terminal meiner 2 Windows10-Computer, auf dem ich einen gemeinsamen Ordner hatte. Ich muss mit dem folgenden umgehen können:

  • cat file.cpp | tx
  • tx < file.cpp
  • tx file.cpp

Wo ein bestimmter Dateiname angegeben ist, muss ich beim Kopieren denselben Dateinamen verwenden. Wenn der Eingabedatenstrom durchgeleitet wurde, muss ein temporärer Dateiname mit der Stunde, Minute und Sekunden erstellt werden. Der freigegebene Hauptordner enthält Unterordner der Wochentage. Dies ist aus organisatorischen Gründen. 

Siehe, das ultimative Skript für meine Bedürfnisse:

tx ()
{
  if [ $# -eq 0 ]; then
    local TMP=/tmp/tx.$(date +'%H%M%S')
    while IFS= read -r line; do
        echo "$line"
    done < /dev/stdin > $TMP
    cp $TMP //$OTHER/stargate/$(date +'%a')/
    rm -f $TMP
  else
    [ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
  fi
}

Wenn es eine Möglichkeit gibt, dies zu optimieren, würde ich gerne wissen.

0
typelogic

Folgendes funktioniert mit Standard sh (Getestet mit dash unter Debian) und ist durchaus lesbar, aber das ist Geschmackssache:

if [ -n "$1" ]; then
    cat "$1"
else
    cat
fi | commands_and_transformations

Details: Wenn der erste Parameter nicht leer ist, dann cat diese Datei, andernfalls cat Standardeingabe. Dann wird die Ausgabe der gesamten if-Anweisung vom commands_and_transformations verarbeitet.

0
Notinlist

Ich finde keine dieser Antworten akzeptabel. Insbesondere akzeptiert die akzeptierte Antwort nur den ersten Befehlszeilenparameter und ignoriert den Rest. Das Perl-Programm, das es zu emulieren versucht, behandelt alle Befehlszeilenparameter. Die akzeptierte Antwort beantwortet also nicht einmal die Frage. Andere Antworten verwenden Bash-Erweiterungen, fügen unnötige "cat" -Befehle hinzu, funktionieren nur für den einfachen Fall des Echo-Inputs für die Ausgabe oder sind nur unnötig kompliziert.

Ich muss ihnen jedoch etwas Anerkennung geben, weil sie mir einige Ideen gegeben haben. Hier ist die vollständige Antwort:

#!/bin/sh

if [ $# = 0 ]
then
        DEFAULT_INPUT_FILE=/dev/stdin
else
        DEFAULT_INPUT_FILE=
fi

# Iterates over all parameters or /dev/stdin
for FILE in "[email protected]" $DEFAULT_INPUT_FILE
do
        while IFS= read -r LINE
        do
                # Do whatever you want with LINE here.
                echo $LINE
        done < "$FILE"
done
0
Gungwald