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
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.txtini
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

Alternative File Reading Methods

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

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

Note that command substitution strips trailing newlines from the output, so the last line may behave differently from input redirection.

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.

FAQ

Why must I write IFS= with nothing after the equals sign?
IFS= sets the Internal Field Separator to an empty string for the duration of the read command. This disables word splitting, which means read does not trim leading or trailing spaces or tabs from the line. If you omit it, whitespace at the start and end of each line is silently removed.

Why should I always use read -r?
Without -r, read interprets backslashes as escape characters — for example, \n becomes a newline and \\ becomes a single backslash. This can silently corrupt data. Use -r by default and only omit it when you explicitly need escape processing.

Why is my variable empty after the while loop?
Piping into while read runs the loop in a subshell. Variables set inside a subshell are not visible in the parent shell. Use done < file or done < <(command) (process substitution) to keep the loop in the current shell.

What is the difference between done < file and cat file | while read?
Both read the same data, but done < file keeps the loop body in the current shell so variables persist after the loop. cat file | while read runs the loop in a subshell due to the pipe, and any variables set inside the loop are lost when it exits.

I am looking for the read -p prompt option — is it covered here?
The -p flag and other interactive read options are covered in the Bash read Command guide, which includes prompting the user, silent input, timeouts, and arrays.

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