Leggi i valori in una variabile di shell da una pipe

Sto cercando di ottenere bash per elaborare i dati da stdin che viene convogliato, ma senza fortuna. Quello che voglio dire è nessuno dei seguenti lavori:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test test= echo "hello world" | read test; echo test=$test test= echo "hello world" | test=`cat`; echo test=$test test= 

dove voglio che l’output sia test=hello world . Ho provato a mettere “” le virgolette attorno a "$test" che non funziona neanche.

Uso

 IFS= read var << EOF $(foo) EOF 

Puoi ingannare la read ad accettare da una pipe come questa:

 echo "hello world" | { read test; echo test=$test; } 

o anche scrivere una funzione come questa:

 read_from_pipe() { read "[email protected]" <&0; } 

Ma non ha senso - i tuoi compiti variabili potrebbero non durare! Una pipeline può generare una subshell, in cui l'ambiente viene ereditato per valore, non per riferimento. Questo è il motivo per cui read non si preoccupa dell'input di una pipe: non è definito.

Cordiali saluti, http://www.etalabs.net/sh_tricks.html è una raccolta di nifty del cruft necessario per combattere le stranezze e le incompatibilità di bourne shells, sh.

se vuoi leggere un sacco di dati e lavorare su ogni riga separatamente, puoi usare qualcosa del genere:

 cat myFile | while read x ; do echo $x ; done 

se vuoi dividere le linee in più parole puoi usare più variabili al posto di x in questo modo:

 cat myFile | while read xy ; do echo $y $x ; done 

in alternativa:

 while read xy ; do echo $y $x ; done < myFile 

Ma non appena inizi a voler fare qualcosa di veramente intelligente con questo genere di cose, è meglio che tu stia facendo un po 'di linguaggio di scripting come perl dove potresti provare qualcosa del genere:

 perl -ane 'print "$F[0]\n"' < myFile 

C'è una curva di apprendimento abbastanza ripida con perl (o credo che qualcuno di questi linguaggi), ma a lungo andare lo troverai molto più semplice se vuoi fare qualsiasi cosa tranne il più semplice degli script. Consiglio il Perl Cookbook e, naturalmente, The Perl Programming Language di Larry Wall e altri.

Questa è un’altra opzione

 $ read test < <(echo hello world) $ echo $test hello world 

read non leggerà da una pipe (o forse il risultato è perso perché la pipe crea una subshell). Tuttavia, puoi utilizzare una stringa qui in Bash:

 $ read abc <<< $(echo 1 2 3) $ echo $a $b $c 1 2 3 

Non sono un esperto di Bash, ma mi chiedo perché questo non sia stato proposto:

 stdin=$(cat) echo "$stdin" 

One-liner prova che funziona per me:

 $ fortune | eval 'stdin=$(cat); echo "$stdin"' 

bash 4.2 introduce l’opzione lastpipe , che consente al codice di funzionare come scritto, eseguendo l’ultimo comando in una pipeline nella shell corrente, anziché una subshell.

 shopt -s lastpipe echo "hello world" | read test; echo test=$test 

La syntax per una pipe implicita da un comando shell in una variabile bash è

 var=$(command) 

o

 var=`command` 

Nei tuoi esempi, stai convogliando i dati a una dichiarazione di assegnazione, che non si aspetta alcun input.

Ai miei occhi il modo migliore per leggere da stdin in bash è il seguente, che ti permette anche di lavorare sulle linee prima che l’input finisca:

 while read LINE; do echo $LINE done < /dev/stdin 

Il primo tentativo è stato abbastanza vicino. Questa variazione dovrebbe funzionare:

 echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; }; 

e l'output è:

test = ciao mondo

Hai bisogno di parentesi dopo il tubo per racchiudere il compito da testare e l'eco.

Senza le parentesi graffe, il compito di testare (dopo il pipe) è in una shell, e l'eco "test = $ test" si trova in una shell separata che non conosce tale assegnazione. Ecco perché stavi ricevendo "test =" nell'output anziché "test = hello world".

Piping qualcosa in un’espressione che coinvolge un incarico non si comporta in questo modo.

Invece, prova:

 test=$(echo "hello world"); echo test=$test 

Il seguente codice:

 echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test ) 

funzionerà anche, ma aprirà un'altra nuova sotto-shell dopo la pipe, dove

 echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; } 

non lo faranno.


Ho dovuto disabilitare il controllo dei lavori per utilizzare il metodo di chepnars (stavo eseguendo questo comando dal terminale):

 set +m;shopt -s lastpipe echo "hello world" | read test; echo test=$test echo "hello world" | test="$( 

Il manuale di Bash dice :

lastpipe

Se impostato, e il controllo del lavoro non è attivo , la shell esegue l'ultimo comando di una pipeline non eseguita in background nell'ambiente di shell corrente.

Nota: il controllo del lavoro è distriggersto per impostazione predefinita in una shell non intertriggers e quindi non è necessario il set +m all'interno di uno script.

Perché mi innamoro, vorrei lasciare un appunto. Ho trovato questo thread, perché devo riscrivere un vecchio script sh per essere compatibile POSIX. Ciò significa fondamentalmente aggirare il problema pipe / subshell introdotto da POSIX riscrivendo il codice in questo modo:

 some_command | read abc 

in:

 read abc << EOF $(some_command) EOF 

E codice come questo:

 some_command | while read abc; do # something done 

in:

 while read abc; do # something done << EOF $(some_command) EOF 

Ma quest'ultimo non si comporta allo stesso modo su input vuoti. Con la vecchia notazione il ciclo while non viene inserito sull'input vuoto, ma in notazione POSIX lo è! Penso che sia dovuto alla nuova riga prima di EOF, che non può essere omesso. Il codice POSIX che si comporta più come la vecchia notazione assomiglia a questo:

 while read abc; do case $a in ("") break; esac # something done << EOF $(some_command) EOF 

Nella maggior parte dei casi questo dovrebbe essere abbastanza buono. Ma sfortunatamente questo comportamento si comporta ancora non esattamente come la vecchia notazione se some_command stampa una riga vuota. Nella vecchia notazione il corpo del tempo viene eseguito e nella notazione POSIX si rompe davanti al corpo.

Un approccio per risolvere questo potrebbe assomigliare a questo:

 while read abc; do case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac # something done << EOF $(some_command) echo "something_guaranteed_not_to_be_printed_by_some_command" EOF 

Penso che stavi cercando di scrivere uno script di shell che potesse prendere input da stdin. ma mentre stai provando a farlo in linea, ti sei perso cercando di creare quella variabile = variabile. Penso che non abbia molto senso farlo in linea, ed è per questo che non funziona come ti aspetti.

Stavo cercando di ridurre

 $( ... | head -n $X | tail -n 1 ) 

per ottenere una linea specifica da vari input. quindi potrei digitare …

 cat program_file.c | line 34 

quindi ho bisogno di un piccolo programma shell in grado di leggere da stdin. come fai tu.

 22:14 ~ $ cat ~/bin/line #!/bin/sh if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi cat | head -n $1 | tail -n 1 22:16 ~ $ 

eccoti.

Cosa ne pensi di questo:

 echo "hello world" | echo test=$(cat)