Read file lines with `while read`, not `for`
`for line in $(cat file)` splits on whitespace, not lines. SC2013 recommends a `while IFS= read -r` loop instead.
Problem
`for line in $(cat file)` (or `$(<file)`) first performs command/parameter substitution, which is then word-split on `IFS` (spaces, tabs, and newlines by default) and globbed. The loop variable ends up bound to whitespace-separated words, not lines — a file with "hello world" on one line produces two iterations, not one. This also silently mangles leading/trailing whitespace, collapses blank lines, and expands any `*`/`?` characters present in the file content.
Bad
for line in $(cat file.txt); do
echo "Line: $line"
doneGood
while IFS= read -r line; do
echo "Line: $line"
done < file.txtExplanation
`while IFS= read -r line` reads exactly one line per iteration, preserves leading/trailing whitespace by clearing `IFS`, and `-r` prevents backslash sequences from being interpreted. Redirecting the file with `< file.txt` avoids a useless `cat` and keeps the loop running in the current shell (unless piped, in which case it runs in a subshell).
Related
SC2086
SC2044
SC2162
When It Matters
for line in $(cat file) does not iterate lines: it iterates whitespace-separated words, then globs each one. A line containing two words produces two iterations, an empty line disappears, and a line containing an asterisk expands to the directory contents. Any script processing a list of paths, a CSV, or log lines will encounter all three cases, and the resulting off-by-many behaviour is not reported as an error.
Second Example
Note
Beware that commands reading stdin inside the loop — ssh is the classic — consume the input file. Pass ssh -n, or redirect the loop from a different file descriptor.
Exceptions
When the file genuinely contains whitespace-free tokens and you want word iteration — a list of package names or numeric IDs — the for loop is fine and more readable. Disable globbing with set -f if any token could contain a metacharacter.
Faq
Q
Why does my loop stop after one iteration?
A
A command inside the loop read the rest of stdin. Give ssh the -n flag, or read the file on a dedicated descriptor: while ... read -r line <&3; done 3< file.
Q
How do I keep variables set inside the loop?
A
Redirect from the file rather than piping into the loop; a pipeline puts the loop in a subshell and discards its assignments.
Q
How do I handle the last line without a newline?
A
Use while IFS= read -r line || [ -n "$line" ]; do to process the trailing partial line.