It is currently Wed May 23, 2018 12:38 am

Reading files via stdin and still be interactive

All times are UTC - 6 hours

Post new topic Reply to topic  [ 6 posts ] 
Author Message
 PostPosted: Fri Apr 24, 2009 2:12 am   

Joined: Thu Apr 23, 2009 7:47 am
Posts: 5
Location: Belgium

I am writing a script that should be able to read a number of lines through stdin, so I could use it in pipes such as

find . -name "*.txt" | myscript

When the script gets input from stdin, it keeps on reading and storing the input until EOF is read, and only then does it start processing the input.
All this works perfectly, but now when I want to ask interactive input from the user (Such as "File xxx already exists; do you want to overwrite it? (y/n)") this does not work, since even though the input file or stream was terminated, the input still comes from the stream rather than from the terminal. So instead of waiting for an answer from the user, bash just enters an empty string from the terminated input stream.

Is there any way of telling the shell that from a certain point onwards I want the input to come from the terminal again, even though the user specified another input stream?


 PostPosted: Tue May 05, 2009 1:55 pm   
User avatar

Joined: Wed May 03, 2006 2:05 pm
Posts: 273
Hi Pieter!

Looks like it's been a while since you posted this, so I hope you have an answer by now, but could you do something like this?

find . -name "*.txt" | while read line ; do echo "$line" | myscript ; done

I'm not sure what "myscript" is, but you might even want to build the 'while read' loop into the script itself.

Hope this helps!

 Profile YIM  
 PostPosted: Wed May 06, 2009 8:12 am   

Joined: Thu Apr 23, 2009 7:47 am
Posts: 5
Location: Belgium
Hi jeo!

Actually I haven't found an answer yet...

I'm not sure if I fully understand what you mean, but I'll try to be a bit clearer about what I mean:

I won't bother to explain what exactly 'myscript' does since it's a rather long explanation and it doesn't really matter. The important thing is that it normally takes a number of filenames as arguments and then does stuff with these files, e.g. creating symbolic links to them.

So a call

myscript file1 file2 file3

would process the 3 files given and create links to them. However, under certain circumstances, the script will prompt the user whether or not to create a directory (in which the links will be created). So the script could ask a question like "Directory /foo/bar does not exist. Do you want to create it?" and then wait for input from the user, which it obviously reads from stdin. So far everything works fine.

Now I also want to be able to read filenames (not the files themselves) from the input stream instead of getting them as arguments, in order to create pipes like

find . -name "*.txt" | myscript

So if the result of "find . -name "*.txt"" are the files foo.txt and bar.txt, then the above call should be equivalent to

myscript foo.txt bar.txt

(I know that in this specific case I could also have writen "myscript *.txt", but you can imagine one would want to do more complex queries using find.)

The script (called 'myscript' here) succesfully reads the filenames from stdin using a while read loop. So it keeps on reading from the input stream until an EOF character is read and treats every line as a filename. The problem is that now I can't ask input from the user anymore, since the input stream now comes from the pipe and not from the terminal. So now, when the script asks "Do you want to create directory /foo/bar?", the user can't answer anymore, since the script is not reading from the terminal, but from the pipe, which is finished since an EOF was read before.

So my question is: How (if at all) can I tell a script to read from the terminal, even though it is used in a pipe? Or formulated another way: How can I first read input from a pipe until I reach the end of the pipe (an EOF), and then redirect the input stream to come from the terminal?

The while read loop you suggest would call 'myscript' for each output line of 'find' separately. This is not really what I want, and I don't think it solves the problem: if 'myscript' needs interactive input to process the information coming from the pipe, it still needs a way to get its input from the terminal again.

The following (stupid) script shows the problem:



while read -p"> " line; do
    words="$words$line "

echo -n "Are you sure you want to print the words you entered? (y/n) "
read answer

if [[ $answer != 'n' ]]; then
    echo "The words you entered are:"
    echo "$words"


If you run it from a terminal, everything works fine: you can keep entering words until you press Ctrl-D (which is EOF), then it asks you if you want to print the words, and unless you answer "n", the words are printed.

But if you call it using a pipe, e.g.

(echo "These"; echo "are"; echo "words") | myscript

where 'myscript' is the above script, the output is


Are you sure you want to print the words you entered? (y/n) The words you entered are:
These are words

You see that the words are still read correctly, but the script will print them without waiting for your answer. This is the problem I want to solve...

Thanks for your help!

 PostPosted: Wed May 06, 2009 10:52 am   

Joined: Mon Mar 02, 2009 3:03 am
Posts: 643
find . -type f -name "*.txt" -exec myscript '{}' \;

should give to your script each file found as an argument.

 PostPosted: Thu May 07, 2009 2:25 am   

Joined: Thu Apr 23, 2009 7:47 am
Posts: 5
Location: Belgium
Ah, that's good to know.

However I was hoping for a more general solution (to process output from any script, not just from find). But maybe that doesn't exist...


 PostPosted: Thu Sep 17, 2009 2:59 pm   

Joined: Thu Sep 17, 2009 2:57 pm
Posts: 1
Hello! I know it's been quite a while since you posted this and I'm sorry for bumping the thread, but here are my 2ยข:

If you take a look at the source code for the 'less' utility, you can see that it reads keyboard input in from stderr. Near line 80 on ttyin.c, you find:
     * Try /dev/tty.
     * If that doesn't work, use file descriptor 2,
     * which in Unix is usually attached to the screen,
     * but also usually lets you read from the keyboard.
#if OS2
    /* The __open() system call translates "/dev/tty" to "con". */
    tty = __open("/dev/tty", OPEN_READ);
    tty = open("/dev/tty", OPEN_READ);
    if (tty < 0)
        tty = 2;

I don't know if this is possible to incorporate inside a shell script, but it's how less does it. Try it out:
ls -l * | less

You'll see that 'less' is still responsive. Very crazy if you ask me, but you might look into this.

Good luck.

EDIT: Ha ha, I'm silly. Just read the input from /dev/tty. It'll automatically get bound to your local terminal's keyboard. (Do an ls -l /dev/tty if you don't believe me). Good luck!

Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC - 6 hours

Who is online

Users browsing this forum: No registered users and 12 guests

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  

BashScripts | Promote Your Page Too
Powered by phpBB © 2011 phpBB Group