9

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?

  • 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 Mar 24 '14 at 22:30
8

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.

  • I can't get any of these to work apart from the last one. – Graeme Mar 26 '14 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 Mar 26 '14 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 Mar 26 '14 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 Mar 26 '14 at 13:40
  • @Graeme - weird - I tried it with dash sh zsh and bash. All worked. – mikeserv Mar 26 '14 at 14:02
0

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
  • You can still run an interactive script, see my answer. Also, why not just sh confirmation.sh? – Graeme Mar 24 '14 at 23:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.