Por que 'jobs' sempre retorna uma linha para processos concluídos quando executados em um subshell dentro de um script?

4

Normalmente, quando um trabalho é iniciado em segundo plano, jobs relatará que ele foi concluído na primeira vez que for executado após a conclusão do trabalho e nada para execuções subseqüentes:

$ ping -c 4 localhost &>/dev/null &
[1] 9666
$ jobs
[1]+  Running                 ping -c 4 localhost &> /dev/null &
$ jobs
[1]+  Done                    ping -c 4 localhost &> /dev/null
$ jobs  ## returns nothing
$ 

No entanto, quando executado em um subshell dentro de um script, ele sempre retorna um valor. Este script nunca sairá:

#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
    sleep 1; 
done

Se eu usar tee na construção [[ ]] para ver a saída de jobs , vejo que está sempre imprimindo a linha Done ... . Não apenas uma vez como eu esperava, mas, aparentemente, para sempre.

O que é ainda mais estranho é que executar jobs dentro do loop faz com que ele saia como esperado:

#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
    jobs
    sleep 1; 
done

Finalmente, como apontado por @mury, o primeiro script funciona como esperado e sai se for executado a partir da linha de comando:

$ ping -c 5 localhost &>/dev/null & 
[1] 13703
$ while [[ -n $(jobs) ]]; do echo -n . ; sleep 1; done
...[1]+  Done                    ping -c 5 localhost &> /dev/null
$ 

Isso surgiu quando eu estava respondendo a uma pergunta no Superusuário, por isso, não poste respostas recomendando formas melhores de fazendo o que esse loop faz. Eu posso pensar em alguns eu mesmo. O que eu estou curioso é sobre

  1. Por que jobs age de maneira diferente na construção [[ ]] ? Por que sempre retornará a linha Done... enquanto ela não é executada manualmente?

  2. Por que a execução de jobs no loop altera o comportamento do script?

por terdon 24.03.2015 / 12:23

1 resposta

6

Você sabe, é claro, que $(…) faz com que o (s) comando (s) dentro dos parênteses para executar em um subshell. E você sabe, é claro, que jobs é um shell embutido. Bem, parece que jobs apaga um trabalho da memória do shell uma vez que sua morte tenha sido relatada . Mas, quando você executa $(jobs) , o comando jobs é executado em um subshell, por isso não tem a chance de dizer ao shell pai (aquele que está executando o script) que a morte do trabalho em segundo plano ( ping , no seu exemplo) foi reportado. Então, toda vez que o shell gerar uma sub-shell para executar o $(jobs) thingie, esse subshell ainda tem uma lista completa de jobs (ou seja, o trabalho ping está lá, apesar de estar morto após a 5ª iteração), e assim jobs ainda (novamente) acredita que ele precisa informar sobre o status do trabalho ping (mesmo que esteja morto nos últimos quatro segundos).

Isso explica por que executar um comando jobs não adulterado dentro do loop faz com que ele saia como esperado: depois de executar jobs no shell pai , o shell pai sabe que a rescisão do trabalho foi relatada ao usuário.

Por que é diferente no shell interativo? Porque, sempre que um filho em primeiro plano de um shell interativo termina, o shell informa sobre quaisquer trabalhos em segundo plano que tenham terminado 1 enquanto o processo de primeiro plano estava em execução. Então, o ping termina enquanto o sleep 1 está em execução, e quando o sleep terminar, o shell relata a morte do trabalho em segundo plano. Et voilà.

1 Pode ser mais preciso dizer "qualquer trabalho em segundo plano que mudaram de estado enquanto o processo de primeiro plano estava em execução. ” Acredito que também possa relatar sobre trabalhos que foram suspensos ( kill -SUSP , o programático equivalente a Ctrl + Z ) ou tornar-se não suspenso ( kill -CONT , que é o que o comando fg faz).

    
por 24.03.2015 / 15:00