How to Read a File Line By Line in Bash

By 

Updated on

7 min read

Bash Read File Line By Line

When writing Bash scripts, you will sometimes find yourself in situations where you need to read a file line by line. For example, you may have a text file containing data that should be processed by the script.

In this tutorial, we will discuss how to read a file line by line in Bash.

Info
If you are looking for the read command’s -p prompt option or other read flags, see the Bash read Command guide.

Quick Reference

MethodWhen to use
while IFS= read -r line; do ... done < fileStandard file reading; preserves all whitespace
mapfile -t lines < fileRead the whole file into an array, then loop with for (Bash 4.0+)
while IFS=, read -r f1 f2; do ... done < fileSplit each line on a delimiter
while IFS= read -r line; do ... done < <(cmd)Read from command output, variables stay in scope
while IFS= read -r -u9 line; do ... done 9< fileLoop body also reads from stdin

Reading a File Line By Line Syntax

The standard syntax for reading a file line by line is:

txt
while IFS= read -r line; do
  printf '%s\n' "$line"
done < input_file

Or the equivalent single-line version:

txt
while IFS= read -r line; do printf '%s\n' "$line"; done < input_file

How it works:

  • input_file is redirected into the while loop as standard input.
  • The read command reads one line at a time and assigns it to the line variable.
  • IFS= clears the Internal Field Separator so that leading and trailing whitespace in each line is preserved. Without it, read strips spaces and tabs from both ends of the line.
  • -r disables backslash interpretation so that backslashes in the file are treated as literal characters. Without it, read may silently alter data containing backslash sequences.
  • printf is used instead of echo to avoid unexpected behavior when a line starts with a value like -e that echo might interpret as an option.
  • Once all lines are processed, the while loop terminates.

Reading a File Line By Line Examples

Let us take a look at the following example. Suppose we have a file named distros.txt containing a list of Linux distributions and their package managers, separated by a comma:

distros.txttxt
Ubuntu,apt
Debian,apt
AlmaLinux,dnf
Arch Linux,pacman
Fedora,dnf

To read the file line by line and print each line:

sh
while IFS= read -r line; do
  printf '%s\n' "$line"
done < distros.txt

The code reads the file line by line, assigns each line to a variable, and prints it. The output is the same as running cat distros.txt.

To print only the distributions that use apt, use an if statement to check if the line contains the substring apt:

sh
while IFS= read -r line; do
  if [[ "$line" == *"apt"* ]]; then
    printf '%s\n' "$line"
  fi
done < distros.txt
output
Ubuntu,apt
Debian,apt

Splitting Lines into Fields

You can pass more than one variable to read to split each line into fields based on IFS. The first field goes into the first variable, the second into the second, and so on. If there are more fields than variables, the leftover fields are all assigned to the last variable.

The following example sets IFS to a comma and splits each line into distro and pm:

sh
while IFS=, read -r distro pm; do
  printf '%s is the package manager for %s\n' "$pm" "$distro"
done < distros.txt
output
apt is the package manager for Ubuntu
apt is the package manager for Debian
dnf is the package manager for AlmaLinux
pacman is the package manager for Arch Linux
dnf is the package manager for Fedora

Here, read assigns the text before the comma to distro and the remaining text to pm, which is why the output prints the package manager first.

Alternative File Reading Methods

Reading a File into an Array with mapfile

The mapfile built-in (also available under the name readarray) reads all lines of a file into an array in a single step. It requires Bash 4.0 or newer:

sh
mapfile -t lines < distros.txt

The -t option strips the trailing newline from each line, and each element of the lines array holds one line of the file. You can then iterate over the array with a for loop :

sh
mapfile -t lines < distros.txt

for line in "${lines[@]}"; do
  printf '%s\n' "$line"
done

If you prefer a for loop over while read, this is the correct way to get one. Looping directly over command output, such as for line in $(cat file), splits lines on every space and tab and expands glob characters like *, so lines with spaces are mangled. Iterating over a quoted array does not have this problem.

To read the whole file into a single string variable instead of an array, use content=$(<distros.txt). The $(<file) form is a Bash shortcut for $(cat file) that does not start an external process.

Using Process Substitution

Process substitution lets you use the output of a command as the input file. This is the recommended way to read from a command rather than a static file, because it keeps the loop in the current shell, unlike piping, which runs the loop body in a subshell and causes variables to be lost after the loop ends:

sh
while IFS= read -r line; do
  printf '%s\n' "$line"
done < <(grep "apt" distros.txt)

The < <(command) syntax redirects the command output as standard input to the loop without creating a subshell for the loop body.

Using a Here String

A here string passes a string directly as standard input. Using $(cat input_file) inside a here string preserves internal newlines:

sh
while IFS= read -r line; do
  printf '%s\n' "$line"
done <<< "$(cat input_file)"

Command substitution strips all trailing newlines from the output, and the here string then adds one newline. As a result, trailing blank lines and the file’s original final-newline state are not preserved.

A here string is also the simplest way to read the contents of a variable line by line; replace "$(cat input_file)" with "$variable".

Using a File Descriptor

You can open the file on a specific file descriptor and pass it to read with the -u option:

sh
while IFS= read -r -u9 line; do
  printf '%s\n' "$line"
done 9< input_file

Use a file descriptor number between 4 and 9 to avoid conflicts with shell internal file descriptors. This approach is useful when the loop body also reads from standard input (for example, prompting the user).

Troubleshooting

Variables set inside the loop are empty after the loop ends
This happens when read runs in a pipe subshell. Replace command | while IFS= read -r line with while IFS= read -r line; do ...; done < <(command). Process substitution keeps the loop in the current shell so variables are accessible after the loop.

Leading or trailing whitespace is being stripped
The IFS= assignment before read is missing or incorrect. Make sure you write IFS= read -r line (with no characters between = and read), not IFS=" " read or just read -r line.

The last line of the file is not processed
If the file does not end with a newline, read returns a non-zero exit code on the final line and the loop exits before processing it. To handle this, add || [[ -n "$line" ]] to the while condition: while IFS= read -r line || [[ -n "$line" ]]; do.

The loop produces garbled output on binary files
The while read pattern is designed for text files. Binary files contain null bytes and non-printable characters that read cannot handle reliably. Use dedicated tools for binary data.

Conclusion

The while IFS= read -r line; do ... done < file pattern is the reliable, portable way to read a file line by line in Bash. Use IFS= to preserve whitespace, -r to prevent backslash interpretation, and process substitution instead of pipes when you need variables to remain in scope after the loop. For details on the read built-in and its options, see the Bash read Command guide.

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page