Process substitution with heredoc works in zsh. How to make it work in bash?

116 Views Asked by At

I want the ability to pass a heredoc to the source command for local interpretation. The body of the heredoc document will be injected later before calling the script, and can be multiline.

I came up with this trick, which works in zsh:

source <(<<'heredoc'
echo foo
sleep 10
echo done
heredoc
) && echo ok || echo no

it prints foo, sleeps 10 seconds, then prints done, then ok. So far, so good.

Now, the script needs to run in bash. When I feed bash (or sh) the exact same thing, I only get ok, with no wait at all, as if the command I was providing with the heredoc was just true.

I cannot understand why, and how I can build the same behavior in bash and sh.

3

There are 3 best solutions below

1
KamilCuk On BEST ANSWER

How to make it work in bash?

Remove any here documents and execute the commands. You could preserve the group.

{
   echo foo
   sleep 10
   echo done
} && echo ok || echo no

You can use, both in zsh and bash, source /dev/stdin if you want to execute commands in the current execution environment coming from a steam, like a here document. No need for <( process substitution in either case. So here you go:

source /dev/stdin <<heredoc
echo foo
sleep 10
echo done
heredoc

Note: this looks very odd thing to do and most probably there are better solutions to what you are doing.

why

Because in bash empty redirection like a here document without a command does nothing. You have to for example cat the content of the stream created by here document to output it to standard output for process substation to capture it.

2
kojiro On

Redirections in bash aren't processes, so it doesn't make sense to use a process substitution to capture them. But you can certainly do it if you use a process to capture the redirection. cat does that.

The following works in bash 4 and up:

source <(cat <<'heredoc'
echo foo
sleep 10
echo done
heredoc
) && echo ok || echo no

You cannot source a process substitution in bash 3.

1
amphetamachine On

You could use eval ("evil"); in this context, it's likely not a security issue.

eval '
echo foo
sleep 10
echo done
' && echo ok || echo no

You could also just, you know, not use dynamic code execution:

{
    echo foo
    sleep 10
    echo done
} && echo ok || echo no