No script bash, como capturar stdout linha por linha

22

Em um script bash, eu gostaria de capturar a saída padrão de um longo comando linha por linha, para que eles possam ser analisados e reportados enquanto o comando inicial ainda está em execução. Essa é a maneira complicada que posso imaginar de fazer isso:

# 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

Gostaria de saber se existe uma maneira mais simples de fazer isso.

EDITAR:

Para esclarecer minha dúvida, adicionarei isso. O longcommand exibe seu status linha por linha, uma vez por segundo. Eu gostaria de pegar a saída antes que o longcommand seja concluído.

Dessa forma, posso potencialmente matar o longcommand se ele não fornecer os resultados esperados.

Eu tentei:

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

Mas whatever (por exemplo, echo ) só é executado após longcommand ser concluído.

    
por gfrigon 28.02.2014 / 17:15

2 respostas

27

Apenas insira o comando em um loop while . Existem várias nuances para isso, mas basicamente (em bash ou qualquer shell POSIX):

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

A outra pegadinha principal com isso (além das coisas IFS abaixo) é quando você tenta usar as variáveis dentro do loop depois de terminar. Isso ocorre porque o loop é realmente executado em um sub-shell (apenas outro processo do shell) do qual você não pode acessar variáveis (também termina quando o loop faz, em cujo ponto as variáveis estão completamente ausentes. Para contornar isso, você pode fazer:

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"
}

O exemplo de Hauke de definir lastpipe em bash é outra solução.

Atualizar

Para certificar-se de que você está processando a saída do comando 'como acontece', você pode usar stdbuf para definir o processo ' stdout como buffer de linha.

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

Isso configurará o processo para gravar uma linha por vez no canal, em vez de armazenar internamente sua saída em blocos. Tenha em atenção que o programa pode alterar esta configuração internamente. Um efeito semelhante pode ser obtido com unbuffer (parte de expect ) ou script .

stdbuf está disponível nos sistemas GNU e FreeBSD, ele afeta somente o buffer stdio e funciona apenas para aplicativos não-setuid, não-setgid que são dinamicamente vinculados (como ele usa um Truque LD_PRELOAD).

    
por 28.02.2014 / 17:25
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
    
por 28.02.2014 / 17:42