Catturare output su più righe in una variabile Bash


583

Ho uno script "myscript" che genera quanto segue:

abc
def
ghi

in un altro script, chiamo:

declare RESULT=$(./myscript)

e $RESULTottiene il valore

abc def ghi

C'è un modo per memorizzare il risultato o con le nuove righe o con il carattere '\ n' in modo che io possa emetterlo con ' echo -e'?


1
mi sorprende. non hai $ (cat ./myscipt)? altrimenti mi sarei aspettato che tentasse di eseguire comandi abc, def e ghi
Johannes Schaub - litb

@litb: sì, suppongo di si; puoi anche usare $ (<./ myscript) che evita di eseguire un comando.
Jonathan Leffler

2
(NB: i due commenti sopra si riferiscono a una revisione della domanda che ho iniziato Ho uno script "myscript" che contiene quanto segue , che ha portato alle domande. L'attuale revisione della domanda ( ho uno script " myscript "che riporta quanto segue ) rende superflui i commenti. Tuttavia, la revisione è dell'11 / 2011/2011, molto tempo dopo che i due commenti sono stati fatti.
Jonathan Leffler,

Risposte:


1083

In realtà, RISULTATO contiene ciò che vuoi - per dimostrare:

echo "$RESULT"

Quello che mostri è quello che ottieni:

echo $RESULT

Come notato nei commenti, la differenza è che (1) la versione tra virgolette doppie della variabile ( echo "$RESULT") conserva la spaziatura interna del valore esattamente come è rappresentato nella variabile - newline, tab, multiple spazi vuoti e tutto - mentre (2 ) la versione non quotata ( echo $RESULT) sostituisce ogni sequenza di uno o più spazi vuoti, tab e newline con un singolo spazio. Pertanto (1) conserva la forma della variabile di input, mentre (2) crea una singola riga di output potenzialmente molto lunga con 'parole' separate da spazi singoli (dove una 'parola' è una sequenza di caratteri non bianchi; non è necessario essere alfanumerici in una qualsiasi delle parole).


69
@troelskn: la differenza è che (1) la versione a doppia virgoletta della variabile conserva la spaziatura interna del valore esattamente come è rappresentato nella variabile, nuove righe, tabulazioni, più spazi vuoti e tutti, mentre (2) la versione non quotata sostituisce ogni sequenza di uno o più spazi vuoti, schede e newline con un singolo spazio. Pertanto (1) conserva la forma della variabile di input, mentre (2) crea una singola riga di output potenzialmente molto lunga con 'parole' separate da spazi singoli (dove una 'parola' è una sequenza di caratteri non bianchi; non è necessario essere alfanumerici in una qualsiasi delle parole).
Jonathan Leffler,

23
Per rendere la risposta più facile da capire: la risposta dice che l'eco "$ RESULT" preserva la nuova riga, mentre l'eco $ RESULT no.
Yu Shen,

Ciò non riesce a preservare le nuove linee e gli spazi iniziali in alcune situazioni.
CommaToast,

3
@CommaToast: hai intenzione di approfondire questo? Le nuove righe finali vengono perse; non c'è un modo semplice per aggirare questo. Spazi vuoti iniziali - Non sono a conoscenza di circostanze in cui siano persi.
Jonathan Leffler,


90

Un altro inconveniente è la sostituzione del comando - $()- striscia le nuove righe. Probabilmente non è sempre importante, ma se vuoi davvero preservare esattamente ciò che è stato prodotto, dovrai usare un'altra riga e alcune citazioni:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Ciò è particolarmente importante se si desidera gestire tutti i possibili nomi di file (per evitare comportamenti indefiniti come operare sul file sbagliato).


4
Ho dovuto lavorare per un po 'con un guscio rotto che non rimuovere l'ultimo newline dalla sostituzione di comandonon sostituzione di processo ), e si è rotto quasi tutto. Ad esempio, se lo hai fatto pwd=`pwd`; ls $pwd/$file, hai ottenuto una nuova riga prima di /, e racchiudere il nome tra virgolette doppie non ha aiutato. È stato risolto rapidamente. Questo avveniva nel periodo 1983-5 su ICL Perq PNX; la shell non aveva $PWDcome variabile incorporata.
Jonathan Leffler,

19

Nel caso in cui tu sia interessato a linee specifiche, usa un array di risultati:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"

3
Se ci sono spazi nelle linee, questo conterà i campi (contenuto tra gli spazi) anziché le linee.
Liam,

2
Si potrebbe utilizzare readarraye processo di sostituzione, invece di sostituzione di comando: readarray -t RESULT < <(./myscript>.
Chepner,

15

Oltre alla risposta data da @ l0b0, ho appena avuto la situazione in cui avevo bisogno sia di mantenere l'output di nuove righe finali dello script sia di controllare il codice di ritorno dello script. E il problema con la risposta di l0b0 è che 'echo x' stava resettando $? tornando a zero ... quindi sono riuscito a trovare questa soluzione molto astuta:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"

Questo è adorabile, in realtà ho avuto il caso d'uso in cui voglio avere una die ()funzione che accetta un codice di uscita arbitrario e facoltativamente un messaggio, e volevo usare un'altra usage ()funzione per fornire il messaggio, ma le nuove righe continuavano a essere schiacciate, spero che questo mi permetta aggirare quello.
dragon788,

1

Dopo aver provato la maggior parte delle soluzioni qui, la cosa più semplice che ho trovato è stata ovvia: usare un file temporaneo. Non sono sicuro di cosa tu voglia fare con il tuo output su più righe, ma puoi quindi gestirlo riga per riga usando read. L'unica cosa che non puoi davvero fare è attaccare facilmente tutto nella stessa variabile, ma per scopi pratici questo è molto più facile da affrontare.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Trucco rapido per farlo eseguire l'azione richiesta:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Nota che questo aggiunge una riga aggiuntiva. Se ci lavori puoi codificarlo, sono semplicemente troppo pigro.


EDIT: Mentre questo caso funziona perfettamente bene, le persone che leggono questo dovrebbero essere consapevoli che puoi facilmente schiacciare il tuo stdin all'interno del ciclo while, dandoti così uno script che eseguirà una riga, cancella lo stdin ed esce. Come ssh farà che penso? L'ho visto di recente, altri esempi di codice qui: /unix/24260/reading-lines-from-a-file-with-bash-for-vs-while

Un'altra volta! Questa volta con un filehandle diverso (stdin, stdout, stderr sono 0-2, quindi possiamo usare & 3 o superiore in bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

puoi anche usare mktemp, ma questo è solo un rapido esempio di codice. L'utilizzo di mktemp è simile a:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Quindi usa $ filenamevar come faresti con il nome reale di un file. Probabilmente non ha bisogno di essere spiegato qui, ma qualcuno si è lamentato nei commenti.


Ho provato anche altre soluzioni, con il tuo primo suggerimento ho finalmente fatto funzionare la mia sceneggiatura
Kar.ma,

1
Downvote: questo è eccessivamente complesso e non riesce a evitare molteplici bashinsidie comuni .
Tripleee

Qualcuno mi ha parlato dello strano problema del filehandle stdin l'altro giorno ed ero tipo "wow". Fammi aggiungere qualcosa molto velocemente.
user1279741

In Bash, è possibile utilizzare l' readestensione del comando -u 3per leggere dal descrittore di file 3.
Jonathan Leffler

Perché non mentre ... do ... done <<(.
Myscript.sh

0

Che ne dici di questo, leggerà ogni riga di una variabile e che può essere utilizzata in seguito! dire che l'output di myscript viene reindirizzato a un file chiamato myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'

4
Bene, questo non è bash, è strano.
vadipp
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.