How to loop over files in Bash

When a shell task must touch a group of files, the risky part is often the match set rather than the loop syntax. A broad glob, an unmatched pattern, or an unquoted path variable can make a Bash script skip names with spaces, process literal text, or touch files outside the intended group.

Bash expands the glob in the for loop header before the loop body starts. Each matching path is assigned to the loop variable in turn, and quoting that variable keeps a filename with spaces as one argument when the command inside the loop runs.

The sample script counts lines in logs/*.log while a text file in the same directory remains untouched. It enables nullglob before the loop so an empty match set is skipped; without that option, Bash leaves an unmatched pattern such as missing/*.log as literal text for the loop to process.

Steps to loop over files in Bash:

  1. Create a small test directory with three log files and one non-log file.
    $ mkdir -p logs
    $ printf 'one\ntwo\n' > logs/app.log
    $ printf 'one\n' > logs/db.log
    $ printf 'one\ntwo\n' > 'logs/web access.log'
    $ printf 'skip\n' > logs/readme.txt
  2. Create the loop script.
    count-logs.sh
    #!/usr/bin/env bash
    set -euo pipefail
    shopt -s nullglob
     
    for logfile in logs/*.log; do
        lines=$(wc -l < "$logfile")
        printf "%s:%s\n" "$logfile" "$lines"
    done

    Set nullglob before the loop that uses the pattern. The path variable is quoted in both commands so a log file with spaces in its name is still handled as one argument.

  3. Check the script syntax before running it.
    $ bash -n count-logs.sh

    No output means Bash did not find a parse error.

  4. Run the script and confirm that only *.log files are listed.
    $ bash count-logs.sh
    logs/app.log:2
    logs/db.log:1
    logs/web access.log:2
  5. Move the matching log files out of the loop path to prove the empty-match case.
    $ mkdir -p archive
    $ mv logs/*.log archive/
  6. Run the script again and confirm that the empty match set prints no output.
    $ bash count-logs.sh

    No output means nullglob removed the unmatched pattern before the loop started.

  7. Remove the sample script and directories.
    $ rm -rf archive count-logs.sh logs