4

I need to understand the behaviour of the following shell script.

#!/bin/bash
echo "First more"
more $1

echo "First echo"
echo $1

echo "Second more"
more $1

When I run the following command:

echo dir1 | bash script.sh

I get the following output

First more
dir1
First echo

Second more

Why does the echo and second more not get access to content of $1?

When I run the following command:

echo dir1 | bash script.sh dir2

I get the following command

First more

dir1

*** dir2: directory ***

First echo
dir2
Second more

*** dir2: directory ***

dir2 gets passed to all the commands, but more just prints dir1 but considers dir2 to be a directory.

2 Answers 2

7

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.

4
  • Thanks for the clarification. In my case both dir1 and dir2 are directories. Why is the parameter coming from pipe considered a string but the one passed as argument considered as directory and errors out?
    – Manoj
    Commented May 7, 2013 at 7:35
  • There is no parameter coming from a pipe. The standard input stream does not provide parameters. more pages through standard input without interpreting that if you give it something on standard input. It doesn't look at dir1 at all in either case, it just prints it out verbatim.
    – Mat
    Commented May 7, 2013 at 7:38
  • Now it is clear! Thank you! So I assume the only way to transfer the data from pipe into the script is to use echo dir1 | xargs bash script.sh.
    – Manoj
    Commented May 7, 2013 at 8:09
  • Depends on what you're trying to do exactly. You can use the read builtin to process data from standard input or other files too.
    – Mat
    Commented May 7, 2013 at 8:11
4

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
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .