How to Read a File Line By Line in Bash

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.
read command’s -p prompt option or other read flags, see the Bash read Command
guide.Quick Reference
| Method | When to use |
|---|---|
while IFS= read -r line; do ... done < file | Standard file reading — preserves all whitespace |
while IFS=, read -r f1 f2; do ... done < file | Split 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< file | Loop body also reads from stdin |
Reading a File Line By Line Syntax
The standard syntax for reading a file line by line is:
while IFS= read -r line; do
printf '%s\n' "$line"
done < input_fileOr the equivalent single-line version:
while IFS= read -r line; do printf '%s\n' "$line"; done < input_fileHow it works:
input_fileis redirected into the while loop as standard input.- The
readcommand reads one line at a time and assigns it to thelinevariable. IFS=clears the Internal Field Separator so that leading and trailing whitespace in each line is preserved. Without it,readstrips spaces and tabs from both ends of the line.-rdisables backslash interpretation so that backslashes in the file are treated as literal characters. Without it,readmay silently alter data containing backslash sequences.printfis used instead ofechoto avoid unexpected behavior when a line starts with a value like-ethatechomight 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:
Ubuntu,apt
Debian,apt
AlmaLinux,dnf
Arch Linux,pacman
Fedora,dnfTo read the file line by line and print each line:
while IFS= read -r line; do
printf '%s\n' "$line"
done < distros.txtThe 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:
while IFS= read -r line; do
if [[ "$line" == *"apt"* ]]; then
printf '%s\n' "$line"
fi
done < distros.txtUbuntu,apt
Debian,aptSplitting 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:
while IFS=, read -r distro pm; do
printf '%s is the package manager for %s\n' "$pm" "$distro"
done < distros.txtapt 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 FedoraAlternative 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:
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:
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:
while IFS= read -r -u9 line; do
printf '%s\n' "$line"
done 9< input_fileUse 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 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