4

Consider the following shell script

echo foo; read; echo bar

Running bash my_script outputs 'foo', waits for the return key and outputs 'bar'.

While this works fine running it that way, it doesn't work anymore if piped to /bin/bash:

$ echo 'echo foo;read;echo bar'|bash

directly outputs 'foo' and 'bar' without waiting for a key press.

Why doesn't read work anymore when using it this way?

Is there any way to rewrite the script in a way it works as file script file as well as a script string piped to /bin/bash?

3 Answers 3

3

This is really easy, actually, First, you need to set aside your stdin in some remembered descriptor:

exec 9<&0

There. You've made a copy. Now, let's pipe our commands at our shell.

echo 'echo foo; read <&9; echo bar' | bash

...well, that was easy. Of course, we're not really done yet. We should clean up.

exec 9<&-

Ok, now we're done.

But we can avoid the cleanup if we just group our commands a little...

{ echo 'echo foo; read <&9; echo bar' | bash; } 9<&0

The descriptor only survives as long as its assigned compound command does in that case.

1
  • Great. I never got the syntax to duplicate stdin.
    – ikrabbe
    Jul 9, 2015 at 7:37
3

The bash builtin read reads from stdin (fd0), but it cannot open stdin as the bash command itself already consumes stdin.

You can eval what you pipe

eval 'echo foo;read;echo bar'

Was: I can't think of another way to pipe a shell script that itself needs to read from stdin. When you pipe into a shell, that shell is not interactive.

I never got it how to duplicate the stdin file descriptor. See mikes answer for this.

BTW.: You can see what happens with the read with

echo 'echo foo;read;echo bar'|strace bash
1

ikrabbe's answer correctly identifies why your command line doesn't work the way you expect it to.  The conventional way to invoke a new shell for one command line is:

$ sh -c 'echo foo; read; echo bar'

Of course you can use bash if you need to, but 98% of what you might want to do in a one-line shell script can be done in the plain old shell.  (Pay no attention to the fact that "plain old shell" forms the acronym "POS".)

If your objective is to get your one-line shell script to pause, waiting for user input (possibly just acknowledgement/confirmation), then that's probably what you should do.  If your objective is to better understand the workings of the shell, try this:

$ (echo 'echo foo; read; echo "$REPLY bar"'; echo candy) | sh
foo
candy bar                               (without waiting)
$

You must log in to answer this question.

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