Jak odczytać z pliku lub stdin w Bash?

W Perlu następujący kod będzie odczytywany z pliku określonego w args linii poleceń lub ze standardowego wejścia:

while (<>) {
   print($_);
}
To bardzo wygodne. Chcę tylko wiedzieć, jaki jest najprostszy sposób odczytu z pliku lub stdin w bash.
 178
Author: codeforester, 2011-08-08

14 answers

Poniższe rozwiązanie odczytuje z pliku, jeśli skrypt jest wywołany z nazwą pliku jako pierwszym parametrem $1, w przeciwnym razie ze standardowego wejścia.

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

Substytucja ${1:-...} przyjmuje $1 jeśli zdefiniowano inaczej używana jest nazwa pliku standardowego wejścia własnego procesu.

 299
Author: Fritz G. Mehner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-19 20:37:09

Być może najprostszym rozwiązaniem jest przekierowanie stdin za pomocą łączącego się operatora przekierowania:

#!/bin/bash
less <&0

Stdin jest deskryptorem pliku zero. Powyższe polecenie wysyła sygnał wejściowy do skryptu bash na stdin less.

Przeczytaj więcej o przekierowaniu deskryptora pliku .

 77
Author: Ryan Ballantyne,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-10 20:48:52

Oto najprostszy sposób:

#!/bin/sh
cat -

Użycie:

$ echo test | sh my_script.sh
test

Aby przypisać stdin do zmiennej, możesz użyć: STDIN=$(cat -) lub po prostu {[5] } jako operator nie jest konieczny (zgodnie z @mklement0 komentarz).


Aby przetworzyć każdą linię ze standardowego wejścia , spróbuj użyć następującego skryptu:

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

Aby odczytać z pliku lub stdin (jeśli argument nie jest obecny), możesz rozszerzyć go 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 znaku ukośnika w żaden szczególny sposób. Rozważ każdy ukośnik wsteczny jako część linii wejściowej.

- BEZ Ustawienia IFS, domyślnie sekwencje spacji i tabulacji na początku i 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 tam jest obejściem za pomocą env POSIXLY_CORRECT=1 echo "$line", który wykonuje twój zewnętrzny GNU echo, który go obsługuje. Zobacz: jak echo "- e"?

Zobacz: jak odczytać stdin, gdy nie są przekazywane żadne argumenty? w stackoverflow SE

 47
Author: kenorb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:48

Rozwiązanie echo dodaje nowe linie za każdym razem, gdy IFS przerywa strumień wejściowy. odpowiedź@fgm można nieco zmodyfikować:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
 13
Author: David Souther,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:48

Myślę, że to jest prosta droga:

$ 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
 12
Author: Amir Mehler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-26 19:25:26

Pętla Perla w pytaniu odczytuje z wszystkie argumenty nazwy pliku z linii poleceń, lub ze standardowego wejścia, jeśli nie podano żadnych plików. Odpowiedzi, które widzę, wydają się przetwarzać pojedynczy plik lub standardowe wejście, jeśli nie podano PLIKU.

Chociaż często wyśmiewane dokładnie jako UUOC (bezużyteczne użycie cat), są chwile, kiedy cat jest najlepszym narzędziem do pracy, i można się domyślić, że jest to jedno z nich: {10]}

cat "$@" |
while read -r line
do
    echo "$line"
done

Jedynym minusem tego jest tworzy rurociąg działający w pod-powłoce, więc rzeczy takie jak przypisanie zmiennych w pętli while nie są dostępne poza rurociągiem. bash wokół tego jest substytucja procesu :

while read -r line
do
    echo "$line"
done < <(cat "$@")

To powoduje, że pętla while działa w głównej powłoce, więc zmienne ustawione w pętli są dostępne poza pętlą.

 6
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-27 16:18:34

Zachowanie Perla, z kodem podanym w OP może przyjmować żaden lub kilka argumentów, a jeśli argument jest pojedynczym myślnikiem - jest to rozumiane jako stdin. Co więcej, zawsze można mieć nazwę pliku z $ARGV. Żadna z udzielonych dotąd odpowiedzi nie naśladuje zachowania Perla pod tym względem. Tutaj jest możliwość czystej Bash. Sztuczka polega na odpowiednim użyciu exec.

#!/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 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 to przekierowanie się powiedzie, pętlę wykonujemy pętlą while. Używam standardowej zmiennej REPLY i w tym przypadku nie trzeba resetować IFS. Jeśli chcesz inną nazwę, musisz zresetować IFS w ten sposób (chyba, że oczywiście nie chcesz tego i wiesz, co robisz): {]}

while IFS= read -r line; do
    printf '%s\n' "$line"
done
 4
Author: gniourf_gniourf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-28 23:19:42

Dokładniej...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file
 2
Author: Sorpigal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-08 12:51:51

Proszę wypróbować następujący kod:

while IFS= read -r line; do
    echo "$line"
done < file
 2
Author: Webthusiast,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-28 23:42:24

Kod ${1:-/dev/stdin} zrozumie tylko pierwszy argument, więc co ty na to?

ARGS='$*'
if [ -z "$*" ]; then
  ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
   echo "$line"
done
 1
Author: Takahiro Onodera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-08 15:10:37

Poniższe działa ze standardem sh (testowanym z dash na Debianie) 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 jest niepusty, to cat ten plik, else cat standardowe wejście. Następnie wyjście całej instrukcji if jest przetwarzane przez commands_and_transformations.

 0
Author: Notinlist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-30 14:47:26

Nie uważam żadnej z tych odpowiedzi za akceptowalną. 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ń. Więc przyjęta odpowiedź nawet nie odpowiada na pytanie. Inne odpowiedzi używają rozszerzeń bash, dodają niepotrzebne polecenia "cat", działają tylko w przypadku prostego powtarzania wejścia na wyjście lub są po prostu niepotrzebnie skomplikowane.

Jednak muszę dać mają trochę uznania, bo dali mi kilka pomysłów. 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
 0
Author: Gungwald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-05 17:11:08

Połączyłem wszystkie powyższe odpowiedzi i stworzyłem funkcję powłoki, która odpowiadałaby moim potrzebom. To jest z terminala cygwin z moich maszyn 2 Windows10, gdzie miałem udostępniony folder między nimi. Muszę być w stanie poradzić sobie z następującymi:

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

Jeśli podano konkretną nazwę pliku, muszę użyć tej samej nazwy pliku podczas kopiowania. Gdzie wejściowy strumień danych został przeportowany przez, następnie muszę wygenerować tymczasową nazwę pliku o godzina minuty i sekundy. Udostępniony folder mainfolder zawiera podfoldery dni tygodnia. To dla celów organizacyjnych.

Oto ostateczny scenariusz 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, który możesz zobaczyć, aby jeszcze bardziej zoptymalizować to, chciałbym wiedzieć.

 0
Author: ifelsemonkey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-28 18:17:38

A może

for line in `cat`; do
    something($line);
done
 -1
Author: Charles Cooper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-05 21:03:39