4

I'm trying to run a script remotely and use its standard output to populate a variable. I'm doing this to avoid temporary files.

Here's the pattern I'm trying:

var=$(bash <(curl -fsSkL http://remote/file.sh))
echo "var=${var}"

I'm testing this pattern without curl using cat:

var=$(bash <(cat ./local/file.sh))
echo "var=${var}"

This should be the same as far as syntax is concerned. ./local/file.sh contains echo hello, so I would expect var to contain the value hello, but alas, executing the above results in the following:

test.sh: command substitution: line 4: syntax error near unexpected token `('
test.sh: command substitution: line 4: `bash <(cat ./local/file.sh)'
var=

How can I accomplish my goal without using temporary files?

1 Answer 1

13

Those are the errors you get when trying to perform a process substitution in bash when the shell is running in POSIX mode. The bash shell does not support process substitutions in POSIX mode.

bash will run in POSIX mode when either

  1. set -o posix has been used, or
  2. the shell is being invoked as sh.

My hunch is that you have a script, test.sh, that you are running with sh test.sh or that has a #!/bin/sh hashbang line, and that your sh happens to be bash. Another possibility is that the script does not have #!-line at all, and it is being invoked by bash-as-sh in some other way.

Instead, see to that your test.sh script is being invoked by bash.

Example:

$ cat script.sh
echo hello
$ cat test.sh
var=$(bash <( cat script.sh ))
printf 'var="%s"\n' "$var"
$ bash -o posix test.sh
test.sh: command substitution: line 2: syntax error near unexpected token `('
test.sh: command substitution: line 2: `bash <( cat script.sh ))'
var=""
$ bash test.sh
var="hello"

If you want to do this in a portable way in your test.sh script using POSIX sh:

var=$( cat script.sh | bash )
printf 'var="%s"\n' "$var"

This would have the effect of the bash process reading the output of cat on its standard input, which is not quite the same as your process substitution variant (which leaves standard input alone). This matters if the script that the internal bash shell executes does any form of reading of data from its standard input.

If you need to leave the bash shell's standard input alone (because you need to read from it), you may possibly assume that mktemp is available and use

var=$( tmpfile=$(mktemp); cat script.sh >"$tmpfile" && bash "$tmpfile"; rm -f "$tmpfile" )
printf 'var="%s"\n' "$var"

Where cat script.sh is understood to later be replaced by your curl command.

If you're comfortable with juggling file descriptors, you could also make file descriptor 3 a copy of the standard input descriptor before calling the bash shell, and then let the fetched script read from that:

var=$( exec 3<&0; cat script.sh | bash )
printf 'var="%s"\n' "$var"

The script.sh script would then use read -u 3 to read from the original standard input stream, or utility <&3 to redirect that input stream into another utility.

6
  • Yep, I was invoking as sh – I didn't realize POSIX didn't support these features. Thanks! As a short follow-up, is there a way to ensure bash-as-sh (if invoked incorrectly) will not run in POSIX mode? In other words, what is the converse of set -o posix? Feb 23, 2019 at 23:43
  • @SeanAllred Since sh does not need to be bash and indeed, sh is often not bash, you should rather make sure that your scripts are executed by bash. A simple way to do this is to make the script executable and add #!/bin/bash as the first line in the script (adapt that to the correct path to the bash executable on your system). Then run it without specifying an explicit interpreter.
    – Kusalananda
    Feb 23, 2019 at 23:46
  • Yes I can ensure that my tools use bash, via shebang or otherwise, but I'm concerned that those using the script I'm making will not invoke it correctly. Is there no converse to option posix? (Working under the assumption now that there's no means by which to accomplish my goal – without temp files – with only POSIX features.) Feb 23, 2019 at 23:50
  • 1
    @SeanAllred If you are sharing your script with others, it extra important to have a #!/bin/bash line! The sh shell on many Linux machines is dash, and that shell would not understand process substitutions however hard you tried. On my machine (OpenBSD), sh is pdksh, and that also can't be made to run process substitutions. What you are asking is the equivalent to how to make someone invoke a Python script with the correct Python interpreter even when they run it with a Ruby interpreter. If the user picks the wrong interpreter, then the documentation was not good enough.
    – Kusalananda
    Feb 23, 2019 at 23:53
  • Well of course there will be a shebang line :-) but fair enough – I guess there's a limit to how much you can protect others from themselves. Thanks for your help! Feb 23, 2019 at 23:54

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.