Eu tenho um script de shell que está lendo da entrada padrão . Em raras circunstâncias, não haverá ninguém pronto para fornecer informações, e o script deve expirar . Em caso de tempo limite, o script deve executar algum código de limpeza. Qual é a melhor maneira de fazer isso?
Esse script deve ser muito portátil , incluindo sistemas unix do século XX sem um compilador C e dispositivos incorporados executando o busybox, para que Perl, bash, qualquer linguagem compilada e até mesmo o POSIX.2 completo não sejam confiáveis. Em particular, $PPID
, read -t
armadilhas e perfeitamente compatíveis com POSIX não estão disponíveis. A gravação em um arquivo temporário também é excluída; o script pode ser executado mesmo que todos os sistemas de arquivos sejam montados somente leitura.
Apenas para tornar as coisas mais difíceis, também quero que o script seja razoavelmente rápido quando não tiver tempo limite. Em particular, eu também uso o script no Windows (principalmente no Cygwin), onde fork e exec são particularmente baixos, por isso quero manter seu uso no mínimo.
Em poucas palavras, eu tenho
trap cleanup 1 2 3 15
foo=`cat`
e quero adicionar um tempo limite. Não posso substituir cat
pelo read
incorporado. Em caso de tempo limite, desejo executar a cleanup
função.
Antecedentes: esse script está adivinhando a codificação do terminal, imprimindo alguns caracteres de 8 bits e comparando a posição do cursor antes e depois. O início do script testa que stdout está conectado a um terminal suportado, mas às vezes o ambiente está mentindo (por exemplo, plink
define TERM=xterm
mesmo que seja chamado comTERM=dumb
). A parte relevante do script fica assim:
text='Éé' # UTF-8; shows up as Ãé on a latin1 terminal
csi='␛['; dsr_cpr="${csi}6n"; dsr_ok="${csi}5n" # ␛ is an escape character
stty_save=`stty -g`
cleanup () { stty "$stty_save"; }
trap 'cleanup; exit 120' 0 1 2 3 15 # cleanup code
stty eol 0 eof n -echo # Input will end with `0n`
# echo-n is a function that outputs its argument without a newline
echo-n "$dsr_cpr$dsr_ok" # Ask the terminal to report the cursor position
initial_report=`tr -dc \;0123456789` # Expect ␛[42;10R␛[0n for y=42,x=10
echo-n "$text$dsr_cpr$dsr_ok"
final_report=`tr -dc \;0123456789`
cleanup
# Compute and return initial_x - final_x
Como posso modificar o script para que, se tr
não tiver lido nenhuma entrada após 2 segundos, ele seja morto e o script execute a cleanup
função?
Respostas:
Que tal isso:
Ou seja: execute o comando de produção de saída e
sleep
no mesmo grupo de processos, um grupo de processos apenas para eles. Qualquer comando que retorne primeiro mata todo o grupo de processos.Alguém se perguntaria: Sim, o cachimbo não é usado; é ignorado usando os redirecionamentos. O único objetivo disso é fazer com que o shell execute os dois processos no mesmo grupo de processos.
Como Gilles apontou em seu comentário, isso não funcionará em um script de shell, porque o processo de script seria eliminado junto com os dois subprocessos.
Uma maneira de forçar a execução de um comando em um grupo de processos separado é iniciar um novo shell interativo:
Mas pode haver ressalvas com isso (por exemplo, quando stdin não é um tty?). O redirecionamento stderr existe para se livrar da mensagem "Terminated" quando o shell interativo é morto.
Testado com
zsh
,bash
edash
. Mas e os velhos?O B98 sugere a seguinte alteração, trabalhando no Mac OS X, com GNU bash 3.2.57 ou Linux com dash:
-
1. diferente do
setsid
que parece não ser padrão.fonte
kill 0
acaba matando também o chamador do script. Existe uma maneira portátil de forçar o pipeline para seu próprio grupo de processos?setprgp()
semsetsid
por agora :-(Você já está capturando 15 (a versão numérica de
SIGTERM
, que é o quekill
envia, a menos que seja dito o contrário), então você já deve estar pronto. Dito isto, se você estiver visualizando o pré-POSIX, saiba que as funções do shell também podem não existir (elas vieram do shell do System V).fonte
cat
a saída. Eu já experimentei um pouco, mas não encontrei nada com o qual estou satisfeito até agora.$!
, acho que algumas dessas máquinas que uso raramente não têm controle de trabalho, está$!
universalmente disponível?bash
específicas das quais fiz uma vez envolvendo abuso-o monitor
. Eu estava pensando em termos de conchas verdadeiramente antigas quando escrevi isso (funcionou na v7). Dito isso, acho que você pode fazer uma de duas outras coisas: (1) o histórico "seja qual for" ewait $!
, ou (2) também enviaSIGCLD
/SIGCHLD
... mas em máquinas suficientemente antigas, a última é inexistente ou não é portável (a primeira é System III / V, o último BSD e V7 não tinham).$!
volta à V7, pelo menos, e certamente é anterior ash
algo que sabia alguma coisa sobre controle de tarefas (na verdade, por um longo tempo/bin/sh
no BSD não fazia controle de tarefas; você tinha que corrercsh
para obtê-la - mas$!
estava lá )$!
.Embora o coretuils a partir da versão 7.0 inclua um comando timeout, você mencionou alguns ambientes que não o possuem. Felizmente, o pixelbeat.org tem um script de tempo limite escrito
sh
.Eu já usei isso em várias ocasiões e funciona muito bem.
http://www.pixelbeat.org/scripts/timeout ( Observação: o script abaixo foi ligeiramente modificado em relação ao pixelbeat.org, veja os comentários abaixo desta resposta.)
fonte
</dev/stdin
é um não-op.</dev/tty
permitiria a leitura do terminal, o que é bom o suficiente para o meu caso de uso.Que tal (ab) usar NC para este
Gostar;
Ou agrupados em uma única linha de comando;
A última versão, embora pareça estranha, na verdade funciona quando eu a testo em um sistema Linux - o meu melhor palpite é que ele funcione em qualquer sistema e, caso contrário, uma variação do redirecionamento de saída pode resolver a portabilidade. O benefício aqui é que nenhum processo em segundo plano está envolvido.
fonte
nc
em algumas antigas caixas Unix, nem em muitos Linux embarcados.Outra maneira de executar o pipeline em seu próprio grupo de processos é executar
sh -c '....'
em um pseudo-terminal usando oscript
comando (que aplica implicitamente asetsid
função).fonte
script
em algumas antigas caixas Unix, nem em muitos Linux embarcados.A resposta em https://unix.stackexchange.com/a/18711 é muito boa.
Eu queria alcançar um resultado semelhante, mas sem ter que chamar explicitamente o shell novamente, porque queria chamar as funções existentes do shell.
Usando o bash, é possível fazer o seguinte:
Então, suponha que eu já tenha uma função shell
f
:Executando isso, vejo:
fonte
fonte