Odpowiedzi:
Poniższe rozwiązanie czyta z pliku, jeśli skrypt jest wywoływany z nazwą pliku jako pierwszym parametrem, w $1
przeciwnym razie ze standardowego wejścia.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
Podstawienie ${1:-...}
trwa, $1
jeśli zdefiniowano inaczej, używana jest nazwa pliku standardowego wejścia własnego procesu.
/proc/$$/fd/0
a /dev/stdin
? Zauważyłem, że ten drugi wydaje się bardziej powszechny i wygląda na bardziej prosty.
-r
do swojej read
komendy, aby przypadkowo nie zjadła \
znaków; służy while IFS= read -r line
do zachowania wiodących i końcowych białych znaków.
/bin/sh
- czy używasz powłoki innej niż bash
lub sh
?
Być może najprostszym rozwiązaniem jest przekierowanie stdin za pomocą łączącego operatora przekierowania:
#!/bin/bash
less <&0
Stdin to zero deskryptorów plików. Powyższe powoduje przesłanie danych wejściowych do skryptu bash do standardowego interfejsu less.
<&0
tej sytuacji nie ma żadnej korzyści - twój przykład będzie działał tak samo z nią, jak i bez niej - najwyraźniej narzędzia, które wywołujesz z poziomu skryptu bash, domyślnie widzą to samo stdin co sam skrypt (chyba że skrypt zużyje go jako pierwszy).
Oto najprostszy sposób:
#!/bin/sh
cat -
Stosowanie:
$ echo test | sh my_script.sh
test
Aby przypisać stdin do zmiennej, możesz użyć: STDIN=$(cat -)
lub po prostu, STDIN=$(cat)
ponieważ operator nie jest konieczny (zgodnie z komentarzem @ mklement0 ).
Aby przeanalizować każdy wiersz ze standardowego wejścia , wypróbuj następujący skrypt:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
Aby odczytać z pliku lub standardowego wejścia (jeśli nie ma argumentu), możesz go rozszerzyć do:
#!/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")
Uwagi:
-
read -r
- Nie traktuj ukośnika w żaden szczególny sposób. Rozważ każdy ukośnik odwrotny jako część linii wejściowej.- Bez ustawienia
IFS
, domyślnie sekwencje SpaceiTab na początku i na końcu linii są ignorowane (przycięte).- Użyj
printf
zamiast,echo
aby uniknąć drukowania pustych linii, gdy linia składa się z jednego-e
,-n
lub-E
. Jednakże istnieje obejście za pomocąenv POSIXLY_CORRECT=1 echo "$line"
którego wykonuje swoją zewnętrzną GNUecho
, który wspiera go. Zobacz: Jak wykonać echo „-e”?
Zobacz: Jak czytać stdin, gdy nie są przekazywane żadne argumenty? w stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
do FILE=${1:--}
. (Quibble: lepiej unikać zmiennych wielkimi literami w powłoce , aby uniknąć kolizji nazw ze zmiennymi środowiskowymi .)
${1:--}
jest zgodny z POSIX, więc powinien działać we wszystkich powłokach podobnych do POSIX. To, co nie zadziała we wszystkich takich powłokach, to proces substytucji ( <(...)
); będzie działał na przykład w bash, ksh, zsh, ale nie w desce rozdzielczej. Lepiej też dodać -r
do swojego read
polecenia, aby przypadkowo nie zjadł \
znaków; starają IFS=
się zachować wiodące i końcowe białe znaki.
echo
: jeśli linia składa się z -e
, -n
lub -E
, nie będą wyświetlane. Aby rozwiązać ten problem, należy użyć printf
: printf '%s\n' "$line"
. Nie uwzględniłem go w mojej poprzedniej edycji… zbyt często moje zmiany są wycofywane, gdy naprawiam ten błąd :(
.
--
jest bezużyteczne, jeśli pierwszy argument to'%s\n'
IFS=
z read
i printf
zamiast echo
. :)
.
Myślę, że jest to prosty sposób:
$ 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
czyta z stdin domyślnie , więc nie ma potrzeby na < /dev/stdin
.
echo
Rozwiązanie dodaje nowe linie gdy IFS
przełamuje strumień wejściowy. Odpowiedź @ fgm można nieco zmodyfikować:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
„s zachowanie: gdy read
ma potencjalnie podzielone na wiele żetonów przez znaki. zawarty w $IFS
nim, zwraca tylko jeden token, jeśli podasz tylko jedną nazwę zmiennej (ale domyślnie przycina i początkowe i końcowe białe znaki).
read
i $IFS
- echo
sama dodaje nowe linie bez -n
flagi. „Narzędzie echo zapisuje na standardowym wyjściu dowolne określone operandy, oddzielone pojedynczymi pustymi znakami (` `), a następnie znakiem nowej linii (` \ n '). ”
\n
dodać znak końcowy echo
: Perl $_
zawiera linię kończącą się \n
na czytanej linii, podczas gdy bash read
nie. (Jednak, jak wskazuje @gniourf_gniourf w innym miejscu, bardziej niezawodne podejście można zastosować printf '%s\n'
zamiast echo
).
Pętla Perla w pytaniu czyta wszystkie argumenty nazwy pliku w wierszu poleceń lub ze standardowego wejścia, jeśli nie określono żadnych plików. Wszystkie odpowiedzi, które widzę, wydają się przetwarzać pojedynczy plik lub standardowe dane wejściowe, jeśli nie określono pliku.
Chociaż często wyśmiewane jako UUOC (Bezużyteczne użycie cat
), zdarzają się chwile, kiedy cat
jest to najlepsze narzędzie do pracy i można argumentować, że jest to jedno z nich:
cat "$@" |
while read -r line
do
echo "$line"
done
Jedynym minusem jest to, że tworzy potok działający w podpowłoce, więc rzeczy takie jak przypisania zmiennych w while
pętli nie są dostępne poza potokiem. Rozwiązaniem bash
tego problemu jest proces zastępowania :
while read -r line
do
echo "$line"
done < <(cat "$@")
To powoduje, że while
pętla działa w głównej powłoce, więc zmienne ustawione w pętli są dostępne poza nią.
>>EOF\n$(cat "$@")\nEOF
. Wreszcie, spór: while IFS= read -r line
jest lepszym przybliżeniem tego, co while (<>)
robi w Perlu (zachowuje wiodące i końcowe białe spacje - chociaż Perl również utrzymuje końcowe \n
).
Zachowanie Perla, z kodem podanym w OP, nie może przyjmować żadnego lub kilku argumentów, a jeśli argument jest pojedynczym łącznikiem, -
jest to rozumiane jako standardowe. Co więcej, zawsze można mieć nazwę pliku $ARGV
. Żadna z podanych do tej pory odpowiedzi nie naśladuje zachowania Perla pod tym względem. Oto czysta możliwość Bash. Sztuka polega na exec
odpowiednim użyciu .
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
Nazwa pliku jest dostępna w $1
.
Jeśli nie podano żadnych argumentów, sztucznie ustawiamy -
jako pierwszy parametr pozycyjny. Następnie zapętlamy parametry. Jeśli parametr nie jest -
, przekierowujemy standardowe wejście z nazwy pliku za pomocą exec
. Jeśli przekierowanie się powiedzie, zapętlamy while
pętlę. Używam REPLY
zmiennej standardowej , w tym przypadku nie trzeba resetować IFS
. Jeśli chcesz mieć inną nazwę, musisz IFS
tak zresetować (chyba że oczywiście nie chcesz tego i wiesz, co robisz):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Dokładniej...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
i -r
do read
polecenia zapewnia, że każdy wiersz jest odczytywany w niezmodyfikowany sposób (w tym początkowe i końcowe białe znaki).
Wypróbuj następujący kod:
while IFS= read -r line; do
echo "$line"
done < file
read
bez IFS=
i -r
, i biednego $line
bez jego zdrowych cytatów.
read -r
notacja. IMO, POSIX źle to zrozumiał; opcja powinna włączyć specjalne znaczenie dla końcowych ukośników odwrotnych, a nie wyłączyć je - aby istniejące skrypty (sprzed istnienia POSIX-a) nie uległy awarii, ponieważ -r
zostały pominięte. Zauważam jednak, że był on częścią IEEE 1003.2 1992, który był najwcześniejszą wersją standardu powłoki i narzędzi POSIX, ale już wtedy został oznaczony jako dodatek, więc to dziwne o dawno minionych szansach. Nigdy nie miałem problemów, ponieważ mój kod nie używa -r
; Muszę mieć szczęście. Zignoruj mnie w tej sprawie.
-r
powinien być standardem. Zgadzam się, że jest to mało prawdopodobne w przypadkach, gdy nieużywanie go prowadzi do problemów. Chociaż uszkodzony kod jest uszkodzony. Moja edycja została po raz pierwszy uruchomiona przez tę kiepską $line
zmienną, która bardzo pominęła swoje cytaty. Naprawiłem read
czas, w którym tam byłem. Nie naprawiłem tego, echo
ponieważ taka zmiana jest wycofywana. :(
.
Kod ${1:-/dev/stdin}
po prostu zrozumie pierwszy argument, więc co powiesz na to.
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Żadna z tych odpowiedzi nie jest do przyjęcia. W szczególności zaakceptowana odpowiedź obsługuje tylko pierwszy parametr wiersza poleceń i ignoruje resztę. Program Perla, który próbuje emulować, obsługuje wszystkie parametry wiersza poleceń. Tak więc zaakceptowana odpowiedź nawet nie odpowiada na pytanie. Inne odpowiedzi używają rozszerzeń bash, dodają niepotrzebne polecenia „cat”, działają tylko w prostym przypadku echa wejścia do wyjścia lub są po prostu niepotrzebnie skomplikowane.
Muszę jednak podziękować im, ponieważ dali mi pomysły. Oto pełna odpowiedź:
#!/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
Połączyłem wszystkie powyższe odpowiedzi i stworzyłem funkcję powłoki, która odpowiada moim potrzebom. To pochodzi z terminalu cygwin na moich 2 komputerach z systemem Windows10, gdzie miałem między nimi folder współdzielony. Muszę być w stanie poradzić sobie z następującymi kwestiami:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Jeśli określona nazwa pliku jest określona, muszę użyć tej samej nazwy pliku podczas kopiowania. Tam, gdzie strumień danych wejściowych został potokowany, muszę wygenerować tymczasową nazwę pliku mającą godzinę, minutę i sekundy. Udostępniony główny folder ma podfoldery dni tygodnia. To jest do celów organizacyjnych.
Oto najlepszy skrypt dla moich potrzeb:
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
}
Jeśli jest jakiś sposób na dalszą optymalizację tego, chciałbym wiedzieć.
Poniższe działa ze standardem sh
(Testowane z dash
Debianem) i jest dość czytelne, ale to kwestia gustu:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Szczegóły: jeśli pierwszy parametr nie jest pusty, to cat
ten plik, w przeciwnym razie cat
standardowe wejście. Następnie dane wyjściowe całej if
instrukcji są przetwarzane przez commands_and_transformations
.
cat "${1:--}" | any_command
. Odczytywanie zmiennych do powłoki i ich echo może działać dla małych plików, ale nie skaluje się tak dobrze.
[ -n "$1" ]
Mogą być uproszczone [ "$1" ]
.
Co powiesz na
for line in `cat`; do
something($line);
done
cat
zostaną umieszczone w wierszu polecenia. Wiersz polecenia ma maksymalny rozmiar. Również to nie będzie czytać wiersz po wierszu, ale słowo po słowie.