Bash is a common scripting language that has evolved much over the years. It provides developers with a toolset to complete quick-and-dirty automation to help streamline productivity. Among its many features is the read tool which helps get input from the command line, prompting the user, or even via redirection and piping.
Highlights
Bash’s read tool can be used in a number of common ways as well as with a number of common options enabled (i.e. flags). By the end of this article the following applications of bash’s read
tool will be covered:
- Prompting and reading input from a user with
echo
andread
. - Prompting and reading input from a user with only the
read
tool. - Disabling automatic newline characters.
- Dealing with escape characters (slashes) in text.
- Using
read
to save the input to multiple variables. - Using
read
to get input from a file, line-by-line. - A basic discussion of Internal Field Separators (IFS).
This article is by no means an exhaustive display of the read
tool’s full gamut of functionality. However, most common cases are addressed here such that anyone should feel comfortable using the read
tool in bash scripting afterward.
Read User Input
The most basic example of the read tool is to prompt a user to enter some input. This can be accomplished via the following approach:
#!/bin/bash # prompt a user to enter a username. echo "Enter your username: " # use read command to store input to # the <username> variable. read username # print the username to the console # as a confirmation of success. echo "Hello, $username"
This script executes the following three instructions:
- Prompts a user to enter a username.
- Stores the user’s input into a variable named
username
. - Prints a dynamic string using the
username
variable to confirm.
The full sequence appears as such in the terminal window:
Enter your username: Zack Hello, Zack
Quick and dirty, but gets the job done. Before we move on to the next example using the read command let us pick this one apart first. You might notice the input Zack
typed is on the line following the prompt to the user.
Keeping User Input on the Same Line
To keep the user-entered input on the same line as the prompt, one can pass the -n
flag to the echo
tool which will, according to the man pages, instruct the echo
tool: “do not output the trailing newline.” We just need to change the first line to the following:
echo -n "Enter your username: "
This results in the following output provided the name Zack
is entered:
Enter your username: Zack Hello, Zack
This keeps the output a bit tighter and arguably makes things easier to debug in many cases. Getting back on track, let’s consider an edge case quickly where slash characters might be entered.
Aside: Dealing with Escaped Characters
If you are developing bash scripts using an IntelliJ-based IDE you may have already noticed a warning that “read without -r will mangle backslashes.” This message urges users to use the -r flag that, according to the man page, instructs read
to “… not allow backslashes to escape any characters.” Let’s consider an example of when this would cause an issue:
# defines a string variable base="alpha\rithms"; # read in base variable without # disabling escape feature read var1 <<< "$base" echo "$var1"; # read in base variable with # escaping disabled read -r var2 <<< "$base"; echo "$var2"
Here we define the variable base
as a string with an escaping backslash in the midst of the characters alpha\rithms
. We then use the <<<
(here string) operator to explicitly pass this variable into the read
command in two ways:
- Without the
-r
flag, allowing escaping characters to be present as typed. - With the
-r
flag, removing escaping slashes from the text.
The output of this code is as follows:
alpharithms alpha\rithms
This is particularly useful in cases where input may contain characters using the \
character for line continuations.
Prompt & Read User Input
Bash scripts are meant to be quick-and-dirty ways to automate tasks. At some point, using the echo
command to facilitate gathering user input was deemed too tedious and the read
tool was given a prompting feature. By passing the p
flag, the read
tool will both prompt and read user input. Consider the following:
# prompt user for name and # store as variable read -p "Enter a username: " username # print the username to the console # as a confirmation of success. echo "Hello, $username"
The output of this code is as follows:
Enter a username: Zack Hello, Zack
This approach demonstrates the -p
option’s ability to remove the dependency of echo
when gathering user input. Let’s now consider extending this use-case to prompt a user for their username and password. In the above example, we note that the username Zack
was visible in the console — not a secure approach for gathering passwords. Using the -s
flag, the read
tool will “silence” the input such that it is not visible on the console. Consider the following:
# prompt user for name and # store as variable read -p "Enter a username: " username read -sp "Enter a password: " password # print the username to the console # as a confirmation of success. echo "Hello, $username. The length of your password is ${#password}."
Here we introduce a second variable password
that prompts the user for input much like the prompting for username
. The difference is the introduction of the -s
flag to ensure the value of password
is not displayed in the terminal as the user types. The final statement echos the length of the password (not secure) for demonstration here to validate that input was, in fact, received. The output of the above script is as follows:
Enter a username: Zack Enter a password: Hello, Zack. The length of your password is 8.
Read Multiple Variables
Bash allows developers to read command line arguments with ease. While not diving too deeply into the use-cases, edge-cases, or finer points of command line arguments in bash — the following example shows how the read
tool handles them.
#!/bin/bash # prompt user for some integers # and store to three variables a, b, c read -p "enter some integers 0-9: " a b c # print the three variables back to stdout echo "Your integers are [$a] [$b] [$c]"
Here’s where things get a little … bashy. Let’s consider the output while entering 3 command line arguments: 1
, 2
, and 3
:
enter 4 integers 0-9: 1 2 3 Your integers are [1] [2] [3]
Here we see each command line argument assigned to a variable, as expected, and printed out such that each of the three brackets in the echo
string receives a single value. Now let’s consider the case where more than 3 command line arguments are given:
enter 4 integers 0-9: 1 2 3 4 Your integers are [1] [2] [3 4]
The output here is similar except that, for the last bracketed variable in the echo
string — the [$3]
— the last two command line arguments are assigned. This is expected behavior due to how bash handles command line arguments. Essentially, bash will assign the values of all command line arguments beyond the number of available variables for assignment, to the last variable. Consider one more case as an example:
enter some integers 0-9: 1 2 3 4 5 6 7 8 9 Your integers are [1] [2] [3 4 5 6 7 8 9]
Every value beyond the first two (1 and 2) are assigned to the third variable c
. This is true for string values as well:
enter some integers 0-9: 1 2 3 4 5 6 cat dog fish horse Your integers are [1] [2] [3 4 5 6 cat dog fish horse]
This behavior isn’t strictly related to the read
command but useful given how the tool is commonly used. Check out this article by Redhat for a more detailed discussion on handling command line arguments in bash. Bash’s handling of command line args is somewhat strange when compared to other languages like Python, which shadows how other C-based languages handle them.
Read From a File Line-by-Line
The read
tool can be used to read data linewise in from an external file. Assuming the file exists, is readable, and contains valid data the following approach will leverage the read
command to read a file line-by-line:
#!/bin/bash # iterate over word boundaries while IFS= read -r line; do # print the line to the console echo "$line" # redirect file contents done < "../data/names.txt"
In this script, the following actions and options are taken/specified:
- A while loop takes input from a file.
- the IFS variable is set to the empty string, to preserve the whitespace.
- the
<
redirection operator is used to indicate the data source. - the
-r
flag is used to avoid issues with slashes.
The output from this script displays the contents of the names.txt
file which has a series of names listed as first, last
separated by newlines:
bob, foo alice, delaney garth, smith wayne, nebo bill, jones ted, jackson midge, marley
The output is exactly as expected! This demonstrates very basic, somewhat opinionated, usage of the while
loop in which the read
tool can be leveraged to read line-by-line from a file.
Final Thoughts
What is the best way to use the read
tool? There is no easy answer here and like many things in the world of computer science and software engineering: it depends on what one is trying to do! The read
command can be used along with the -p
option to prompt and read user input which is a great time-saver as well as incredibly useful. Knowing the nuances of bash’s command line argument interpretations can ensure multiple variables can be accurately handled via the read
tool as well.