sections in this module | City College of San Francisco - CS160B Unix/Linux Shell Scripting Module: Functions |
module list |
read reads one line from standard input. If the read command has variables attached, the line's contents are distributed between the variables. The first word is placed in the first variable, the second is placed in the second variable, etc, and the last variable will have the rest of the input line. Thus, if the line
Today is Tuesday
is read using the command
read a b
the contents of the variables a and b are
You can, of course, read the entire line into a single variable. Using the same input line with
read line
results in
The line is read in, and any leading and/or trailing whitespace (spaces and tabs) is removed before it is placed in the variable(s).
Special characters
The only special character that read understands is a backslash. It can be used to escape (quote) any character, but the only characters that are affected are whitespace and a newline. If you issue the command
read line
with the input
results in
(Note the spaces before the backslash (\). This is so that a space appears between the words when the physical lines are joined together.)
The backslash can also be used to control how text is distributed between the variables. In the input line
Greg Boyd Teacher
the command
read name job
results in
If you use a backslash to escape the space between Greg and Boyd, however, the input
Greg\ Boyd Teacher
with the same command
read name job
results in
$ echo "$name"
Greg Boyd
$ echo "$job"
Teacher
$
Avoid pipes
You might be tempted to try something cute like this:
date | read wday mon day time other
and expect read to extract the pieces of the output of date, distributing them between the variables. Indeed it does, but the pipe forces read to run in a separate process from your shell script, so the variables read sets are not yours!
$ date
Tue Mar 16 17:29:34 PDT 2010
$ date | read wday mon day rest
$ echo $wday
$
Because of the pipe, the read command runs in a separate process. The variables wday, mon, day and rest are set for that process, but not for the process running your shell script! Your shell script does not have the variables wday, mon, day and rest. You can see this by grepping for the variable in the output of set:
$ set | grep wday
$
We will see this same situation later when we cover loops. The only solution is to avoid using the pipe. A simple solution is to save the data you want in a file, then use read to read from the file:
$ date > $$date
$ read wday mon day rest < $$date
$ echo $wday
Tue
$ rm $$date
$
but this is more clumsy than using the set -- command with $(date)
Note: the issue with pipes starting a child shell even to run a shell built-in command like read occurs in the bash shell. Other shells may be less restrictive. Using redirection from a file works always, however.
Prompt and read
One important use of read is to write interactive shell scripts - shell scripts that ask the user a question and retrieve some information from them. For example, the program chfn is used to change the finger information of the user. This information is kept in the outdated GECOS field of /etc/passwd and contains subfields for the user's real name, phone number, office number, etc. After retrieving the current information from /etc/passwd, chfn asks the user to update each field. Running chfn looks like the following:
-bash$ chfn
Default values are printed inside of '[]'.
To accept the default, type <return>.
To have a blank entry, type the word 'none'.
Name [Greg Boyd]:
Location (Ex: 42U-J4) []:L462
Office Phone (Ex: 1632) []:use email
Home Phone (Ex: 9875432) []:none
-bash$
After outputting some instructions, chfn asks a series of four questions. Each question consists of a prompt, which contains the default response, and a user response. The prompt and user response occur on the same line. If chfn was a shell script and the current value of the user's Name was kept in the variable $Name, you could code the first question like this:
This is the basis of writing an interactive shell script, called prompt and read. The user is asked a question, then the response is retrieved using read.
In the example above, when we retrieve the user's name, the question is output to standard output. Often, shell scripts want to send their results to standard output as well. This is not a problem unless a user wants to save the shell script's output. Let's look at a simple example of this.
The shell script uinfo below simply asks the user for a userid, then outputs information about that user:
#!/bin/bashWhen we run uinfo, everything is fine:
However, if we decide we want to save the output of uinfo in a file, we have a problem:
The problem is due to using standard output for both the prompt and the shell script's output. If we just continue as if we could see the prompts, uinfo works fine:
but this is not practical. To make matters worse, our output file has been polluted with the prompt! Instead, we need a way to get our prompt to appear on the screen, while reserving standard output for actual output.
For this reason, it is customary to use standard error when outputting prompts. Let's rewrite our program with this modification:
#!/bin/bash
echo -n "Enter the username:" >&2
read user
if line=$(grep "^$user:" /etc/passwd); then
Now the user can run our program, redirect its output to a file and still interact with it:
-bash$ uinfo > uinfo.out
Enter the username:gboyd
-bash$ cat uinfo.out
Home directory: /users/gboyd
Shell: /usr/bin/bash
-bash$
read -p
The read command has an option to output a prompt prior to reading the input line. This frees you from writing a separate echo command. The prompt is even output to standard error for you, and assumes that the prompt and input should occur on the same line:
using echo and read | using read -p |
echo -n "Enter the filename:" >&2 read filename | read -p "Enter filename:" filename |
IFS (Internal Field Separators)
IFS is a variable that contains delimiters. When IFS is used to break text into tokens, each character in IFS is treated as a delimiter, and each is equivalent. The default contents of IFS is a space, a tab, and a newline.
IFS is used when a line is read from standard input using the read command. The text read is distributed between the variables attached to the read command using the delimiters in IFS. Normally this means that whitespace is used to delimit the tokens on the input line:
You can control this process by manipulating the delimiters in IFS. Suppose we are reading a line from a CSV file, where fields are delimited by commas. An example of a line might be
$ cat tmp
Greg Boyd,66 First St.,San Francisco,CA,94103,4155551212,Unix Instructor
$
If we set IFS to only contain the comma, we can chop this record apart easily. (As we shall see, IFS affects other things besides the read command, so it is customary to restore the default value after you are finished.)
This is a bit cumbersome, but, in some cases, it is very useful.
IFS is also used when substitutions are performed in every Unix command. Returning to our command execution for a moment, the shell processes commands in many steps. Briefly,
Using the same example as above, start by setting IFS to a comma:
$ echo "$line"
Greg Boyd,66 First St.,San Francisco,CA,94103,4155551212,Unix Instructor
$ OIFS="$IFS"
$ IFS=,
Now you can use the contents of the variable line to set the positional parameters using the command set --
$ set -- $line
$ echo $#
7
$ echo "$7"
Unix Instructor
$
Notes:
Preview question: |
Prev | This page was made entirely with free software on linux: Kompozer and Openoffice.org |
Next |