As atribuições são como comandos com um status de saída, exceto quando há substituição de comando?

10

Veja os exemplos a seguir e suas saídas nos shells POSIX:

  1. false;echo $?ou false || echo 1:1
  2. false;foo="bar";echo $?ou foo="bar" && echo 0:0
  3. foo=$(false);echo $?ou foo=$(false) || echo 1:1
  4. foo=$(true);echo $?ou foo=$(true) && echo 0:0

Conforme mencionado pela resposta mais votada em /programming/6834487/what-is-the-variable-in-shell-scripting :

$? é usado para encontrar o valor de retorno do último comando executado.

Isso provavelmente é um pouco enganador nesse caso, então vamos obter a definição POSIX, que também é citada em um post desse segmento:

? Expande para o status de saída decimal do pipeline mais recente (consulte Pipelines).

Portanto, parece que uma atribuição em si conta como um comando (ou melhor, como uma parte do pipeline) com um valor de saída zero, mas que se aplica antes do lado direito da atribuição (por exemplo, a substituição de comando chama nos meus exemplos aqui).

Vejo como esse comportamento faz sentido do ponto de vista prático, mas me parece um pouco incomum que a tarefa em si contaria nessa ordem. Talvez para deixar mais claro por que isso é estranho para mim, vamos assumir que a tarefa era uma função:

ASSIGNMENT( VARIABLE, VALUE )

então foo="bar"seria

ASSIGNMENT( "foo", "bar" )

e foo=$(false)seria algo como

ASSIGNMENT( "foo", EXECUTE( "false" ) )

o que significa que as EXECUTEexecuções são executadas primeiro e somente depois, ASSIGNMENT mas ainda é o EXECUTEstatus que importa aqui.

Estou correto na minha avaliação ou estou entendendo errado / faltando alguma coisa? Essas são as razões certas para eu ver esse comportamento como "estranho"?

phk
fonte
1
Desculpe, mas não está claro para mim o que você acha estranho.
Kusalananda
1
@Kusalananda Talvez seja útil dizer que começou comigo me perguntando: "Por que false;foo="bar";echo $?sempre retorna 0 quando o último comando real executado foi false?" É basicamente que as atribuições se comportam de maneira especial quando se trata de códigos de saída. Seu código de saída é sempre 0, exceto quando não é por causa de algo que foi executado como parte do lado direito da tarefa.
Php
1
Relacionado: stackoverflow.com/questions/20157938/…
Kusalananda

Respostas:

10

O status de saída para atribuições é estranho . A maneira mais óbvia de uma atribuição falhar é se a variável de destino estiver marcada readonly.

$ err(){ echo error ; return ${1:-1} ; }
$ PS1='$? $ '
0 $ err 42
error
42 $ A=$(err 12)
12 $ if A=$(err 9) ; then echo wrong ; else E=$? ; echo "E=$E ?=$?" ; fi
E=9 ?=0
0 $ readonly A
0 $ if A=$(err 10) ; then echo wrong ; else E=$? ; echo "E=$E ?=$?" ; fi
A: is read only
1 $

Observe que nem os caminhos verdadeiro nem falso da instrução if foram utilizados; a falha na atribuição parou a execução de toda a instrução. o bash no modo POSIX e o ksh93 e o zsh abortam um script se uma atribuição falhar.

Para citar o padrão POSIX sobre isso :

Um comando sem um nome de comando, mas que inclui uma substituição de comando, possui um status de saída da última substituição de comando que o shell executou.

Essa é exatamente a parte da gramática de shell envolvida no

 foo=$(err 42)

que vem de um simple_command(comando_ simples → cmd_prefix → ASSIGNMENT_WORD). Portanto, se uma atribuição for bem-sucedida, o status de saída será zero, a menos que a substituição do comando esteja envolvida; nesse caso, o status de saída será o status do último. Se a atribuição falhar, o status de saída será diferente de zero, mas talvez você não possa obtê-lo.

Icaro
fonte
1
Para adicionar à sua resposta, aqui está uma resposta de um segmento diferente, onde um padrão POSIX mais recente é citado, a conclusão é basicamente a mesma: unix.stackexchange.com/a/270831/117599
phk
4

Você diz,

… Parece que uma atribuição em si conta como um comando… com um valor de saída zero, mas que se aplica antes do lado direito da atribuição (por exemplo, uma chamada de substituição de comando…)

Essa não é uma maneira terrível de encarar. Mas é uma pequena simplificação excessiva. O status geral de retorno de

A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
é o status de saída de . A atribuição que ocorre após a atribuição não define o status geral da saída como 0.cmd4E=D=

Além disso, como o icarus aponta , as variáveis ​​podem ser definidas como somente leitura. Considere a seguinte variação no exemplo do icarus:

$ err() { echo "stdout $*"; echo "stderr $*" >&2; return ${1:-1}; }
$ readonly A
$ Z=$(err 41 zebra) A=$(err 42 antelope) B=$(err 43 badger)
stderr 41 zebra
stderr 42 antelope
bash: A: readonly variable
$ echo $?
1
$ printf "%s = %s\n" Z "$Z" A "$A" B "$B"
Z = stdout 41 zebra
A =
B =
$

Mesmo sendo Asomente leitura, o bash executa a substituição do comando à direita de A=- e depois anula o comando porque Aé somente leitura. Isso contradiz ainda mais a sua interpretação de que o valor de saída da atribuição se aplica antes do lado direito da atribuição.

G-Man diz que 'restabelece Monica'
fonte