Search code examples
linuxpipezshprocess-substitution

mixing process substitution and pipes in zsh


Using ZSH, I am trying to wrap a sed command into a function, then use it, while mixing pipes with process substution. Let me explain with an example:

$ echo "test text" | gzip > myfile.gz
$ sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g' <(zcat myfile.gz) | more
test text
$ ncat() { sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g' $@; }
$ zcat myfile.gz | ncat | more
test text
$ ncat <(zcat myfile.gz) | more
sed: can't read /proc/self/fd/13: No such file or directory

As you can see, in those 2 out of those 3 usages work. The last one is the one that intrigues me here. (Note that all commands work with bash)

Can you explain why the output of a function that uses process substitution in input, is not usable through a pipe?

I am not looking for a workaround to just make my example work. I am looking for an explanation, because I couldn't find one.

FYI sed is not relevant here, I tried with multiple other commands (echo, cat...), and got the same outcome


Solution

  • When you pipe the ncat function's output, it has to be run in a subshell. For some reason, the FD connected to the process substitution pipe is not being inherited by this subshell. Since it uses /proc/self/fd, if the FD isn't open in the child process, the attempt to read from it fails.

    It seems like a bug to me, since a function should work similarly to external functions in this regard.

    This isn't Linux-specific, I reproduced the problem on MacOS running zsh 5.3.

    It only seems to happen when the subshell is created for a pipe. To see when a subprocess is created and whether the FD is inherited, I changed the function to:

    ncat() { echo $$;sh -c 'echo $PPID';echo "$@";ls /dev/fd; }
    

    (/dev/fd is the Mac equivalent of /proc/self/fd.)

    If I run

    (ncat <(gzcat myfile.gz )) 
    

    The parentheses create a subshell, but the FD associated with the process substitution is shown in the ls /dev/fd output.