1. Introduction
When we create Bash scripts, it can be handy to take user input. In this tutorial, we’ll take a look at how to do this with the read command.
The Bash read command is a powerful built-in utility used for word segmentation of strings under Linux.
Since it is a built-in command, as long as we have Bash available there is no need for additional setup steps.
2. Basic Syntax
Let’s explore the basic syntax including all optional parameters:
$ read [options] [name...]
The options parameters influence how the read command interacts with the input. We will explore these in the upcoming sections.
The name parameter specifies in which variable(s) to store the actual words resulting from the split operation.
2.1. Default Behavior
A no-argument call of read fetches a single line from the standard input stream and assigns it to the REPLY built-in variable:
$ read
baeldung is a cool tech site # what we type
$ echo $REPLY
baeldung is a cool tech site
Let’s now specify some variables to store the results:
$ read input1 input2 input3
baeldung is a cool tech site # what we type
$ echo "[$input1] [$input2] [$input3]"
[baeldung] [is] [a cool tech site]
If the number of variables is lower than the words obtained, all the remaining words and their delimiters are crammed in the last variable.
By default the read command splits the input into words, considering the
We can pass the input on multiple lines using the special <*backslash*> character:
$ read input1 input2 input3
baeldung \ # what
is a cool \ # we
tech site # type
$ echo "[$input1] [$input2] [$input3]"
[baeldung] [is] [a cool tech site]
Let’s take a closer look at this example. We used
The
2.2. The Internal Field Separator
The Internal Field Separator (IFS) determines what are the word boundaries in a given line. We can modify it to fit our needs and process a custom line:
$ {
IFS=";"
read input1 input2 input3
echo "[$input1] [$input2] [$input3]"
}
baeldung;;is;a;cool;tech;site # what we type
[baeldung] [] [is;a;cool;tech;site]
Notice the second word is empty as two semicolon characters have nothing in between.
Let’s run the same function again but now let’s use
$ {
IFS=" "
read input1 input2 input3
echo "[$input1] [$input2] [$input3]"
}
baeldung is a cool tech site # what we type
[baeldung] [is] [a cool tech site]
The output changes this time because word splitting behaves differently with
2.3. Return Codes
The return code is 0 in case of success. If an error happens or an EOF is encountered, the return code is greater than 0:
$ {
read input1-abc
echo "return code [$?]"
}
bash: read: `input1-abc': not a valid identifier
return code [1]
In this small snippet, we try to pass an invalid variable name and then we print the exit status.
2.4. Adding Basic Options
Let’s take a look at some of the most basic options we can use:
- -a array: stores the results of the word split operation in an array rather than separate variables
- –e: use the Bash built-in Readline library to read the input line
- -s: does not echo the input line to the standard output stream
- –p prompt: print the prompt text before requesting the input from the standard input stream without a
character - –i text: print the text as default input on the standard output stream (can only be used in combination with -e)
Let’s now implement a simple password prompt with hidden characters using the -s and -i options:
$ {
prompt="You shall not pass:"
read -p "$prompt" -s input
echo -e "\n input word [$input]"
}
You shall not pass: # invisible input here
input word [baledung is a cool site]
In some cases, the number of words varies from input to input so we can take advantage of the array-based variable:
$ {
declare -a input_array
text="baeldung is a cool tech site"
read -e -i "$text" -a input_array
for input in ${input_array[@]}
do
echo -n "[$input] "
done
}
baeldung is a cool tech site # default input here
[baeldung] [is] [a] [cool] [tech] [site]
The -i option allows us to specify a default input line. With the -e option we can also edit the input easily thanks to the Readline built-in library.
3. Advanced Syntax
Now that we’ve seen read in action, let’s take a look at some more advanced options:
- -d delim: specify the delimiter of the input line rather than using the
character - –u fd: read the input line from a given file descriptor
- –r: treat the
character as it is (cannot be used for escaping special characters) - -t timeout: attempt to read the input for a given period of seconds
- –N: read exactly N characters from the input unless a timeout occurs or EOF is reached
3.1. Reading from Files
Reading from files can be quite useful when we want to process specific fields inside them.
Let’s take a look at some simple structured data for cars:
car,car model,car year,car vin
Mercury,Grand Marquis,2000,2G61S5S33F9986032
Mitsubishi,Truck,1995,SCFFDABE1CG137362
Now let’s consider extracting specific fields from this CSV file and printing specific fields out:
$ {
exec {file_descriptor}<"./file.csv"
declare -a input_array
while IFS="," read -a input_array -u $file_descriptor
do
echo "${input_array[0]},${input_array[2]}"
done
exec {file_descriptor}>&-
}
In this example, we first call the exec built-in bash command to open a file descriptor on our input. Then, we pass this to the read command in a while loop which allows us to read the file line by line.
Finally, we close the file descriptor by calling again the exec command.
Notice that we defined the IFS as part of the while loop. This ensures that further word split operations outside the loop will use the default IFS.
This gives us the following output:
car,car year
Mercury,2000
Mitsubishi,1995
We mentioned in the beginning that we can alter the default input line delimiter.
Let’s consider the case where we structure the input using lines separated by semicolons rather than
car,car model,car year,car vin; \
> Mercury,Grand Marquis,2000,2G61S5S33F9986032; \
> Mitsubishi,Truck,1995,SCFFDABE1CG137362;
We now modify our function to take into account the new line delimiter:
$ {
# same calls as before
while IFS="," read -a input_array -d ";" -u $file_descriptor
# same calls as before
}
This provides the same output as before.
3.2. Reading from Other Commands
We can also use the read command in combination with other Linux commands through pipe redirection.
Let’s redirect the output of the ls command and extract the file names and their access rights from the ⁄ (root) folder:
$ {
ls -ll / | { declare -a input
read
while read -a input;
do
echo "${input[0]} ${input[8]}"
done }
}
The output varies for each Linux distribution, however, we can see the truncated output as we would expect it:
drwxr-xr-x bin
drwxr-xr-x boot
drwxr-xr-x dev
# some more folders
Let’s drill down a bit more to understand this example. First, we executed the ls -ll / command to print the files in the root directory.
Then, we pipe the output to the standard input of the command group that calls our read.
In this way, we can iteratively process multiple lines of input and print the first and eighth columns of each line.
We also execute read once before the loop to skip the summary that ls prints at the top. This is quite useful when we want to avoid table headers.
3.3. Timeouts and Special Characters
In complex scripts, we may want more flexibility to avoid blocking read calls.
Additionally, the input could contain *specific <backslash*> characters that we don’t want to escape (for example in a generated password):
{
prompt="You shall not pass:"
read -p "$prompt" -s -r -t 5 input
if [ -z "$input" ]; then
echo -e "\ntimeout occured!"
else
echo -e "\ninput word [$input]"
fi
}
Notice that we used the -t option and specified a timeout of 5 seconds. This ensures that the read command will automatically return if no input is entered (including the default
Since we’d said that backslashes should be taken literally, it’s no longer possible to enter the input across multiple lines as we did in one of the previous examples:
You shall not pass: # invisible input here
input word [baeldung\is]
3.4. Reading Exactly N Characters
Let’s complicate things even further and assume we want to have exactly 11 characters in the input:
{
prompt="Reading exactly 11 chars:"
read -p "$prompt" -N 11 -t 5 input1 input2
echo -e "\ninput word1 [$input1]"
echo "input word2 [$input2]"
}
When we run the above example the output is a bit surprising:
Reading exactly 11 chars:baeldung is # no <newline> here
input word1 [baeldung is]
input word2 []
Introducing the -N option causes three major side-effects.
First, the line delimiter does not matter anymore. The read command will wait for the exact number of characters at the input to exit (therefore it makes sense to have a timeout here as well).
Secondly, it no longer splits the input into words because we want exactly 11 characters assigned to input1.
Finally, if the timeout occurs, read assigns even a partial input to the input1 variable.
4. Conclusion
In this tutorial, we’ve seen how to use the Linux bash built-in read command.
We’ve seen how to use various options to customize the behavior for regular user interactions and later how to process inputs from other commands as well.
Overall, the read command allows us to split inputs sensibly for further processing thus providing a powerful built-in utility.
As always, the full source code of the article is available on GitHub.