Skip to content
shellmap

Loop over lines in a file

Iterate through a file one line at a time and run a command per line — for batch processing a hostnames list, replaying a SQL script line-by-line, or reacting to log lines.

How to loop over lines in a file in each shell

Bashunix
while IFS= read -r line; do echo "got: $line"; done < file.txt

`IFS=` (empty) prevents leading/trailing whitespace stripping per-line. `-r` prevents backslash escape interpretation (so `\n` in input stays as literal `\n`, not a newline). Both together: every line preserved EXACTLY. DON'T use `for line in $(cat file.txt)` — that splits on `$IFS` (default: space/tab/newline) so multi-word lines split, and quoting is wrong. The `while read` form is correct for newline-delimited input; the `for` form is correct only for whitespace-delimited tokens.

Zshunix
while IFS= read -r line; do echo "got: $line"; done < file.txt

Same form as bash. Zsh-specific quality-of-life: `cat file.txt | while read -r line; echo $line; end` — wait no, that's fish-ish. Actual zsh: `for line in "${(@f)$(< file.txt)}"; do echo $line; done` — `(@f)` is "split on newlines" parameter flag, more concise than the read loop for small files. For huge files, prefer streaming read.

Fishunix
while read -l line; echo "got: $line"; end < file.txt

Fish `read` builtin. `-l` makes the variable local to the loop scope. No `IFS=` equivalent needed — fish doesn't do bash-style word splitting by default. For "loop over CSV with field split": `while read -d , -l -a fields; echo $fields[1]; end < file.csv` (`-d` sets delimiter, `-a` splits into array).

PowerShellwindows
Get-Content file.txt | ForEach-Object { Write-Host "got: $_" }

`$_` is the current pipeline item (the line). For large files, `Get-Content` is the streaming variant — reads line-by-line, doesn't materialize the whole file. For the WHOLE-FILE-IN-MEMORY alternative: `(Get-Content file.txt) | ForEach-Object { ... }` (the parens force full read first). For sub-millisecond performance on huge files (millions of lines), drop to .NET: `[IO.File]::ReadLines("file.txt") | ForEach-Object { ... }` — true line-by-line streaming with the .NET reader (faster than `Get-Content` for very large files).

cmd.exewindows
for /f "usebackq tokens=*" %i in ("file.txt") do echo got: %i

`for /f` reads file content. `"usebackq"` enables the backtick-quoted filename form (lets you use double-quotes around the filename, mandatory if path contains spaces). `tokens=*` captures the entire line. Inside a `.bat` file, double the `%`: `for /f "usebackq tokens=*" %%i in ("file.txt") do echo got: %%i`. Empty lines are SKIPPED by default — pass `eol=^` and verify if you need them.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • The bash `while IFS= read -r line` idiom IS the right form. EVERY part matters: drop `IFS=` and you strip leading whitespace; drop `-r` and you mangle backslashes; use `for line in $(cat file)` and you split on spaces. This is one of the most common bash bugs in scripts; cargo-cult-copy the full form.
  • Lines without a trailing newline are a portability hazard. `while read` discards the LAST line if the file doesn't end with newline (because `read` returns false on EOF before delivering the line). Fix: `while IFS= read -r line || [ -n "$line" ]; do ...; done < file.txt` — the `|| [ -n "$line" ]` catches the last unterminated line.
  • For HUGE files (multi-GB), `Get-Content file.txt | ForEach-Object` is fast and streaming; the WHOLE-file-into-memory `(Get-Content file.txt)` is NOT. For pwsh-on-Linux pipeline-heavy scripts, the .NET `[IO.File]::ReadLines` form is the fastest because it bypasses the pwsh pipeline object-construction overhead per line.
  • For PARALLEL per-line processing (where each line's work is CPU-heavy), bash `xargs -P N -I {} sh -c '...' < file.txt` runs N parallel workers. pwsh 7+: `Get-Content file.txt | ForEach-Object -Parallel { ... } -ThrottleLimit 8`. Massive speedup when each line's work is independent (e.g., HTTP requests to N URLs).

Related commands

Related tasks