Bash – Environment Variables Not Set in Pipeline Function

bashenvironment-variablesio-redirectionpipeshell

I have the following recursive function to set environment variables:

function par_set {
  PAR=$1
  VAL=$2
  if [ "" != "$1" ]
  then
    export ${PAR}=${VAL}
    echo ${PAR}=${VAL}
    shift
    shift
    par_set $*
  fi
}

If I call it by itself, it both sets the variable and echoes to stdout:

$ par_set FN WORKS
FN=WORKS
$ echo "FN = "$FN
FN = WORKS

Redirecting stdout to a file also works:

$ par_set REDIR WORKS > out
cat out
REDIR=WORKS
$ echo "REDIR = "$REDIR
REDIR = WORKS

But, if I pipe stdout to another command, the variable doesn't get set:

$ par_set PIPE FAILS |sed -e's/FAILS/BARFS/'
PIPE=BARFS
$ echo "PIPE = "$PIPE
PIPE =

Why does the pipe prevent the function from exporting the variable? Is there a way to fix this without resorting to temp files or named pipes?

Solved:

Working code thanks to Gilles:

par_set $(echo $*|tr '=' ' ') > >(sed -e's/^/  /' >> ${LOG})

This allows the script to be called thusly:

$ . ./script.sh PROCESS_SUB ROCKS PIPELINES=NOGOOD
$ echo $PROCESS_SUB
ROCKS
$ echo $PIPELINES
NOGOOD
$ cat log
7:20140606155622162731431:script.sh:29581:Parse Command Line parameters.  Params must be in matched pairs separated by one or more '=' or ' '.
  PROCESS_SUB=ROCKS
  PIPELINES=NOGOOD

Project hosted on bitbucket https://bitbucket.org/adalby/monitor-bash if interested in full code.

Best Answer

Each part of a pipeline (i.e. each side of the pipe) runs in a separate process (called a subshell, when a shell forks a subprocess to run part of the script). In par_set PIPE FAILS |sed -e's/FAILS/BARFS/', the PIPE variable is set in the subprocess that executes the left-hand side of the pipe. This change is not reflected in the parent process (environment variables do not transfer between processes, they are only inherited by subprocesses.

The left-hand side of a pipe always runs in a subshell. Some shells (ATT ksh, zsh) run the right-hand side in the parent shells; most also run the right-hand side in a subshell.

If you want to both redirect the output of a part of the script and run that part in the parent shell, in ksh/bash/zsh, you can use process substitution.

par_set PROCESS SUBSTITUTION > >(sed s/ION/ED/)

With any POSIX shell, you can redirect the output to a named pipe.

mkfifo f
<f grep NAMED= &
par_set NAMED PIPE >f

Oh, and you're missing quotes around variable substitutions, your code breaks on things like par_set name 'value with spaces' star '*'.

export "${PAR}=${VAL}"
…
par_set "$@"
Related Question