Andrew Gilmartin
Andrew Gilmartin

Reputation: 1786

How to read data and read user response to each line of data both from stdin

Using bash I want to read over a list of lines and ask the user if the script should process each line as it is read. Since both the lines and the user's response come from stdin how does one coordinate the file handles? After much searching and trial & error I came up with the example

exec 4<&0
seq 1 10 | while read number
do
    read -u 4 -p "$number?" confirmation
    echo "$number $confirmation"
done

Here we are using exec to reopen stdin on file handle 4, reading the sequence of numbers from the piped stdin, and getting the user's response on file handle 4. This seems like too much work. Is this the correct way of solving this problem? If not, what is the better way? Thanks.

Upvotes: 3

Views: 1029

Answers (3)

Yes, using an additional file descriptor is a right way to solve this problem. Pipes can only connect one command's standard output (file descriptor 1) to another command's standard input (file descriptor 1). So when you're parsing the output of a command, if you need to obtain input from some other source, that other source has to be given by a file name or a file descriptor.

I would write this a little differently, making the redirection local to the loop, but it isn't a big deal:

seq 1 10 | while read number
do
    read -u 4 -p "$number?" confirmation
    echo "$number $confirmation"
done 4<&0

With a shell other than bash, in the absence of a -u option to read, you can use a redirection:

printf "%s? " "$number"; read confirmation <&4

You may be interested in other examples of using file descriptor reassignment.

Another method, as pointed out by chepner, is to read from a named file, namely /dev/tty, which is the terminal that the program is running in. This makes for a simpler script but has the drawback that you can't easily feed confirmation data to the script manually.

Upvotes: 1

Norman Ramsey
Norman Ramsey

Reputation: 202705

For your application, killmatching, two passes is totally the right way to go.

  • In the first pass you can read all the matching processes into an array. The number will be small (dozens typically, tens of thousands at most) so there are no efficiency issues. The code will look something like

    set -A candidates
    ps | grep | while read thing do candidates+=("$thing"); done
    

    (Syntactic details may be wrong; my bash is rusty.)

  • The second pass will loop through the candidates array and do the interaction.

Also, if it's available on your platform, you might want to look into pgrep. It's not ideal, but it may save you a few forks, which cost more than all the array lookups in the world.

Upvotes: 0

chepner
chepner

Reputation: 532333

You could just force read to take its input from the terminal, instead of the more abstract standard input:

while read number
do
    < /dev/tty read -p "$number?" confirmation
    echo "$number $confirmation"
done

The drawback is that you can't automate acceptance (by reading from a pipe connected to yes, for example).

Upvotes: 2

Related Questions