Risposte:
La seguente soluzione legge da un file se lo script viene chiamato con un nome file come primo parametro, $1
altrimenti dall'input standard.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
La sostituzione ${1:-...}
prende $1
se definito, altrimenti viene utilizzato il nome file dell'input standard del proprio processo.
/proc/$$/fd/0
e /dev/stdin
? Ho notato che quest'ultimo sembra essere più comune e sembra più semplice.
-r
al tuo read
comando, in modo che non mangi accidentalmente i \
caratteri; utilizzare while IFS= read -r line
per conservare spazi bianchi iniziali e finali.
/bin/sh
- stai usando una shell diversa da bash
o sh
?
Forse la soluzione più semplice è reindirizzare stdin con un operatore di reindirizzamento unito:
#!/bin/bash
less <&0
Stdin è il descrittore di file zero. Quanto sopra invia l'input reindirizzato al tuo script bash nello stdin di less.
Ulteriori informazioni sul reindirizzamento del descrittore di file .
<&0
in questa situazione - il tuo esempio funzionerà allo stesso modo con o senza di esso - apparentemente, gli strumenti che invochi all'interno di uno script bash di default vedono lo stesso stdin dello script stesso (a meno che lo script non lo consumi per primo).
Ecco il modo più semplice:
#!/bin/sh
cat -
Uso:
$ echo test | sh my_script.sh
test
Per assegnare stdin alla variabile, puoi usare: STDIN=$(cat -)
o semplicemente STDIN=$(cat)
come operatore non è necessario (come da commento @ mklement0 ).
Per analizzare ciascuna riga dall'input standard , provare il seguente script:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
Per leggere dal file o stdin (se l'argomento non è presente), è possibile estenderlo a:
#!/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")
Appunti:
-
read -r
- Non trattare un carattere barra rovesciata in alcun modo speciale. Considerare ogni barra rovesciata come parte della riga di input.- Senza impostazione
IFS
, per impostazione predefinita le sequenze di Spacee Taball'inizio e alla fine delle righe vengono ignorate (tagliate).- Uso
printf
invece diecho
evitare di stampare righe vuote quando la linea è costituita da un singolo-e
,-n
o-E
. Tuttavia, esiste una soluzione alternativaenv POSIXLY_CORRECT=1 echo "$line"
che utilizza l' esecuzione della GNU esternaecho
che la supporta. Vedi: Come echo "-e"?
Vedi: Come leggere stdin quando non vengono passati argomenti? su stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
al FILE=${1:--}
. (Quibble: meglio evitare variabili shell maiuscole per evitare collisioni di nomi con variabili d' ambiente .)
${1:--}
è conforme a POSIX, quindi dovrebbe funzionare in tutte le shell simili a POSIX. Ciò che non funzionerà in tutte queste shell è la sostituzione dei processi ( <(...)
); funzionerà in bash, ksh, zsh, ma non in trattino, per esempio. Inoltre, è meglio aggiungere -r
al tuo read
comando, in modo che non mangi accidentalmente i \
caratteri; anteponi IFS=
per preservare gli spazi bianchi iniziali e finali.
echo
: se una riga è composta da -e
, -n
o -E
, non verrà mostrata. Per risolvere questo problema, è necessario utilizzare printf
: printf '%s\n' "$line"
. Non l'ho incluso nella mia precedente modifica ... troppo spesso le mie modifiche vengono ripristinate quando correggo questo errore :(
.
--
è inutile se il primo argomento è'%s\n'
IFS=
con read
e printf
invece di echo
. :)
.
Penso che questo sia il modo semplice:
$ 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
legge da stdin di default , quindi non c'è alcun bisogno per < /dev/stdin
.
La echo
soluzione aggiunge nuove righe ogni volta che IFS
interrompe il flusso di input. La risposta di @ fgm può essere modificata un po ':
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
's comportamento: mentre read
non potenzialmente suddiviso in più tokens sulla base dei caratteri. contenuto in $IFS
, restituisce un singolo token solo se si specifica un solo nome di variabile (ma per impostazione predefinita vengono tagliati e spazi bianchi iniziali e finali).
read
e $IFS
- a sua echo
volta aggiunge nuove righe senza la -n
bandiera. "L'utilità echo scrive tutti gli operandi specificati, separati da singoli caratteri vuoti (` ') e seguiti da un carattere di nuova riga (`\ n'), nell'output standard."
\n
aggiunto da echo
: Perl's $_
include la fine \n
della riga dalla riga letta, mentre bash's read
no. (Tuttavia, come sottolinea @gniourf_gniourf altrove, l'approccio più solido è quello di utilizzare printf '%s\n'
al posto di echo
).
Il ciclo Perl nella domanda legge da tutti gli argomenti del nome file sulla riga di comando o dall'input standard se non viene specificato alcun file. Le risposte che vedo sembrano elaborare un singolo file o input standard se non è stato specificato alcun file.
Anche se spesso deriso accuratamente come UUOC (Uso inutile di cat
), ci sono momenti in cui cat
è lo strumento migliore per il lavoro ed è discutibile che questo sia uno di questi:
cat "$@" |
while read -r line
do
echo "$line"
done
L'unico aspetto negativo di questo è che crea una pipeline in esecuzione in una sotto-shell, quindi cose come le assegnazioni di variabili nel while
ciclo non sono accessibili al di fuori della pipeline. Il bash
modo per aggirare questo è Sostituzione processo :
while read -r line
do
echo "$line"
done < <(cat "$@")
Questo lascia il while
loop in esecuzione nella shell principale, quindi le variabili impostate nel loop sono accessibili al di fuori del loop.
>>EOF\n$(cat "$@")\nEOF
. Infine, un cavillo: while IFS= read -r line
è una migliore approssimazione di ciò che while (<>)
fa in Perl (conserva gli spazi bianchi iniziali e finali, sebbene Perl mantenga anche il finale \n
).
Il comportamento di Perl, con il codice indicato nell'OP, può accettare nessuno o più argomenti e se un argomento è un singolo trattino, -
questo viene inteso come stdin. Inoltre, è sempre possibile avere il nome file con $ARGV
. Nessuna delle risposte fornite finora imita davvero il comportamento di Perl sotto questi aspetti. Ecco una pura possibilità di Bash. Il trucco è usare in modo exec
appropriato.
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
Il nome file è disponibile in $1
.
Se non viene fornito alcun argomento, impostiamo artificialmente -
il primo parametro posizionale. Quindi eseguiamo il ciclo sui parametri. Se un parametro non lo è -
, reindirizziamo l'input standard dal nome file con exec
. Se questo reindirizzamento ha esito positivo, eseguiamo il loop con un while
loop. Sto usando la REPLY
variabile standard e in questo caso non è necessario reimpostare IFS
. Se vuoi un altro nome, devi reimpostarlo in questo IFS
modo (a meno che, ovviamente, non lo desideri e sappia cosa stai facendo):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Più accuratamente...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
e -r
il read
comando assicurano che ogni riga venga letta non modificata (incluso lo spazio bianco iniziale e finale).
Si prega di provare il seguente codice:
while IFS= read -r line; do
echo "$line"
done < file
read
senza IFS=
e -r
, e il povero $line
senza le sue citazioni sane.
read -r
notazione. IMO, POSIX ha sbagliato; l'opzione dovrebbe abilitare il significato speciale per i backslash finali, non disabilitarlo - in modo che gli script esistenti (prima dell'esistenza di POSIX) non si interrompessero perché -r
omessi. Osservo, tuttavia, che faceva parte di IEEE 1003.2 1992, che era la prima versione della shell POSIX e degli standard di utility, ma è stata contrassegnata come aggiunta anche allora, quindi si tratta di cianfrusaglie per opportunità ormai lontane. Non ho mai avuto problemi perché il mio codice non usa -r
; Devo essere fortunato Ignorami su questo.
-r
dovrebbe essere standard. Sono d'accordo che è improbabile che si verifichi nei casi in cui il suo mancato utilizzo comporti problemi. Tuttavia, il codice non funzionante è un codice non funzionante. La mia modifica è stata innescata per la prima volta da quella povera $line
variabile che ha perso le sue virgolette. Ho risolto read
mentre ero lì. Non ho risolto il problema echo
perché è il tipo di modifica che viene ripristinata. :(
.
Il codice ${1:-/dev/stdin}
capirà solo il primo argomento, quindi, che ne dici di questo.
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Non trovo accettabile nessuna di queste risposte. In particolare, la risposta accettata gestisce solo il primo parametro della riga di comando e ignora il resto. Il programma Perl che sta tentando di emulare gestisce tutti i parametri della riga di comando. Quindi la risposta accettata non risponde nemmeno alla domanda. Altre risposte usano estensioni bash, aggiungono comandi "cat" non necessari, funzionano solo per il semplice caso di eco dell'input all'output o sono semplicemente inutilmente complicate.
Tuttavia, devo dare loro un po 'di credito perché mi hanno dato alcune idee. Ecco la risposta completa:
#!/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
Ho combinato tutte le risposte sopra e ho creato una funzione shell adatta alle mie esigenze. Questo proviene da un terminale Cygwin delle mie 2 macchine Windows 10 in cui avevo una cartella condivisa tra di loro. Devo essere in grado di gestire quanto segue:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Laddove viene specificato un nome file specifico, è necessario utilizzare lo stesso nome file durante la copia. Laddove il flusso di dati di input è stato reindirizzato, è necessario generare un nome file temporaneo con l'ora, i minuti e i secondi. La cartella principale condivisa ha sottocartelle dei giorni della settimana. Questo è per scopi organizzativi.
Ecco, la sceneggiatura definitiva per le mie esigenze:
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
}
Se c'è un modo che puoi vedere per ottimizzare ulteriormente questo, vorrei sapere.
Il seguente funziona con lo standard sh
(testato con dash
su Debian) ed è abbastanza leggibile, ma è una questione di gusti:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Dettagli: se il primo parametro non è vuoto, allora cat
quel file, altrimenti cat
input standard. Quindi l'output dell'intera if
istruzione viene elaborato dal commands_and_transformations
.
cat "${1:--}" | any_command
. La lettura delle variabili shell e l'eco delle stesse possono funzionare per file di piccole dimensioni ma non si adattano così bene.
[ -n "$1" ]
può essere semplificato a [ "$1" ]
.
Che ne dite di
for line in `cat`; do
something($line);
done
cat
verrà inserito nella riga di comando. La riga di comando ha una dimensione massima. Inoltre, questo non leggerà riga per riga, ma parola per parola.