Clarification regarding behavior of shell script along with pipe

You're confusing arguments and standard input. Piping data to a program is not equivalent to giving it command line arguments.

In your first case, you are passing no arguments to your script, only feeding it data through its standard input stream. So $1 is unset for the whole duration of the script.
The first invocation of more thus has no parameter, and pages the standard input. This displays what you had piped in there (dir1, as text). The subsequent echo only prints a new line since it doesn't get anything to print, and the last more has nothing left to print either - standard input has been "drained" by the first one.

In the second case, you do pass an argument. So $1 has the value dir2 in the script. The same thing happens, except that the first more both:

  • pages through both standard input
  • attempts to page the file dir2 and errors out since that's a directory

The echo does what is expected given that $1 contains dir2, and the last more only errors on dir2 - it has nothing to read from standard input.


The difference is in "Arguments" VS "Standard Input".

When you run echo dir1 | bash script.sh, the $1 argument in your script.sh is always empty as no argument is given to it (try adding a set -x at the begin and you will see it in the debug output). The dir1 which is echoed comes from standard input as the more command read stdin if no argument is given (remember $1 is empty).

How cmd1 | cmd2 works

When using pipe :

  1. cmd2 is a subprocess of cmd1.
  2. the stdin of cmd2 is "plugged" on the stdout of cmd1.

As linux stdio lib offered a buffered stream through file descriptor, the stdin content will be consumed (i.e. read only once) only when stdin will be opened.

Step by step cmd1 | cmd2 workflow

Example command :

echo dir1 | (echo "a" ; read stdinvalue; echo "$stdinvalue")

  1. echo dir1 | : write "dir1\n" on stdout of the first command which is not echoed but buffered through stdio and available to subprocess via stdin.
  2. echo "a" : write "a\n" on stdout ; doesn't read stdin ! so the "dir1\n" string is still available
  3. read stdinvalue : read stdin until EOL (or EOF) and stores the string in a bash variable
  4. echo "$stdinvalue" : write stdinvalue variable value to stdout