Por que a última função executada em um pipeline de script shell POSIX não retém valores de variáveis?

1

Assuma o seguinte script:

#!/bin/sh

func1() {
  eval $1'=$(cat)'
  eval echo "Value$2 inside function : \$$1"
}

func1 x 1 <<'HEREDOC'
Hello World
HEREDOC

echo "Value1 outside function: $x"

x=""

echo "Hello World" | func1 x 2

echo "Value2 outside function: $x"

Na bash 4.3.43-4.fc25, a saída é:

Value1 inside function : Hello World
Value1 outside function: Hello World
Value2 inside function : Hello World
Value2 outside function: 

Usar o bashism shopt -s lastpipe faz com que a última linha também mostre "Hello World", mas intuitivamente não entendo por que isso não acontece automaticamente. Isso é esperado?

Parece que o padrão POSIX realmente não aborda este tópico: link

    
por Kevin 29.01.2017 / 07:17

3 respostas

1

POSIX diz que :

each command of a multi-command pipeline is in a subshell environment

e que

Changes made to the subshell environment shall not affect the shell environment

Se você pensar na maneira mais direta de implementar um pipeline, isso faz sentido - chame pipe() , bifurque um subshell e troque a saída / entrada padrão de um lado cada, depois execute o comando no subshell. Isso também é o comportamento definido do Bash .

Você não pode confiar nisso, porque

as an extension, however, any or all commands in a pipeline may be executed in the current environment

Isso é o que o lastpipe faz nesse caso. Em alguns outros shells, esse é o comportamento padrão em certas situações.

    
por 29.01.2017 / 07:54
2

Sim, o POSIX menciona esse tópico. De aqui :

A subshell environment shall be created as a duplicate of the shell environment, except that signal traps that are not being ignored shall be set to the default action. Changes made to the subshell environment shall not affect the shell environment. Command substitution, commands that are grouped with parentheses, and asynchronous lists shall be executed in a subshell environment. 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.

Depende do shell para escolher seu comportamento padrão. Em:

echo 1 | read x
echo "$x"

apenas zsh e ksh output 1 , outros shells modernos semelhantes a Bourne geram string vazia.

    
por 29.01.2017 / 07:53
1

Historicamente, o shell Bourne e o Korn shell se comportou de maneira diferente. Como os dois lados de um pipe são executados em paralelo, em diferentes processos, não é possível que atribuições de variáveis em ambos os lados sejam preservadas no restante do script. Dado unset a; a=b | a=c , a variável a pode acabar não definida ou igual a b ou igual a c ; não pode ser igual a ambos os b e c .

No shell Bourne, cada lado do operador de pipe é executado em um subshell (ou seja, em um processo de shell separado), portanto, as atribuições de variáveis não são preservadas. No shell Korn, o lado direito de um pipe é executado no processo shell original.

Como em muitos casos em que os comportamentos existentes diferem, o POSIX permite ambos os comportamentos. Conforme declarado na seção “Ambiente de Execução da Shell” :

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 will be executed in the current shell environment.

Some systems have implemented the last stage of a pipeline in the current environment so that commands such as:

command | read foo

set variable foo in the current environment. This extension is allowed, but not required; therefore, a shell programmer should consider a pipeline to be in a subshell environment, but not depend on it.

Na prática, comandos diferentes do último sempre são executados em um subshell, exceto algumas otimizações onde o comportamento é o mesmo, a menos que você observe atentamente o número de processos que são criados. Ou seja, nenhum shell tem unset a; a=b | true; echo $a print b .

A maioria dos outros tipos de Bourne, como cinza, traço, bash, pdksh e mksh, se comportam como o shell Bourne. Zsh se comporta como o shell Korn. Versões recentes do bash podem mudar para o comportamento do ksh com shopt -s lastpipe .

    
por 30.01.2017 / 01:50