Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"printf -v" inside function not working with redirected output

Tags:

linux

bash

unix

With bash 4.1.2 and 4.3.48, the following script gives the expected output:

#!/bin/bash

returnSimple() {
   local  __resultvar=$1
   printf -v "$__resultvar" '%s' "ERROR"
   echo "Hello World"
}

returnSimple theResult
echo ${theResult}
echo Done.

Output as expected:

$ ./returnSimple
Hello World
ERROR
Done.

However, when stdout from the function is piped to another process, the assignment of the __resultvar variable does not work anymore:

#!/bin/bash

returnSimple() {
   local  __resultvar=$1
   printf -v "$__resultvar" '%s' "ERROR"
   echo "Hello World"
}

returnSimple theResult | cat
echo ${theResult}
echo Done.

Unexpected Output:

$ ./returnSimple
Hello World

Done.

Why does printf -v not work in the second case? Should printf -v not write the value into the result variable independent of whether the output of the function is piped to another process?

like image 278
Andreas Fester Avatar asked Oct 12 '17 13:10

Andreas Fester


2 Answers

See man bash, section on Pipelines:

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

That's why when you write cmd | cat, cmd receives a copy of variable that it can't modify.

A simple demo:

$ test() ((a++))
$ echo $a

$ test
$ echo $a
1
$ test | cat
$ echo $a
1
like image 101
randomir Avatar answered Oct 30 '22 11:10

randomir


Interestingly enough, the same also happens when using eval $__resultvar="'ERROR'" instead of the printf -v statement. Thus, this is not a printf related issue.

Instead, adding a echo $BASH_SUBSHELL to both the main script and the function shows that the shell spawns a sub shell in the second case - since it needs to pipe the output from the function to another process. Hence the function runs in a sub shell:

#!/bin/bash

returnSimple() {
    local  __resultvar=$1
    echo "Sub shell level: $BASH_SUBSHELL"
    printf -v "$__resultvar" '%s' "ERROR"
}

echo "Sub shell level: $BASH_SUBSHELL"
returnSimple theResult | cat
echo ${theResult}
echo Done.

Output:

% ./returnSimple.sh
Sub shell level: 0
Sub shell level: 1

Done.

This is the reason why any variable assignments from within the function are not passed back to the calling script.

like image 32
Andreas Fester Avatar answered Oct 30 '22 12:10

Andreas Fester