Redirecionando a saída de erro padrão para a variável bash

7

Aqui está um snippet de código C que fará com que segfault :

// segfault.c

#include <string.h>

int main()
{
    memset((char *)0x0, 1, 100);
    return 1;
}

Compile com:

gcc segfault.c -o segfault

Se executado a partir do bash:

$ ./segfault
Segmentation fault (core dumped)

Agora eu envolvi a chamada dentro de um script bash. Existem três tentativas consecutivas. Eu quero obter a saída de erro dentro da variável ret e exibi-lo.

#!/bin/bash

# segfault.sh

ret='./segfault 2>&1'
echo "1) " $ret
ret='eval ./segfault 2>&1'
echo "2) " $ret
ret='eval ./segfault 2>&1 | cat'
echo "3) " $ret

Se eu executar o script do bash:

1) 
2) 
3)  ./segfault.sh: line 7: 28814 Segmentation fault (core dumped) ./segfault

É evidente que apenas a terceira forma de chamada funciona. Minha pergunta é: por que as duas primeiras formas não puderam capturar a saída de erro?

    
por Holmes.Sherlock 17.01.2016 / 11:47

2 respostas

7

Funciona para mim com script bash simplificado (somente stderr ):

$ cat seg.sh 
#!/bin/bash
echo "Segfault" 1>&2
$ test='./seg.sh'; echo "x$test"
Segfault
x
$ test='./seg.sh 2>&1'; echo "x$test"
xSegfault
$ test='eval ./seg.sh 2>&1'; echo "x$test"
xSegfault

O problema no seu caso é causado pelo fato de que Segmentation fault (core dumped) não é escrito pelo seu programa (porque ele é morto pelo kernel), mas pelo processo pai, que obtém as informações sobre a morte de seu filho. Este efeito é oculto colocando-o em outro processo e pipe com cat em seu último exemplo. Você deve confiar no código de saída e, em seguida, no stderr :

$ ./segfault; echo $?
Segmentation fault (core dumped)
139
    
por 17.01.2016 / 12:50
2

A mensagem “Falha de segmentação (núcleo)” é emitida por bash, não pelo programa que deixou de funcionar (quando a mensagem é emitida, o programa já está morto!). O redirecionamento se aplica apenas ao próprio programa.

Para redirecionar mensagens do próprio shell sobre o programa, execute o programa dentro de uma construção de agrupamento de shell e redirecione a saída de todo o grupo. A construção de agrupamento de shell mais básica, que não faz nada além de grupo, é chaves.

ret='{ ./segfault; } 2>&1'

O formulário ret='eval ./segfault 2>&1' aplica-se o redirecionamento para toda a avaliação do comando eval , por isso, em princípio, ele deve funcionar, e ele faz de fato trabalhar na minha máquina com o bash 4.3.30 e versões mais antigas. O que pode estar acontecendo (e eu posso reproduzi-lo com ksh) é que a sua versão do bash faz algumas otimizações para evitar subprogramas que se bifurcam quando eles são o último comando em um subnível. A maneira nominal de executar o comando ret='eval ./segfault' é:

  • Crie um canal.
  • Bifurcação, por exemplo, crie um subprocesso de shell. No subprocesso (processo 1):
    • Redireciona a saída para o pipe.
    • Execute o eval incorporado.
    • Garfo. No subprocesso (processo 2):
      • Executar o arquivo ./segfault , ou seja, substituir o programa shell que está sendo executado neste processo pelo programa segfault .
    • (No processo 1) Aguarde o processo 2 terminar.
    • O processo 1 sai.
  • (no processo shell original): leia a partir do canal e acumule os dados na variável ret .
  • Quando o canal estiver fechado, continue a execução.

Como você pode ver, o processo 1 cria outro processo, aguarda que termine e sai imediatamente. Seria mais eficiente para o processo 1 se reciclar. Alguns escudos (e versões shell) são melhores que outros em reconhecer tais situações e fazer um chamada de cauda otimização . No entanto, no caso de ret='{ ./segfault; } 2>&1' , o processo 2 tem seu erro padrão redirecionado para o descritor de arquivo 1, mas o processo 1 não. Na versão shell você tentou, o otimizador não reconheceu esta situação (que poderia ter realizado uma chamada de cauda, mas deve ter configurado o redirecionamento de forma diferente).

    
por 18.01.2016 / 00:53