script shell de saída de um subshell

30

Considere este trecho:

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

Normalmente, quando funcé chamado, o script é finalizado, que é o comportamento pretendido. No entanto, se for executado em um sub-shell, como em

result=`func`

não sairá do script. Isso significa que o código de chamada deve verificar o status de saída da função todas as vezes. Há alguma maneira de evitar isto? É para isso que set -eserve?

Ernest AC
fonte
1
eu quero uma função "stop" que imprime uma mensagem para stderr e pára o script, mas não parar quando a função que chama parada é executado em uma sub-shell, como no exemplo
Ernest AC
2
Claro, porque sai do subshell e não do atual. Basta chamar a função diretamente: func.
1
i não pode chamá-lo diretamente porque ele retorna uma cadeia que tem de ser armazenado em uma variável
Ernest AC
1
@ ErnestAC Forneça todos os detalhes na pergunta original. A função acima não retorna uma string.
1
@htor eu mudei o exemplo
Ernest AC

Respostas:

10

Você pode matar o shell original ( kill $$) antes de ligar exit, e isso provavelmente funcionaria. Mas:

  • parece-me bastante feio
  • ele quebrará se você tiver um segundo subshell, ou seja, use um subshell dentro de um subshell.

Em vez disso, você pode usar uma das várias maneiras de devolver um valor nas Perguntas frequentes sobre o Bash . A maioria deles não é tão boa, infelizmente. Você pode apenas ficar parado procurando erros após cada chamada de função ( -etem muitos problemas ). Ou isso, ou mude para Perl.

derobert
fonte
5
Obrigado. Eu prefiro mudar para Python, no entanto.
Ernest AC
2
Enquanto escrevo, é o ano de 2019. É ridículo dizer a alguém para "mudar para Perl". Desculpe ser contencioso, mas você diria a alguém frustrado com 'C' que mudasse para Cobol, que é equivalente à IMO? Como Ernest aponta, o Python é uma escolha muito melhor. Minha preferência seria Ruby. De qualquer maneira, qualquer coisa menos Perl.
Graham Nicholls
38

Você pode decidir que o status de saída 77, por exemplo, significa sair de qualquer nível de subcamação e

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -Eem combinação com as ERRarmadilhas é um pouco como uma versão aprimorada, set -ena medida em que permite definir seu próprio tratamento de erros.

No zsh, os traps de ERR são herdados automaticamente; portanto, você não precisa set -E, também pode definir traps como TRAPERR()funções e modificá-los através $functions[TRAPERR], comofunctions[TRAPERR]="echo was here; $functions[TRAPERR]"

Stéphane Chazelas
fonte
1
Solução interessante! Claramente mais elegante que kill $$.
3
Uma coisa a observar, essa armadilha não manipula comandos interpolados, por exemplo echo "$(exit 77)"; o script continuará como se tivéssemos escrito #echo ""
Warbo 30/03
Interessante! Existe alguma sorte no bash (bem mais antigo) que não possua -E? talvez tenhamos que recorrer à definição de uma armadilha em um sinal de USUÁRIO e a uma eliminação desse sinal? Vou fazer alguns reasearch também ...
Olivier Dulac
Como saber, quando não estiver em uma armadilha sub-shell, para retornar 1 em vez de 77?
ceving
7

Como alternativa kill $$, você também pode tentar kill 0, funcionará no caso de subcascas aninhadas (todos os chamadores e processos secundários receberão o sinal) ... mas ainda é brutal e feio.

Stéphane Gimenez
fonte
2
Esse processo de matança não seria o ID 0?
Ernest AC
5
Isso matará todo o grupo de processos. Você pode encontrar coisas que não deseja (se, por exemplo, você começou algumas coisas em segundo plano).
derobert 18/09/12
2
@ ErnestAC veja a página de manual kill (2), pids ≤0 têm um significado especial.
derobert 18/09/12
0

Tente isso ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

Os resultados que recebo são ...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

Notas

  • Parametrizado para permitir que o ifteste seja trueou false(veja as 2 execuções)
  • Quando o ifteste é realizado false, nunca alcançamos o subshell.
DocSalvager
fonte
Isso é muito semelhante à ideia original sobre a qual o usuário estava postando e disse que não funcionou. Eu não acho que isso funcione para o caso subshell. Seu teste usando saídas falsas após o caso "shell" e nunca chega ao caso de teste "subshell". Acredito que falharia nesse caso, pois o subshell sairia da chamada "exit 1", mas não propagará o erro para o shell externo.
stuckj
0

(Resposta específica do Bash) O Bash não tem conceito de exceções. No entanto, com set -o errexit (ou o equivalente: set -e) no nível mais externo, o comando com falha resultará na saída do subshell com um status de saída diferente de zero. Se este for um conjunto de subshells aninhados sem condicionais em torno da execução desses subshells, ele efetivamente 'acumulará' todo o script e sairá.

Isso pode ser complicado ao tentar incluir bits de vários códigos bash em um script maior. Um pedaço do bash pode funcionar muito bem por si só, mas quando executado sob errexit (ou sem errexit), se comporta de maneiras inesperadas.

[192.168.13.16 (f0f5e19e) ~ 22:58:22] # bash -o errexit / tmp / foo
algo deu errado
[192.168.13.16 (f0f5e19e) ~ 22:58:31] # bash / tmp / foo
algo deu errado
Mas chegamos aqui de qualquer maneira
[192.168.13.16 (f0f5e19e) ~ 22:58:37] # cat / tmp / foo
#! / bin / bash
Pare () {
    eco "$ {1}"
    saída 1
}

se falso; então
    eco "foo"
outro
    (
        parar "algo deu errado"
    )
    eco "Mas chegamos aqui de qualquer maneira"
fi
[192.168.13.16 (f0f5e19e) ~ 22:58:40] #
Brian Chrisman
fonte
-2

Meu exemplo para sair em um forro:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit
vicente
fonte
1
Isso realmente não parece ser uma resposta que funcionaria com o comando sendo executado em subconjuntos, conforme solicitado pelo OP ... Dito isso, embora eu não discorde dos votos negativos dados a resposta. Votos negativos sem comentário ou motivo são tão inúteis quanto respostas ruins.
DVS