================================== Reading stdin into shell variables ================================== -IAN! idallen@idallen.ca Many shell scripts need to obtain information from the keyboard or standard input. You might need to prompt the user to enter a file name, or to answer yes/no to a question. The built-in command that reads stdin in the Bourne shells is named "read". The "read" command reads one line of standard input, splits it on blanks (and only on blanks - quotes and other meta-characters are not special) and stores it into one or more shell variables named on the same command line: $ read a b c one two three four five # this is typed by the user $ echo "$a" one $ echo "$b" two $ echo "$c" three four five The first blank-separated word goes into the first named variable, the second into the second named variable, etc. for the first N-1 variable names. All the words that are left over go into the Nth and last variable, including any embedded blanks between those left over words. $ read a b c "one two" * four # this is typed by the user $ echo "$a" "one $ echo "$b" two" $ echo "$c" * four The words going into the first N-1 named variables have no blanks in or around them at all. Quotes and GLOB meta-characters have no meaning; they are just ordinary characters. Blanks can only appear between the left-over words put into the last named variable. If you give only one named variable to "read", the entire line of standard input is copied into the one variable unchanged, including all the embedded blanks: $ read x "one two" * four # this is typed by the user $ echo "$x" "one two" * four Note that the arguments to the "read" command are variable names, e.g. "foo", not expansions of variables, e.g. "$foo". We don't want the shell to expand the variable "$foo" before running the "read" command; we want the "read" command to get the name of the variable, "foo", directly. Don't use dollar signs on the "read" command line variable names! If there are not enough words to fill all the named variables on the "read" command line, the extra variables are set to be empty (null string): $ read a b c one # this is typed by the user $ echo "/$a/$b/$c/" /one/// Entering a blank line in response to a "read" will set *all* the named variables to be empty: $ read a b c # user enters a blank line $ echo "/$a/$b/$c/" //// --------------------- Return status and EOF --------------------- The "read" command, like all shell commands, has a return status. It returns 0 (good) if the read succeeds. (The read succeeds even if you enter a blank line!) It returns 1 (failed) only on end-of-file (EOF). The EOF signal means "read" can be used to read many lines of standard input from a file: #!/bin/sh -u n=0 while read line ; do let n=n+1 echo "Line $n contains $line" done The above loop can be placed into a shell script whose input is coming from a file, e.g. "./script tmp $ read a b c &2 "$0: Enter number of seconds to sleep:" read seconds echo "You entered '$seconds' seconds; sleeping ..." sleep $seconds Without the prompt to tell the user what input is needed, the user of the script has no idea what is expected. As with error messages, prompts must be sent to standard error, not to standard output. Prompts sent to stdout disappear when output is redirected, and the user again has no idea what is wrong! Prompts on stderr are visible even with redirection: $ ./script >out ./script: Enter number of seconds to sleep: 2 $ $ cat out You entered '2' seconds; sleeping ... ----------------------------------------- Prompting only if reading from a keyboard [OPTIONAL] ----------------------------------------- Prompts are only useful if standard input is coming from a keyboard, where the user is expected to type something. If a script is reading standard input from a pipe or a file, no prompt is needed. In fact, the prompt is incorrect and misleading: $ echo 2 | ./script ./script: Enter number of seconds to sleep: You entered '2' seconds; sleeping ... The prompt appears even though standard input will come from the pipe and the user gets no chance to enter anything from the keyboard. The "tty" command is useful to determine if standard input is coming from a terminal. It prints the terminal device pathname and returns true (zero) if standard input is connected to a terminal device (i.e. to your keyboard): $ tty /dev/pts/1 $ echo $? 0 $ echo hi | tty not a tty $ echo $? 1 $ tty &2 "$0: Enter number of seconds to sleep:" fi read seconds echo "You entered '$seconds' seconds; sleeping ..." sleep $seconds Now the prompt appears only if the user needs to type something on the keyboard: $ ./script ./script: Enter number of seconds to sleep: 2 You entered '2' seconds; sleeping ... $ echo 3 | ./script You entered '3' seconds; sleeping ...