Faça bash exit! = 0 quando chamado por AWK e interrompido com ^ C

7

Estou tendo um problema com a seguinte parte do script (G) AWK:

do {
    ...
} while (system("sleep 10"))

Minha intenção é quebrar o loop quando o usuário pressiona ^ C durante o sono, mas não está funcionando.

Eu acredito que o problema é que o Bash sai com 0 quando interrompido com ^ C, pelo menos quando ele é executado pelo system() :

do AWK
$ awk 'BEGIN { print "\n" system("sleep 2") }'
(let the sleep complete)
0

$ awk 'BEGIN { print "\n" system("sleep 2") }'
^C
0

Por que isso acontece?

Isso é um bug no Bash ou no (G) AWK?

Existe uma solução simples que não envolve uma sintaxe complicada específica do Bash como trap ?

O melhor que eu pude fazer é isto:

do {
    ...
} while (42 == system("sleep 10 && exit 42"))

que ainda parece um kludge para mim.

    
por Tobia 03.03.2016 / 17:07

1 resposta

6

Qual system() do awk deve retornar está mal especificado .

O que parece ser comum entre awk implementações é que, após uma saída normal, ele retorna o código de saída (o número passou para exit(3) módulo 256), mas quando o processo shell é morto por um sinal, há um muito comportamento diferente.

Observe também que, embora a função C system(3) deva ignorar SIGINT (e SIGQUIT) no pai, não está muito claro (pelo menos para mim) que o requisito também se aplica a awk ' system() . Algumas awk implementações (como mawk ) morrerão naquele SIGINT (esse também é o comportamento que eu gostaria de ver, pois não gosto de meu CTRL-C ser ignorado apenas porque awk está rodando o system() function), algumas (como gawk ou implementações tradicionais) não.

Observe também que alguns shells podem interceptar alguns desses sinais e eventualmente chamar exit() , o que afetaria o comportamento (veja a discussão em comentários sobre o shell Bourne, por exemplo), é por isso que uso exec nos exemplos abaixo para remover o shell do loop.

Para o valor retornado por system() (há ainda mais variação se você considerar close() 1 ) em um SIGINT, vemos:

$ nawk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ bwk-awk 'BEGIN {print system("exec kill -s INT $$")}'
0.0078125
$ mawk 'BEGIN {print system("exec kill -s INT $$")}'
130
$ gawk 'BEGIN {print system("exec kill -s INT $$")}'
0

0.0078125 sendo 2 / 256 (para SEGV de 11 , você obteria 0,542969 ((128 + 11) / 256) se um núcleo fosse descartado, 0,0429688 (11/256) caso contrário), nawk sendo o nawk encontrado no Solaris 10 ou 11, ou sua porta Linux no toolchest da Heirloom, bwk-awk sendo o awk mantido pelo próprio Brian Kernighan (o K em awk ) a base para o awk encontrado em alguns BSDs (aqui testados no Debian GNU / Linux). /usr/xpg4/bin/awk no Solaris 11 se comporta como gawk .

Portanto, com base no valor s retornado por system(3) (um inteiro onde os bits 0 a 6 são o número do sinal, o bit 7 o core-bit e os bits 8 a 15 o código de saída), awk ' s system() acima retorna:

  • s / 256 (tradicional awk implementations),
  • int(s/256) ( gawk ),
  • ou em mawk , a mesma transformação feita por shells como Bourne ou C-shell, por exemplo ( (s&127)+128 se morto, s>>8 caso contrário), exceto que se um núcleo for descartado, você obtém (s&127)+256 em vez de (s&127)+128 (o valor é (s&255)+128 ).

Então, aqui, você poderia fazer:

awk 'BEGIN{print system("trap exit\ 1 INT; sleep 10")}'

Mas isso ainda faria com que awk fosse eliminado com algumas implementações awk , como mawk . Se o seu sh for bash ou yash , você poderá fazer:

awk 'BEGIN{print system("set -m; sleep 10; exit")}'

Portanto, sleep é executado em seu próprio grupo de processos (e apenas obtém o SIGINT).

Outra alternativa poderia ser ignorar o SIGINT antes de chamar awk . No entanto, a maioria das shells (e isso é um requisito POSIX) não pode mudar um manipulador de sinal se o sinal já foi ignorado no início. Então coisas como:

(
  trap '' INT
  awk 'BEGIN{print system("trap exit\ 1 INT; sleep 10; exit")}'
)

não vai funcionar. zsh não tem essa limitação (auto-infligida), então se você sabe que zsh está disponível, você poderia fazer:

(
  trap '' INT
  awk 'BEGIN{print system("exec zsh -c \"TRAPINT() exit 1; sleep 10\"")}'
)

O que funcionaria se awk fosse mawk , gawk ou outro e evitaria ter que mexer no controle do trabalho. Neste ponto, vale a pena considerar usar perl / python / ruby ... em vez de awk , onde você pode adaptar o tratamento de sinais às suas necessidades.

Notas

1 Sobre close() de um pipeline, como em:

awk 'BEGIN {cmd = "kill -s INT $$"; cmd | getline; print close(cmd)}'

Primeiro, desta vez ^C interrompe awk em todas as implementações que eu tentei (não existe esse requisito para ignorar o SIGINT / SIGQUIT para popen(3) / pclose(3) (uma maneira natural de implementar esse getline ) como existe para system(3) ).

Mas, quando se trata do status de saída (em que s é o valor retornado por pclose(3) / waitpid(2) , como para system() acima), vemos:

  • O Solaris nawk : não funciona, você não pode chamar close() assim no Solaris nawk .
  • /usr/xpg4/bin/awk no Solaris. Retorna sempre 0, mesmo após um exit(1) feito pelo processo. Claramente um bug de conformidade.
  • gawk e bwk-awk : dá s ( exit 1 fornece 256, morto por SIGINT, 2, morto por um SIGSEGV de 11 com núcleo, dá 139).
  • mawk : o mesmo que para system() , parece que mawk é a única implementação que pensou nisso.
por 03.03.2016 / 17:34