Antworten:
Die folgende Lösung liest aus einer Datei, wenn das Skript mit einem Dateinamen als erstem Parameter aufgerufen wird, $1
andernfalls aus der Standardeingabe.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
Die Ersetzung ${1:-...}
erfolgt, $1
wenn anders definiert, der Dateiname der Standardeingabe des eigenen Prozesses.
/proc/$$/fd/0
und /dev/stdin
? Mir ist aufgefallen, dass Letzteres häufiger vorkommt und einfacher aussieht.
-r
Ihren read
Befehl zu ergänzen , damit er nicht versehentlich \
Zeichen frisst . Verwenden Sie while IFS= read -r line
diese Option, um führende und nachfolgende Leerzeichen beizubehalten.
/bin/sh
Sie eine andere Shell als bash
oder verwenden sh
.
Die vielleicht einfachste Lösung besteht darin, stdin mit einem zusammenführenden Umleitungsoperator umzuleiten:
#!/bin/bash
less <&0
Stdin ist Dateideskriptor Null. Das Obige sendet die Eingabe, die an Ihr Bash-Skript weitergeleitet wird, an less's stdin.
<&0
In dieser Situation hat die Verwendung keinen Vorteil - Ihr Beispiel funktioniert mit oder ohne es gleich - anscheinend sehen Tools, die Sie innerhalb eines Bash-Skripts aufrufen, standardmäßig denselben Standard wie das Skript selbst (es sei denn, das Skript verwendet es zuerst).
Hier ist der einfachste Weg:
#!/bin/sh
cat -
Verwendung:
$ echo test | sh my_script.sh
test
Um der Variablen stdin zuzuweisen , können Sie Folgendes verwenden: STDIN=$(cat -)
oder einfach nur, STDIN=$(cat)
da kein Operator erforderlich ist (gemäß @ mklement0-Kommentar ).
Versuchen Sie das folgende Skript, um jede Zeile aus 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 (wenn kein Argument 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
- Behandeln Sie einen Backslash-Charakter nicht auf besondere Weise. Betrachten Sie jeden Backslash als Teil der Eingabezeile.- Ohne Einstellung
IFS
werden standardmäßig die Sequenzen von Spaceund Tabam Anfang und Ende der Zeilen ignoriert (getrimmt).- Verwenden Sie
printf
stattecho
, um zu vermeiden, dass leere Zeilen gedruckt werden, wenn die Zeile aus einer einzelnen Zeile besteht-e
,-n
oder-E
. Es gibt jedoch eine Problemumgehung, mitenv POSIXLY_CORRECT=1 echo "$line"
der Ihre externe GNU ausgeführt wird,echo
die sie unterstützt. Siehe: Wie kann ich "-e" wiedergeben?
Siehe: Wie lese ich stdin, wenn keine Argumente übergeben werden? bei Stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
zu FILE=${1:--}
. (Quibble: besser, alle Großbuchstaben Shell- Variablen zu vermeiden, um Namenskollisionen mit Umgebungsvariablen zu vermeiden .)
${1:--}
ist POSIX-konform, so dass es in allen POSIX-wie Muscheln funktionieren soll. Was in all diesen Shells nicht funktioniert, ist die Prozessersetzung ( <(...)
); es wird zum Beispiel in bash, ksh, zsh funktionieren, aber nicht in dash. Es ist auch besser, -r
Ihren read
Befehl zu ergänzen , damit er nicht versehentlich \
Zeichen frisst . Stellen Sie vor IFS=
, um führende und nachfolgende Leerzeichen beizubehalten.
echo
: Wenn eine Zeile aus oder besteht -e
, wird sie nicht angezeigt. Um dies zu beheben, müssen Sie Folgendes verwenden : . Ich habe es nicht in meine vorherige Bearbeitung aufgenommen. Zu oft werden meine Änderungen rückgängig gemacht, wenn ich diesen Fehler behebe . -n
-E
printf
printf '%s\n' "$line"
:(
--
ist nutzlos, wenn das erste Argument ist'%s\n'
IFS=
mit read
und printf
anstelle von verwenden würden echo
. :)
.
Ich denke, das ist der einfache 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
read
liest aus stdin standardmäßig , so gibt es keine Notwendigkeit für < /dev/stdin
.
Die echo
Lösung fügt immer dann neue Zeilen hinzu, wenn IFS
der Eingabestream unterbrochen wird. Die Antwort von @ fgm kann ein wenig geändert werden:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
das Verhalten bezogen haben: while read
wird möglicherweise von den Zeichen in mehrere Token aufgeteilt. enthalten $IFS
, gibt es nur ein einzelnes Token zurück, wenn Sie nur einen einzelnen Variablennamen angeben (aber standardmäßig werden Leerzeichen und führende und nachfolgende Leerzeichen abgeschnitten).
read
und $IFS
- echo
selbst zu und füge neue Zeilen ohne -n
Flag hinzu. "Das Dienstprogramm echo schreibt alle angegebenen Operanden, die durch einzelne Leerzeichen (` ') und gefolgt von einem Zeilenumbruchzeichen (`\ n') getrennt sind, in die Standardausgabe."
\n
hinzufügen echo
: Perl's $_
enthält die Zeile, die \n
von der gelesenen Zeile endet , während Bashs read
dies nicht tut. (Wie @gniourf_gniourf jedoch an anderer Stelle ausführt, ist es robuster, printf '%s\n'
anstelle von echo
) zu verwenden.
Die Perl-Schleife in der Frage liest aus allen Dateinamenargumenten in der Befehlszeile oder aus der Standardeingabe, wenn keine Dateien angegeben sind. Die Antworten, die ich sehe, scheinen alle 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, und es ist fraglich, ob dies eines davon ist:
cat "$@" |
while read -r line
do
echo "$line"
done
Der einzige Nachteil dabei ist, dass eine Pipeline erstellt wird, die in einer Sub-Shell ausgeführt wird, also Dinge wie Variablenzuweisungen in der while
Schleife außerhalb der Pipeline nicht zugänglich sind. Der bash
Weg dahin ist die Prozesssubstitution :
while read -r line
do
echo "$line"
done < <(cat "$@")
Dies verlässt die while
Schleife in der Haupt-Shell ausgeführt, sodass auf in der Schleife festgelegte Variablen außerhalb der Schleife zugegriffen werden kann.
>>EOF\n$(cat "$@")\nEOF
. Schließlich ein Streitpunkt: while IFS= read -r line
ist eine bessere Annäherung an das, was while (<>)
in Perl funktioniert (behält führende und nachfolgende Leerzeichen bei - obwohl Perl auch die nachfolgenden Leerzeichen beibehält \n
).
Perls Verhalten mit dem im OP angegebenen Code kann kein 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 zu haben $ARGV
. Keine der bisher gegebenen Antworten ahmt Perls Verhalten in dieser Hinsicht wirklich nach. Hier ist eine reine Bash-Möglichkeit. Der Trick ist, exec
angemessen zu verwenden .
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
Dateiname ist verfügbar in $1
.
Wenn keine Argumente angegeben werden, setzen wir künstlich -
den ersten Positionsparameter. Wir durchlaufen dann die Parameter. Ist dies kein Parameter -
, leiten wir die Standardeingabe vom Dateinamen mit um exec
. Wenn diese Umleitung erfolgreich ist, schleifen wir mit einer while
Schleife. Ich verwende die Standardvariable REPLY
, und in diesem Fall müssen Sie nicht zurücksetzen IFS
. Wenn Sie einen anderen Namen möchten, müssen Sie diesen zurücksetzen IFS
(es sei denn, Sie möchten das natürlich nicht und wissen, was Sie tun):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Genauer...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
und -r
zum read
Befehl wird sichergestellt, dass jede Zeile unverändert gelesen wird (einschließlich führender und nachfolgender Leerzeichen).
Bitte versuchen Sie den folgenden Code:
while IFS= read -r line; do
echo "$line"
done < file
read
ohne IFS=
und -r
und die Armen $line
ohne ihre gesunden Zitate zu sehen.
read -r
Notation nicht. IMO, POSIX hat das falsch verstanden; Die Option sollte die spezielle Bedeutung für nachfolgende Backslashes aktivieren und nicht deaktivieren, damit vorhandene Skripte (von vor POSIX) nicht beschädigt werden, da die -r
weggelassen wurden. Ich stelle jedoch fest, dass es Teil von IEEE 1003.2 1992 war, der frühesten Version des POSIX-Shell- und Utilities-Standards, aber es wurde bereits damals als Ergänzung markiert, sodass es um längst vergangene Möglichkeiten geht. Ich bin nie in Schwierigkeiten geraten, weil mein Code nicht verwendet wird -r
. Ich muss Glück haben. Ignoriere mich dabei.
-r
dies Standard sein sollte. Ich bin damit einverstanden, dass dies in Fällen unwahrscheinlich ist, in denen die Nichtverwendung zu Problemen führt. Fehlerhafter Code ist jedoch fehlerhafter Code. Meine Bearbeitung wurde zuerst durch diese schlechte $line
Variable ausgelöst , die ihre Anführungszeichen stark verfehlt hat. Ich habe das behoben, read
während ich dabei war. Ich habe das nicht behoben, echo
weil dies die Art von Bearbeitung ist, die zurückgesetzt wird. :(
.
Code ${1:-/dev/stdin}
wird nur das erste Argument verstehen, also wie wäre es damit?
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Ich finde keine dieser Antworten akzeptabel. Insbesondere behandelt die akzeptierte Antwort nur den ersten Befehlszeilenparameter und ignoriert den Rest. Das Perl-Programm, das es zu emulieren versucht, verarbeitet 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, dass Eingabe zu Ausgabe wiederholt wird, oder sind einfach unnötig kompliziert.
Ich muss ihnen jedoch etwas Anerkennung zollen, 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 "$@" $DEFAULT_INPUT_FILE
do
while IFS= read -r LINE
do
# Do whatever you want with LINE here.
echo $LINE
done < "$FILE"
done
Ich habe alle oben genannten Antworten kombiniert und eine Shell-Funktion erstellt, die meinen Anforderungen entspricht. Dies ist von einem Cygwin-Terminal meiner 2 Windows 10-Computer, auf dem sich ein freigegebener Ordner befand. Ich muss in der Lage sein, Folgendes zu handhaben:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Wenn ein bestimmter Dateiname angegeben ist, muss beim Kopieren derselbe Dateiname verwendet werden. Wenn der Eingabedatenstrom durchgeleitet wurde, muss ich einen temporären Dateinamen mit der Stunde, Minute und Sekunde generieren. Der freigegebene Hauptordner enthält Unterordner der Wochentage. Dies dient organisatorischen Zwecken.
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 weiter zu optimieren, würde ich gerne wissen.
Das Folgende funktioniert mit Standard sh
(Getestet mit dash
auf Debian) und ist gut 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, sonst cat
Standardeingabe. Dann wird die Ausgabe der gesamten if
Anweisung von der verarbeitet commands_and_transformations
.
cat "${1:--}" | any_command
. Das Lesen und Wiedergeben von Shell-Variablen funktioniert möglicherweise für kleine Dateien, ist jedoch nicht so gut skalierbar.
[ -n "$1" ]
kann vereinfacht werden [ "$1" ]
.
Wie wäre es mit
for line in `cat`; do
something($line);
done
cat
wird in die Befehlszeile eingefügt. Die Befehlszeile hat eine maximale Größe. Auch dies wird nicht Zeile für Zeile, sondern Wort für Wort gelesen.