Força a liberação de saída para um arquivo enquanto o script bash ainda está em execução

90

Eu tenho um pequeno script, que é chamado diariamente pelo crontab usando o seguinte comando:

/homedir/MyScript &> some_log.log

O problema com esse método é que some_log.log só é criado depois que o MyScript termina. Eu gostaria de liberar a saída do programa no arquivo enquanto ele está sendo executado para que eu pudesse fazer coisas como

tail -f some_log.log

e acompanhar o progresso, etc.

olamundo
fonte
Precisamos de uma descrição - ou, se possível, um código - do que seu pequeno script faz exatamente ...
ChristopheD
7
Para descompactar scripts python, você pode usar "python -u". Para descompactar scripts perl, veja a resposta de Greg Hewgill abaixo. E assim por diante ...
Eloici
Se você pode editar o script, normalmente pode liberar o buffer de saída explicitamente dentro de um script, por exemplo em python com sys.stdout.flush().
drevicko

Respostas:

28

O próprio bash nunca gravará nenhuma saída em seu arquivo de log. Em vez disso, os comandos que ele invoca como parte do script irão, cada um individualmente, gravar a saída e liberar sempre que desejar. Portanto, a sua pergunta é realmente como forçar o esvaziamento dos comandos dentro do script bash, e isso depende do que eles são.

Chris Dodd
fonte
25
Eu realmente não entendo essa resposta.
Alfonso Santiago de
3
Para ter uma ideia melhor de por que a saída padrão se comporta assim, consulte stackoverflow.com/a/13933741/282728 . Uma versão curta - por padrão, se redirecionado para um arquivo, o stdout é totalmente armazenado em buffer; ele é gravado em um arquivo somente após uma descarga. Stderr não é - é escrito após cada '\ n'. Uma solução é usar o comando 'script' recomendado pelo usuário3258569 abaixo, para que o stdout seja liberado após cada final de linha.
Alex
2
Afirmando o óbvio, e dez anos depois, mas este é um comentário, não uma resposta, e não deveria ser a resposta aceita.
RealHandy
87

Eu encontrei uma solução para isso aqui . Usando o exemplo do OP, você basicamente executa

stdbuf -oL /homedir/MyScript &> some_log.log

e o buffer é liberado após cada linha de saída. Costumo combinar isso com nohupa execução de trabalhos longos em uma máquina remota.

stdbuf -oL nohup /homedir/MyScript &> some_log.log

Desta forma, o seu processo não é cancelado quando você sai.

Martin Wiebusch
fonte
1
Você poderia adicionar um link para alguma documentação para stdbuf? Com base neste comentário , parece que não está disponível em algumas distros. Você poderia esclarecer?
Ação judicial de Monica de
1
stdbuf -o ajusta o buffer stdout. As outras opções são -i e -e para stdin e stderr. L define o buffer de linha. Também é possível especificar o tamanho do buffer ou 0 para nenhum buffer.
Seppo Enarvi
5
esse link não está mais disponível.
john-jones
2
@NicHartley: stdbufé parte do GNU coreutils, a documentação pode ser encontrada em gnu.org
Thor
Caso ajude alguém, use export -f my_function e stdbuf -oL bash -c "my_function -args"se precisar executar uma função em vez de um script
Anônimo
29
script -c <PROGRAM> -f OUTPUT.txt

A chave é -f. Citação do man script:

-f, --flush
     Flush output after each write.  This is nice for telecooperation: one person
     does 'mkfifo foo; script -f foo', and another can supervise real-time what is
     being done using 'cat foo'.

Executado em segundo plano:

nohup script -c <PROGRAM> -f OUTPUT.txt
user3258569
fonte
Uau! Uma solução que funciona busybox! (minha concha congela depois, mas tanto faz)
Victor Sergienko
9

Você pode usar teepara gravar no arquivo sem a necessidade de descarregar.

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null
crenate
fonte
2
Isso ainda armazena a saída, pelo menos no meu ambiente Ubuntu 18.04. O conteúdo eventualmente é gravado no arquivo de qualquer maneira, mas acho que o OP está pedindo um método onde eles possam monitorar o progresso com mais precisão antes de o arquivo terminar de ser escrito, e este método não permite isso mais do que o redirecionamento de saída faz.
mltsy
3

Isso não é uma função de bash, pois tudo o que o shell faz é abrir o arquivo em questão e, em seguida, passar o descritor de arquivo como a saída padrão do script. O que você precisa fazer é garantir que a saída seja eliminada do seu script com mais frequência do que você atualmente.

Em Perl, por exemplo, isso pode ser feito definindo:

$| = 1;

Veja perlvar para mais informações sobre isso.

Greg Hewgill
fonte
2

O armazenamento em buffer da saída depende de como seu programa /homedir/MyScripté implementado. Se você descobrir que a saída está sendo armazenada em buffer, será necessário forçá-la em sua implementação. Por exemplo, use sys.stdout.flush () se for um programa Python ou use fflush (stdout) se for um programa C.

Midas
fonte
2

Isso ajudaria?

tail -f access.log | stdbuf -oL cut -d ' ' -f1 | uniq 

Isso exibirá imediatamente entradas exclusivas do access.log usando o utilitário stdbuf .

Ondra Žižka
fonte
O único problema é que stdbuf parece ser um utilitário antigo que não está disponível em novas distros.
Ondra Žižka
..nor in my busybox :(
Campa
Na verdade, tenho stdbufdisponível no Ubuntu no momento, não tenho certeza de onde consegui.
Ondra Žižka
Tenho stdbuf no Centos 7.5
Max
1
No Ubuntu 18.04, stdbuffaz parte de coreutilus(encontrado com apt-file search /usr/bin/stdbuf).
Rmano,
1

O problema aqui é que você tem que esperar que os programas que você executa a partir do seu script terminem seus trabalhos.
Se no seu script você executa o programa em segundo plano, você pode tentar algo mais.

Em geral, uma chamada para syncantes de sair permite liberar buffers do sistema de arquivos e pode ajudar um pouco.

Se no script você iniciar alguns programas em segundo plano ( &), você pode esperar que eles terminem antes de sair do script. Para se ter uma ideia de como ele pode funcionar você pode ver abaixo

#!/bin/bash
#... some stuffs ...
program_1 &          # here you start a program 1 in background
PID_PROGRAM_1=${!}   # here you remember its PID
#... some other stuffs ... 
program_2 &          # here you start a program 2 in background
wait ${!}            # You wait it finish not really useful here
#... some other stuffs ... 
daemon_1 &           # We will not wait it will finish
program_3 &          # here you start a program 1 in background
PID_PROGRAM_3=${!}   # here you remember its PID
#... last other stuffs ... 
sync
wait $PID_PROGRAM_1
wait $PID_PROGRAM_3  # program 2 is just ended
# ...

Uma vez que waitfunciona com tarefas e também com PIDnúmeros, uma solução preguiçosa deve ser colocada no final do script

for job in `jobs -p`
do
   wait $job 
done

Mais difícil é a situação se você executar algo que executa outra coisa em segundo plano porque você tem que pesquisar e esperar (se for o caso) o fim de todo o processo filho : por exemplo, se você executar um daemon provavelmente não é o caso esperar termine :-).

Nota:

  • wait $ {!} significa "esperar até que o último processo em segundo plano seja concluído" onde $!está o PID do último processo em segundo plano. Então, colocar wait ${!}logo depois program_2 &é equivalente a executar diretamente program_2sem enviá-lo em segundo plano com&

  • Com a ajuda de wait:

    Syntax    
        wait [n ...]
    Key  
        n A process ID or a job specification
    
Hastur
fonte
1

Obrigado @user3258569, o script é talvez a única coisa que funciona busybox!

A casca estava congelando para mim depois disso, no entanto. Procurando a causa, encontrei estes grandes avisos vermelhos "não use em shells não interativos" na página de manual do script :

scripté projetado principalmente para sessões de terminal interativas. Quando stdin não é um terminal (por exemplo echo foo | script:), a sessão pode travar, porque o shell interativo dentro da sessão de script perde EOF e scriptnão tem ideia de quando fechar a sessão. Consulte a seção NOTAS para obter mais informações.

Verdadeiro. script -c "make_hay" -f /dev/null | grep "needle"estava congelando a casca para mim.

Camponês ao aviso, pensei que echo "make_hay" | scriptIRÁ passar um EOF, por isso tentei

echo "make_hay; exit" | script -f /dev/null | grep 'needle'

e funcionou!

Observe os avisos na página do manual. Isso pode não funcionar para você.

Victor Sergienko
fonte
0

alternativa para stdbuf é awk '{print} END {fflush()}' eu gostaria que houvesse um bash embutido para fazer isso. Normalmente não deveria ser necessário, mas com versões mais antigas pode haver erros de sincronização do bash nos descritores de arquivo.

Brian Chrisman
fonte
-2

Não sei se daria certo, mas que tal ligar sync?

forkandwait
fonte
1
syncé uma operação de sistema de arquivos de baixo nível e não está relacionada à saída em buffer no nível do aplicativo.
Greg Hewgill
2
syncgrava quaisquer buffers de sistema de arquivos sujos no armazenamento físico, se necessário. Isso é interno ao sistema operacional; os aplicativos executados no sistema operacional sempre têm uma visão coerente do sistema de arquivos, independentemente de os blocos de disco terem sido gravados ou não no armazenamento físico. Para a pergunta original, o aplicativo (script) provavelmente está armazenando em buffer a saída em um buffer interno ao aplicativo, e o SO nem saberá (ainda) que a saída está realmente destinada a ser gravada em stdout. Portanto, uma operação hipotética do tipo "sincronização" não seria capaz de "acessar" o script e extrair os dados.
Greg Hewgill
-2

Tive esse problema com um processo em segundo plano no Mac OS X usando o StartupItems. É assim que eu resolvo:

Se eu fizer sudo ps aux, posso ver que mytoolfoi lançado.

Descobri que (devido ao buffer), quando o Mac OS X é encerrado, mytoolnunca transfere a saída para o sedcomando. No entanto, se eu executar sudo killall mytool, mytooltransfere a saída para o sedcomando. Portanto, adicionei um stopcaso ao StartupItemsque é executado quando o Mac OS X é encerrado:

start)
    if [ -x /sw/sbin/mytool ]; then
      # run the daemon
      ConsoleMessage "Starting mytool"
      (mytool | sed .... >> myfile.txt) & 
    fi
    ;;
stop)
    ConsoleMessage "Killing mytool"
    killall mytool
    ;;
Freeman
fonte
Essa realmente não é uma boa resposta, Freeman, já que é muito específica para o seu ambiente. O OP deseja monitorar a saída, não eliminá-la.
Gray
-3

bem, goste ou não, é assim que o redirecionamento funciona.

No seu caso, a saída (significando que o seu script foi concluído) do seu script redirecionada para esse arquivo.

O que você deseja fazer é adicionar esses redirecionamentos em seu script.


fonte