Como fazer o `xargs` ignorar a saída da criança e continuar o processamento

24

Às vezes, xargstrabalho longo da noite para o dia e é realmente irritante descobrir pela manhã que xargsmorreu em algum lugar no meio, por exemplo, devido a uma falha de segmentação em um único caso especial, como aconteceu esta noite.

Se um xargsfilho for morto, ele não processa mais nenhuma entrada:

Console 1:

[09:35:48] % seq 40 | xargs -i --max-procs=4 bash -c 'sleep 10; date +"%H:%M:%S {}";'
xargs: bash: terminated by signal 15
09:35:58 3
09:35:58 4
09:35:58 2
<Exit with code 125>

Console 2:

[09:35:54] kill 5601

De alguma forma, posso impedir que xargspare para processar mais entradas depois que um processo filho morre e, em vez disso, continue processando?

Christoph Wurm
fonte
Estou usando a xargsversão 4.4.2 debian wheezye parece que tudo corre bem, mesmo se eu matar um sleepprocesso específico . Qual versão xargsvocê está usando? pode ser que eles tenham corrigido o problema na versão mais recente.
precisa
Um pouco tarde para a festa, mas como sobre xargs ... bash -c '...;exit 0'ou mesmoxargs ... bash -c '... || echo erk'
Samveen
Observe que parallel -j 1é uma possível solução de invasão.
barrycarter

Respostas:

25

Não, você não pode. Nas xargsfontes em savannah.gnu.org :

if (WEXITSTATUS (status) == CHILD_EXIT_PLEASE_STOP_IMMEDIATELY)
  error (XARGS_EXIT_CLIENT_EXIT_255, 0,
         _("%s: exited with status 255; aborting"), bc_state.cmd_argv[0]);
if (WIFSTOPPED (status))
  error (XARGS_EXIT_CLIENT_FATAL_SIG, 0,
         _("%s: stopped by signal %d"), bc_state.cmd_argv[0], WSTOPSIG (status));
if (WIFSIGNALED (status))
  error (XARGS_EXIT_CLIENT_FATAL_SIG, 0,
         _("%s: terminated by signal %d"), bc_state.cmd_argv[0], WTERMSIG (status));
if (WEXITSTATUS (status) != 0)
  child_error = XARGS_EXIT_CLIENT_EXIT_NONZERO;

Não há sinalizador em torno dessa verificação ou em torno da função que a chama. Parece estar relacionado ao max procs, o que, suponho, faz sentido: se você definir max procs alto o suficiente, ele não se incomodará em verificar até atingir o limite, o que talvez você nunca consiga.

Uma solução melhor para o que você está tentando fazer pode ser usar o GNU Make :

TARGETS=$(patsubst %,target-%,$(shell seq 1 40))

all: $(TARGETS)

target-%:
    sleep 10; date +"%H:%M:%S $*"

Então:

$ make -k -j4 

terá o mesmo efeito e lhe dará um controle muito melhor.

ckhan
fonte
9

Parece que um dos coloquialismos mais óbvios é mencionado apenas por outras propostas.

Ou seja, você pode usar o seguinte:

bash -c '$PROG_WHICH_MAY_FAIL ; (true)'

para "forçar o sucesso".

Observe que isso é parecido com a proposta de lornix (mas não em tantas palavras).

De qualquer forma, como isso efetivamente ignora o status real de saída do processo, certifique-se de considerar de alguma forma salvar o status do subprocesso para análise post-mortem. Por exemplo:

bash -c '$PROG_WHICH_MAY_FAIL || touch failed; (true)'

O trueaqui é um pouco redundante e, portanto, pode ser melhor escrito como:

bash -c '$PROG_WHICH_MAY_FAIL || touch failed'

Como provavelmente gostaríamos de saber quando o arquivo 'falhou' não pôde ser tocado. Em outras palavras, não estamos mais ignorando a falha, estamos tomando nota e continuando.

E, depois de considerar a natureza recursiva desse problema, talvez vejamos exatamente por que o xargs não torna fácil ignorar a falha. Como nunca é uma boa ideia - você deve aprimorar o tratamento de erros no processo que está desenvolvendo. Eu acredito que essa noção, no entanto, é mais inerente à "filosofia Unix" em si.

Por fim, suponho que também é isso que James Youngman alude ao recomendar trap, que provavelmente poderia ser usado de maneira semelhante. Ou seja, não ignore o problema ... prenda-o e resolva-o ou você acorda um dia e descobre que nenhum dos subprogramas teve sucesso ;-)

kingofephyra
fonte
3

Use trap:

$ seq 40 | xargs -i --max-procs=4 bash -c \
 'trap "echo erk; exit 1" INT TERM;  sleep 10; date +"%H:%M:%S {}";' fnord
16:07:39 2
16:07:39 4
erk
16:07:39 1
^C
erk
erk
erk
erk

Alternativamente, mude do shell para outro idioma no qual você também pode definir manipuladores de sinal.

Observe também que, depois de bash -c foo..especificar o valor que $0deve levar (aqui fnord) , para que a primeira palavra produzida por seqnão seja consumida.

James Youngman
fonte
2

Coloque outro comando lá para 'comer' o sinal do programa que está morrendo.

Eu tentei o seu exemplo, inicialmente como mostrado para provar o problema ... 'killall sleep' mata o processo do sono, interrompe o bash e o xargs é encerrado.

Como teste, coloquei um comando do tipo 'execute outro comando' entre xargs e bash ... neste caso '/ usr / bin / time'. desta vez (sem trocadilhos), o sono matador mata o processo de sono, mas o xargs continua.

Você canalizaria a saída do tempo para / dev / null, e isso faria exatamente o que você está procurando, sem uma grande reescrita do processo existente.

Imagino que, se refletir por um momento, poderia criar outro programa para fazer o mesmo sem a conversa stderr de '/ usr / bin / time'. Ou até mesmo escrever um, é apenas um 'fork' (ou derivado exec ()).

Lembre-se de usar '/ usr / bin / time', pois não tenho certeza de que o 'time' interno do bash fará o mesmo 'comer' do sinal.

lornix
fonte
1
Uma boa alternativa timepara esse fim seria env, pois tudo o que faz é adicionar zero ou mais variáveis ​​opcionais ao ambiente do programa que ele executa. Ele não emite saída própria e o código de retorno do programa invocado será passado de volta para o que for chamado env.
James Sneeringer
{Risada} Pensei nisso um tempo depois de escrever isso. O tempo foi a primeira coisa que veio à mente como um comando "executar alguma coisa". Funciona bem embora. Parabéns e obrigado.
Lornix #
2

Nem timenem envtrabalhou para mim (que repassar o valor de retorno do seu programa infantil) então eu escrevi bliss:

#!/bin/sh
"$@"
exit 0

então chmod u+x ~/bliss

e algo como find_or_similar | xargs ~/bliss fatally_dying_program.sh

pix
fonte