Bash – How to read user input when using script in pipe

bashfile-descriptorsinteractivepiperead

General problem

I want to write a script that interacts with the user even though it is in the middle of a chain of pipes.

Concrete example

Concretely, it takes a file or stdin, displays lines (with line numbers), asks the user to input a selection or line numbers, and then prints the corresponding lines to stdout. Let's call this script selector. Then basically, I want to be able to do

grep abc foo | selector > myfile.tmp

If foo contains

blabcbla
foo abc bar
quux
xyzzy abc

then selector presents me (on the terminal, not in myfile.tmp!) with options

1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:

after which I type in

2-3

and end up with

foo abc bar
xyzzy abc

as contents of myfile.tmp.

I've got a selector script up and running, and basically it is working perfectly if I don't redirect input and output. So

selector foo

behaves like I want. However, when piping things together as in the above example, selector prints the presented options to myfile.tmp and tries to read a selection from the grepped input.

My approach

I've tried to use the -u flag of read, as in

exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "

but this doesn't do what I hoped it would.

Q: How do I get actual user interaction?

Best Answer

Using /proc/$PPID/fd/0 is unreliable: the parent of the selector process may not have the terminal as its input.

There is a standard path that always refers to the current process's terminal: /dev/tty.

nl "$INPUT" >/dev/tty
read -p"Select options: " </dev/tty

or

exec </dev/tty >/dev/tty
nl "$INPUT"
read -p"Select options: "
Related Question