Como obtenho o valor de saída e saída de um subshell ao usar "bash -e"?

70

Considere o seguinte código

outer-scope.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

inner-scope.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

Estou tentando outer-scope.shsair quando uma chamada inner()falha. Desde que $()invoca um sub-shell, isso não acontece.

De que outra forma eu obtenho a saída de uma função, preservando o fato de que a função pode sair com um código de saída diferente de zero?

jabalsad
fonte

Respostas:

102

$()preserva o status de saída; você só precisa usá-lo em uma declaração que não possui status próprio, como uma atribuição.

output = $ (interno)

Depois disso, $?conteria o status de saída de innere você poderá usar todos os tipos de verificações para ele:

output=$(inner) || exit $?
echo $output

Ou:

if ! output=$(inner); then
    exit $?
fi
echo $output

Ou:

if output=$(inner); then
    echo $output
else
    exit $?
fi

(Nota: Um bare exitsem argumentos é equivalente a exit $?- ou seja, ele sai com o status de saída do último comando. Eu usei o segundo formulário apenas para maior clareza.)


Além disso, para o registro: sourceé completamente independente neste caso. Você pode apenas definir inner()no outer-scope.sharquivo, com os mesmos resultados.

gravidade
fonte
Por que é isso, mesmo que $? contém o status de saída de $ () que o script não sai automaticamente (considerando que -e está definido)? EDIT: deixa pra lá, acho que você respondeu minhas perguntas, obrigado!
precisa saber é o seguinte
Não tenho certeza. (Eu não testei nenhuma das opções acima.) Mas há algumas restrições em -e, todas explicadas na página de manual do bash; Além disso, se você estiver perguntando echo $(), talvez seja porque os códigos de saída dos subshells são ignorados quando a linha - o echocomando - possui um código de saída (geralmente 0).
grawity
11
Hmm, quando digito if ! $(exit 1) ; then echo $?; fi, entendo 0. Não tenho certeza ifdo caminho a seguir, se você precisar preservar esse valor de saída.
Ron Burk
@ grrawity - Isso funciona com o Dash? Estou tentando solucionar um comportamento irritante do Autoconf (ou seja, o Autoconf informando sucesso quando a Sun ou o compilador da IBM imprimem opção ilegal no terminal).
JWW
3
if ! output=$(inner); then exit $?; fisairá com um código de retorno 0, porque $?fornecerá o código de retorno em !vez do código de retorno de inner. Você poderia obter o comportamento desejado com if output=$(inner); then : ; else exit $?; fimas isso é, obviamente, mais detalhado
SJL
26

Consulte BashFAQ / 002 :

Se você deseja ambos (status de saída e saída):

output=$(command)
status=$? 

Um caso especial

Observe sobre um caso complicado com variáveis ​​locais de função, compare o seguinte código:

f() { local    v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }

Nós conseguiremos:

$ f     # fooled by 'local' with inline initialization
output:data, status:0

$ g     # a good one
output:data, status:1

Por quê?

Quando a saída de um subshell é usada para inicializar uma localvariável, o status de saída não é mais do subshell, mas do comando , o que é mais provável .local 0

Consulte também https://stackoverflow.com/a/4421282/537554

ryenus
fonte
3
Embora isso realmente não tenha respondido à pergunta, isso me foi útil hoje, então marque com +1.
Fourpastmidnight 27/07/16
11
O status de saída dos comandos bash é sempre o do último comando executado. Quando passamos tanto tempo em idiomas fortemente tipados, é fácil esquecer que "local" não é um especificador de tipo, mas apenas outro comando. Obrigado por reiterar este ponto aqui, me ajudou hoje.
Markeissler 17/09/16
2
Uau, encontrei esse problema exato agora e você o esclareceu. Obrigado!
krb686
4
#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("

Ao adicionar echo, o subshell não fica sozinho (não é verificado separadamente) e não aborta. A atribuição contorna esse problema.

Você também pode fazer isso e redirecionar a saída para um arquivo, para processá-lo posteriormente.

tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile
Daniel Beck
fonte
Claro, o $ tmpfile continua a existir na segunda variante ...
Daniel Beck