Search code examples
bashshellprocesssubstitution

Bash process substitution in string


I'm trying to add a switch to a script which will make a parameter on an internal command optional. Normally, I'd do this by either using a conditional to run the command with the parameter or without it, or if the parameter is simple, just create a string with the parameter in and substitute that. If the parameter is not needed then the string is just empty.

I have the added complexity that there's a dry run mode which just prints the command rather than running it so there's already one conditional. For this reason, I just wanted to create the internal parameter as a string and then substitute it either in the echo of the dry run, or the real command. However, the option is a little complex and looks like this:

<(echo $msg_enc | base64 -d -)

This takes a string encoded in base64, decodes it and presents the contents as a temporary file which can be read. I can't really change this structure easily, so I'm trying to work out how I can keep this in a string and evaluate it. To do this I created a shell script, call_just_print.sh and another tiny shell script to mimic the command, just_print.sh

call_just_print.sh

msg="hello world"
msg_enc="$(echo $msg | base64)"

dry_run=0
if [[ "${1}" == "dry-run" ]]; then
    dry_run=1
fi

enc_cmd=" <(echo \$msg_enc | base64 -d -)"
if [[ "${dry_run}" == "1" ]]; then
    echo "./just_print.sh ${enc_cmd}"
else
    ./just_print.sh "${enc_cmd}"
fi

This really just picks up a dry run hint from the command line and, if set, simply prints the command it's going to run, or if not set, will run the command.

just_print.sh

echo "[$(cat ${1})]"

This trivially receives a parameter which it assumes to be a file and displays it to the terminal. Ideally, the output would print [hello world] when run without dry-run mode. When I run it, I get the following:

$ ./call_just_print.sh dry-run
./just_print.sh  <(echo $msg_enc | base64 -d -)
$ ./call_just_print.sh
cat: invalid option -- 'd'
Try 'cat --help' for more information.
[]

The dry-run mode works fine but when used without the flag, the msg_enc var is passed verbatim to just_print.sh resulting in the following being run: cat '<(echo' '$msg_enc' '|' base64 -d '-)' which is absolutely not right.

I tried using eval in the call as follows:

./just_print.sh "$(eval ${enc_cmd})"

but I found that while this results in the correct evaluation of the variable resulting in creation of the temporary file, it then tries to execute it:

./call_just_print.sh: line 15: /dev/fd/63: Permission denied

Can anyone tell me where I'm going wrong?


Solution

  • Change

        ./just_print.sh "${enc_cmd}"
    

    to

        eval "./just_print.sh ${enc_cmd}"
    

    so it will evaluate the result of the variable expansion as shell code. Otherwise it's treated as a literal argument to just_print.sh.

    Be very careful when using eval -- the code to be executed should be generated by your script, not user-provided.