Isso é um bug no bash? 'return' não sai da função se for chamado de um cano

16

Eu tenho tido alguns problemas estranhos com o bash ultimamente. Ao tentar simplificar meu script, criei este pequeno código:

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

return deveria ter saído da função sem imprimir $? , não deveria? Bem, então eu verifiquei se posso voltar de um tubo sozinho:

$ echo | while read -r; do return 1; done
bash: return: can only 'return' from a function or sourced script

O mesmo acontece sem um loop while :

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

Há algo que estou sentindo falta aqui? Uma pesquisa no Google não trouxe nada sobre isso! Minha versão bash é 4.2.37 (1) -release no Debian Wheezy.

    
por Teresa e Junior 19.09.2015 / 05:34

5 respostas

10

Relacionados: link

Não é um bug que você não pode sair de um script ou retornar de uma função por exit ou return em subshells. Eles são executados em outro processo e não afetam o processo principal.

Além disso, suponho que você esteja vendo comportamentos não documentados de bash em especificações (provavelmente) indefinidas. Em uma função, nenhum erro é declarado para return no nível superior dos comandos subshell e apenas se comporta como exit .

IMHO é um erro bash para o comportamento inconsistente de return dependendo se a declaração principal está em uma função ou não.

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

Saída:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only 'return' from a function or sourced script
1
script.sh: line 22: return: can only 'return' from a function or sourced script
1
22
23
    
por 19.09.2015 / 06:41
17

Não é um erro em bash , mas seu comportamento documentado :

Each command in a pipeline is executed in its own subshell

A instrução return é válida estando dentro de uma definição de função mas estando em um subshell também, ela não afeta seu shell pai, então a próxima instrução, echo , é executada independentemente. No entanto, é uma construção de shell não portátil, pois o padrão POSIX permite que os comandos compondo um pipeline para ser executado em um subshell (o padrão) ou no topo (uma extensão permitida).

Additionally, each command of a multi-command pipeline is in a subshell environment; as an extension, however, any or all commands in a pipeline may be executed in the current environment. All other commands shall be executed in the current shell environment.

Espero que você possa dizer a bash para se comportar da maneira esperada com algumas opções:

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here
    
por 19.09.2015 / 11:03
6

Por documentação do POSIX, usando return fora da função ou o script de origem não é especificado . Então, depende do seu shell para lidar.

O shell SystemV reportará o erro, enquanto que em ksh , return fora da função ou script de origem se comportará como exit . A maioria dos outros shells POSIX e osh de schily também se comportam assim:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

ksh e zsh não produziram porque a última parte do pipe nesses shells foi executada no shell atual em vez de no subshell. A instrução de retorno afetou o ambiente de shell atual que chamou a função, fazendo com que a função retornasse imediatamente sem imprimir nada.

Na sessão interativa, bash reporta o erro mas não terminou o shell, schily's osh relatou o erro e finalizou o shell:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only 'return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

( zsh na sessão interativa e a saída é terminal não finalizada, bash , yash e schily's osh relataram o erro mas não terminaram o shell)

    
por 19.09.2015 / 12:35
4

Eu acho que você tem o comportamento esperado, no bash, cada comando em um pipeline é executado em um subshell. Você pode se convencer tentando modificar uma variável global de sua função:

foo(){ x=42; : | x=3; echo "x==$x";}

A propósito, o retorno está funcionando, mas retorna da subcamada. Mais uma vez você pode verificar isso:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

Produzirá o seguinte:

1
This should not be printed.

Portanto, a declaração de retorno saiu corretamente da subshell

.

    
por 19.09.2015 / 09:05
1

A resposta mais geral é que bash e alguns outros shells normalmente Coloque todos os elementos de um pipeline em processos separados. Isso é razoável quando a linha de comando é

program1 | program2 | program3

desde que os programas são normalmente executados em processos separados de qualquer maneira (a menos que você diga exec program ). Mas pode ser uma surpresa para

command1 | command2 | command3

onde alguns ou todos os comandos são comandos internos. Exemplos triviais incluem:

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

Um exemplo um pouco mais realista é

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

em que todo o loop whiledodone é colocado em um subprocesso, Por isso, suas alterações para t não são visíveis para o shell principal depois que o loop termina. E isso é exatamente o que você está fazendo - entrando em um loop while , fazendo com que o loop seja executado como um subshell, e, em seguida, tentando retornar do subshell.

    
por 19.09.2015 / 09:10