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 , and characters as word delimiters.

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 to escape characters on each line (2 and 3) except for line 4.

The character also serves as a default input line delimiter. We’ll see a bit later on how to alter this behavior.

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 as the Internal Field Separator:

$ {
      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 characters. A sequence of <whitespace> characters is considered a delimiter.

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 characters:

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 line delimiter character) in this interval.

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.