Kent
Kent

Reputation: 195059

Explanation needed for tee, process substitution, redirect...and different behaviors in Bash and Z shell ('zsh')

Recently in my work, I am facing an interesting problem regarding tee and process substitution.

Let's start with examples:

I have three little scripts:

$ head *.sh

File one.sh

#!/bin/bash
echo "one starts"
if [ -p /dev/stdin ]; then
    echo "$(cat /dev/stdin) from one"
else
    echo "no stdin"
fi

File two.sh

#!/bin/bash
echo "two starts"
if [ -p /dev/stdin ]; then
    echo "$(cat /dev/stdin) from two"
else
    echo "no stdin"
fi

File three.sh

#!/bin/bash
echo "three starts"
if [ -p /dev/stdin ]; then
    sed 's/^/stdin for three: /' /dev/stdin
else
    echo "no stdin"
fi

Now I am going to execute two commands:

1: echo "hello" | tee >(./one.sh) >(./two.sh) | ./three.sh

2: echo "hello" | tee >(./one.sh) >(./two.sh) >(./three.sh) >/dev/null

First in Bash and then in Z shell (zsh).

Bash (GNU bash, version 5.0.17(1))

$ echo "hello" | tee >(./one.sh) >(./two.sh) |./three.sh

three starts
stdin for three: hello
stdin for three: one starts
stdin for three: two starts
stdin for three: hello from two
stdin for three: hello from one

Now the other command:

$ echo "hello" | tee >(./one.sh) >(./two.sh) >(./three.sh) >/dev/null

one starts
two starts
three starts
stdin for three: hello
hello from two
hello from one
<---!!!note here I don't have prompt unless I press Enter or Ctrl-c)

Zsh (zsh 5.8 (x86_64-pc-linux-gnu))

Both commands,

echo "hello" | tee >(./one.sh) >(./two.sh) >(./three.sh) >/dev/null
echo "hello" | tee >(./one.sh) >(./two.sh) |./three.sh

Give the expected result:

one starts
three starts
two starts
hello from two
hello from one
stdin for three: hello

Upvotes: 1

Views: 1008

Answers (1)

Shawn
Shawn

Reputation: 52354

echo "hello"|tee >(./one.sh) >(./two.sh) |./three.sh

There are two possible order of operations for the tee part of the pipeline

First

  1. Redirect standard output to a pipe that's connected to ./three.sh's standard input.
  2. Set up the pipes and subprocesses for the command substitutions. They inherit the same redirected standard output pipe used by tee.
  3. Execute tee.

Second

  1. Set up the pipes and subprocesses for the the command substitutions. They share the same default standard output - to the terminal.
  2. Redirect tee's standard output to a pipe that's connected to ./three.sh's standard input. This redirection doesn't affect the pipes set up in step 1.
  3. Execute tee.

bash uses the first set of operations, zsh uses the second. In both cases, the order you see output from your shell scripts in is controlled by your OS's process scheduler and might as well be random. In the case where you redirect tee's standard output to /dev/null, they both seem to follow the second scenario and set up the subprocesses before the parent tee's redirection. This inconsistency on bash's part does seem unusual and a potential source of subtle bugs.

I can't replicate the missing prompt issue, but that's with bash 4.4.20 - I don't have 5 installed on this computer.

Upvotes: 1

Related Questions