Eu tenho um script que não sai quando eu quero.
Um script de exemplo com o mesmo erro é:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
Eu assumiria ver a saída:
:~$ ./test.sh
1
:~$
Mas eu realmente vejo:
:~$ ./test.sh
1
2
:~$
O ()
encadeamento de comando de alguma forma cria um escopo? O que está exit
saindo, se não o script?
shell-script
exit
subshell
Minix
fonte
fonte
Respostas:
()
executa comandos no subshell; portanto,exit
você está saindo do subshell e retornando ao shell pai. Use chaves{}
se desejar executar comandos no shell atual.No manual do bash:
Vale ressaltar que a sintaxe do shell é bastante consistente e o subshell também participa de outras
()
construções, como substituição de comando (também com a`..`
sintaxe de estilo antigo ) ou substituição de processo, para que o seguinte também não saia do shell atual:Embora possa ser óbvio que os subshells estão envolvidos quando os comandos são explicitamente colocados no interior
()
, o fato menos visível é que eles também são gerados nessas outras estruturas:comando iniciado em segundo plano
não sai do shell atual porque (depois
man bash
)o gasoduto
ainda sai apenas do subshell.
No entanto, conchas diferentes se comportam de maneira diferente a esse respeito. Por exemplo,
bash
coloca todos os componentes do pipeline em subshells separados (a menos que você use alastpipe
opção em invocações onde o controle de tarefas não está ativado), mas a AT&Tksh
ezsh
executa a última parte dentro do shell atual (ambos os comportamentos são permitidos pelo POSIX). portantobasicamente não faz nada no bash, mas sai do zsh por causa do último
exit
.coproc exit
também é executadoexit
em um subshell.fonte
{
e}
não são sintaxes, são palavras reservadas e devem estar entre espaços, e a lista deve terminar com um terminador de comando (ponto-e-vírgula, nova linha, e comercial)(echo $$)
imprime o ID do shell pai porque$$
é expandido antes mesmo da criação do subshell. Na verdade, a impressão do ID do processo do subshell pode ser complicada, consulte stackoverflow.com/questions/9119885/…$$
expandido antes da criação do subshell, e ainda$BASHPID
mostra o valor correto para um subshell?A execução de
exit
um subshell é uma armadilha:O script imprime 42, sai do subshell com código de retorno
1
e continua com o script. Mesmo substituindo a chamada porecho $(CALC) || exit 1
não ajuda, porque o código de retorno deecho
é 0, independentemente do código de retorno decalc
. Ecalc
é executado antes deecho
.Ainda mais intrigante é impedir o efeito
exit
, envolvendo-o no interior,local
como no script a seguir. Tropecei no problema quando escrevi uma função para verificar um valor de entrada. Exemplo:Eu quero criar um arquivo chamado "year month day.log", ou seja,
20141211.log
hoje. A data é inserida por um usuário que pode não fornecer um valor razoável. Portanto, na minha funçãofname
, verifico o valor de retorno dedate
para verificar a validade da entrada do usuário:Parece bom. Deixe o script ser nomeado
s.sh
. Se o usuário chamar o script./s.sh "Thu Dec 11 20:45:49 CET 2014"
, o arquivo20141211.log
será criado. Se, no entanto, o usuário digitar./s.sh "Thu hec 11 20:45:49 CET 2014"
, o script exibirá:A linha
fname…
diz que os dados de entrada incorretos foram detectados no subshell. Mas oexit 1
final dalocal …
linha nunca é acionado porque alocal
diretiva sempre retorna0
. Isso ocorre porquelocal
é executado depois$(fname)
e, portanto, substitui seu código de retorno. E por isso, o script continua e invocatouch
com um parâmetro vazio. Este exemplo é simples, mas o comportamento do bash pode ser bastante confuso em um aplicativo real. Eu sei, programadores reais não usam locais.☺Para deixar claro: sem o
local
, o script é interrompido conforme o esperado quando uma data inválida é inserida.A correção é dividir a linha como
O comportamento estranho está de acordo com a documentação da
local
página de manual do bash: "O status de retorno é 0, a menos que local seja usado fora de uma função, um nome inválido seja fornecido ou o nome seja uma variável somente leitura".Embora não seja um bug, sinto que o comportamento do bash é contra-intuitivo. Estou ciente da sequência de execução
local
, no entanto, não devo mascarar uma tarefa quebrada.Minha resposta inicial continha algumas imprecisões. Após uma discussão reveladora e profunda com mikeserv (obrigado por isso), fui corrigi-los.
fonte
doit()
.A solução real:
O agrupamento de erros será executado apenas se
bla
retornar um status de erro eexit
não estiver em uma subshell; portanto, o script inteiro será interrompido.fonte
Os colchetes iniciam um subshell e a saída sai apenas desse subshell.
Você pode ler o código de saída
$?
e adicioná-lo ao seu script para sair do script se o subshell foi encerrado:fonte