Nello script bash, come catturare stdout riga per riga


26

In uno script bash, vorrei catturare l'output standard di un lungo comando riga per riga, in modo che possano essere analizzati e riportati mentre il comando iniziale è ancora in esecuzione. Questo è il modo complicato che posso immaginare di farlo:

# Start long command in a separated process and redirect stdout to temp file
longcommand > /tmp/tmp$$.out &

#loop until process completes
ps cax | grep longcommand > /dev/null
while [ $? -eq 0 ]
do
    #capture the last lines in temp file and determine if there is new content to analyse
    tail /tmp/tmp$$.out

    # ...

    sleep 1 s  # sleep in order not to clog cpu

    ps cax | grep longcommand > /dev/null
done

Vorrei sapere se esiste un modo più semplice per farlo.

MODIFICARE:

Per chiarire la mia domanda, aggiungerò questo. Il longcommanddisplay sua linea di stato per linea volta al secondo. Vorrei catturare l'output prima del longcommandcompletamento.

In questo modo, posso potenzialmente uccidere longcommandse non fornisce i risultati che mi aspetto.

Ho provato:

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

Ma whatever(ad es. echo) Viene eseguito solo dopo il longcommandcompletamento.


Risposte:


32

Basta inserire il comando in un whileciclo. Ci sono una serie di sfumature a questo, ma sostanzialmente (nella basho in qualsiasi shell POSIX):

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

L'altro gotcha principale con questo (oltre alle IFScose di seguito) è quando si tenta di utilizzare le variabili all'interno del ciclo al termine. Questo perché il ciclo viene effettivamente eseguito in una sotto-shell (solo un altro processo di shell) da cui non è possibile accedere alle variabili (inoltre termina quando il ciclo lo fa, a quel punto le variabili sono completamente sparite. tu puoi fare:

longcommand | {
  while IFS= read -r line
  do
    whatever "$line"
    lastline="$line"
  done

  # This won't work without the braces.
  echo "The last line was: $lastline"
}

L'esempio di Hauke di impostazione lastpipein bashè un'altra soluzione.

Aggiornare

Per essere sicuro di elaborare l'output del comando 'come accade', è possibile utilizzare stdbufper impostare il processo ' stdoutin modo che sia bufferizzato in linea.

stdbuf -oL longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

Ciò configurerà il processo in modo che scriva una riga alla volta nel pipe invece di bufferizzare internamente il suo output in blocchi. Attenzione che il programma può modificare questa impostazione internamente. Un effetto simile può essere ottenuto con unbuffer(parte di expect) o script.

stdbufè disponibile sui sistemi GNU e FreeBSD, influenza solo il stdiobuffering e funziona solo per applicazioni non setuid, non setgid che sono collegate dinamicamente (poiché usa un trucco LD_PRELOAD).


@Stephane Non IFS=è necessario bash, l'ho verificato dopo l'ultima volta.
Graeme,

2
sì. Non è necessario se si omette line(nel qual caso il risultato viene inserito $REPLYsenza gli spazi iniziali e finali tagliati). Prova: echo ' x ' | bash -c 'read line; echo "[$line]"'e confronta con echo ' x ' | bash -c 'IFS= read line; echo "[$line]"'oecho ' x ' | bash -c 'read; echo "[$REPLY]"'
Stéphane Chazelas,

@Stephane, ok, non ho mai capito che ci fosse una differenza tra quella e una variabile nominata. Grazie.
Graeme,

@Graeme Potrei non essere stato chiaro nella mia domanda, ma vorrei elaborare l'output riga per riga prima che il comando lungo venga completato (al fine di reagire rapidamente se i comandi lunghi visualizzano un messaggio di errore).
Modificherò la

@gfrigon, aggiornato.
Graeme,

2
#! /bin/bash
set +m # disable job control in order to allow lastpipe
shopt -s lastpipe
longcommand |
  while IFS= read -r line; do lines[i]="$line"; ((i++)); done
echo "${lines[1]}" # second line
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.