11

Let's assume I have a file named confirmation.sh with the following content:

#!/bin/bash
echo -n "Are you sure [Y/n]? "
read line
case "$line" in
    n|N) echo "smth"
        ;;
    y|Y) echo "smth"
        ;;
esac

and I want to run this script in the following way:

cat confirmation.sh | sh

I see Are you sure [Y/n]? and the script is interrupted. What's the problem?

1
  • 2
    You have /bin/bash in the bang line, yet you use a .sh extension and try to pipe the script to sh. Not a problem since the code you have is compatible with both, but worth pointing out.
    – Graeme
    Commented Mar 24, 2014 at 22:30

4 Answers 4

13

As others have said, this is because the stdin of sh has been redirected to read from the pipe, it is not connected to the terminal as it would normally be. One thing you can do to get around this is to use /dev/tty to force the script to read from the terminal. Eg:

#!/bin/sh

read -p "Are you sure [Y/n]?" line </dev/tty
case "$line" in
  y|Y) echo "confirmed"
    ;;
  *) echo "not confirmed"
    ;;
esac

Normally you would only do this if you specifically want to prevent people from scripting the input, eg:

echo Y | sh confirmation.sh

This would still read from the terminal even though the user might expect this to automatically input Y at the prompt. It is common for programs that expect a password to do this.

2
sh 3<<CONFIRM /dev/fd/3
    $(cat ./confirmation.sh)
CONFIRM

sh 3<./confirmation.sh /dev/fd/3

note: thanks to @Graeme for correcting me on the above two examples...

Its a lot easier to do if you keep stdin clear.

2<./confirmation.sh . /dev/stderr

Or, since a terminal's 0 1 2 are all the same file, just add:

read line <&2

And your

cat ./confirmation.sh | sh

Works just fine.

5
  • I can't get any of these to work apart from the last one.
    – Graeme
    Commented Mar 26, 2014 at 11:48
  • Weird, they all worked for me... I'm in the middle of something, but in... 10 or so minutes i can test again. In the meantime... BOLD YOUR COMMENT maybe? I dont like disseminating misinformation.
    – mikeserv
    Commented Mar 26, 2014 at 11:50
  • @Graeme - not sure why I missed it the first time - could have sworn I tested them all at the same time - but the first two do need a change to operate correctly. Thank you.
    – mikeserv
    Commented Mar 26, 2014 at 12:41
  • Looks good, It still doesn't read from the terminal when I source from stderr though. I probably should though, I don't understand why.
    – Graeme
    Commented Mar 26, 2014 at 13:40
  • @Graeme - weird - I tried it with dash sh zsh and bash. All worked.
    – mikeserv
    Commented Mar 26, 2014 at 14:02
1

This has worked for a single argument being piped to my script:

if [ -p /dev/stdin ]; then set -- "$( cat )"; fi

I can then access the piped data in a positional argument ($1), which is compatible with my other script workings.


From info test:

[ -p /dev/stdin ]: -p FILE is True if FILE exists and is a named pipe.

-1

The short answer is you can't. The pipe redirects stdout to stdin, so therefore you cannot run an interactive script, as you have already redirected the output from the first command as the input to the second command in the pipe statement.

You might be looking to do something like this:

cat confirmation.sh > ask.sh && sh ask.sh
1
  • You can still run an interactive script, see my answer. Also, why not just sh confirmation.sh?
    – Graeme
    Commented Mar 24, 2014 at 23:11

You must log in to answer this question.

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