3

There is simple test case

local testa=("a")
local testb=("b")

function test { testb=(${(P)1}) }

test "testa"
echo "testb="$testb

output testb=a and

local testa=("a")
local testb=("b")

function test { testb=(${(P)1}) }

test "testa" | cat
echo "testb="$testb

output is testb=b

I assume that output must be testb=a too.

What is wrong?

1

The pipe introduces a subshell in which variable changes are masked from the parent; this is similar to while loops involving pipes where variable changes inside that loop do not affect the parent. The PID change can be observed with the zsh/system module:

zmodload zsh/system

local testa=("a")
local testb=("b")

function test {
    testb=(${(P)1})
    echo "INSIDE testb=$testb $sysparams[pid]"
}

test "testa" | cat
echo "OUTSIDE testb=$testb $sysparams[pid]"

Which should show something like

% zsh foo.zsh
INSIDE testb=a 61488
OUTSIDE testb=b 61487
% 

different subshell, different variables.

1

The two sides of the pipe operator run in parallel. This is done by running the two sides in separate processes. (It was the only way to do it in the original Unix shell, and it's still the only way to do it in modern shells.) Consider a snippet like

testb=left | testb=right
echo $testb

In zsh, the right-hand side of a pipe operator runs in the original process, and the left-hand side runs in a subshell. A subshell is normally implemented through a separate process; even if the shell optimizes the execution and doesn't use a separate process, it ensures that any modification of the shell state (variables, redirection, etc.) are confined to the subshell, for consistency. Thus the snippet above prints right.

(Other shells may run the right-hand side of the pipe in a subshell as well. Amongst the common shells, only zsh and ATT ksh run the right-hand side in the original context. The left-hand side always runs in a subshell.)

If you want to pipe data without creating a subshell, you can use a process substitution. A process substitution creates a pipe in a subshell and gives control over whether the original context is connected to the the input side or the output side of the pipe.

test "testa" > >(cat)

(Note the space between > and >(…): >>(cat) would be parsed as a process substitution in append mode, which would pass a path to the pipe as a second argument to test. Here we want to redirect the output of the function call to the pipe.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.