Skip to content

Process Files Safely in a Bash Loop

Batch scripts often need to find matching files and perform the same check on each one. Filenames can contain spaces, quotes, and even newline characters, so splitting a command’s output on whitespace can target the wrong paths.

This tutorial uses Bash, find -print0, and a null-delimited read loop to count lines in matching log files. It reports what it reads and does not rename, delete, or change any file.


Step 1: Create a Small Set of Practice Files

01

Prepare Files with Different Names

Practice Data

Make a practice directory and add two .log files, including one whose name contains a space. Add a non-log file too; the script will leave it out because it does not match the selected pattern.

Terminal window
mkdir -p "$HOME/linux-script-lab/logs"
printf 'started\nWARN retry\n' > "$HOME/linux-script-lab/logs/app one.log"
printf 'ERROR unavailable\n' > "$HOME/linux-script-lab/logs/app-two.log"
printf 'notes are not logs\n' > "$HOME/linux-script-lab/logs/notes.txt"
❯ View Expected Console Output
The practice directory now contains two .log files and one .txt file.

Step 2: Find Matching Files Without Splitting Their Names

02

Emit a Null Separator Between Paths

File Discovery

find can print each matching path followed by a null byte with -print0. A null byte cannot appear inside a Linux filename, so it is an unambiguous delimiter. Do not store null-delimited output in a normal shell variable; use a loop or a compatible command to read it.

Terminal window
find "$HOME/linux-script-lab/logs" -maxdepth 1 -type f -name '*.log' -print0 |
xargs -0 -r printf 'Found: %s\n'
❯ View Expected Console Output
Found: /home/sam/linux-script-lab/logs/app one.log
Found: /home/sam/linux-script-lab/logs/app-two.log

Step 3: Read Each Path Safely in Bash

03

Use a Null-Delimited Read Loop

Iteration

read -r preserves backslashes, and -d '' tells Bash to stop reading at a null byte. Quote "$file" whenever you pass the current path to another command. The %q format displays special characters in an escaped form, making unusual names easier to recognize in a preview.

Terminal window
while IFS= read -r -d '' file; do
printf 'Would inspect: %q\n' "$file"
done < <(find "$HOME/linux-script-lab/logs" \
-maxdepth 1 -type f -name '*.log' -print0)
❯ View Expected Console Output
Would inspect: /home/sam/linux-script-lab/logs/app\ one.log
Would inspect: /home/sam/linux-script-lab/logs/app-two.log

Step 4: Apply a Read-Only Check to Every Match

04

Count Lines Without Changing the Files

Batch Operation

Extend the loop to count newline characters in each file. wc -l reads the file and prints the count; the redirection feeds the file to standard input, so the result contains only the number. The counter after the loop reports how many files matched.

Terminal window
count=0
while IFS= read -r -d '' file; do
((count += 1))
lines=$(wc -l < "$file")
printf '%2d %q (%s lines)\n' "$count" "$file" "$lines"
done < <(find "$HOME/linux-script-lab/logs" \
-maxdepth 1 -type f -name '*.log' -print0)
printf 'Matched files: %d\n' "$count"
❯ View Expected Console Output
1 /home/sam/linux-script-lab/logs/app\ one.log (2 lines)
2 /home/sam/linux-script-lab/logs/app-two.log (1 lines)
Matched files: 2

Step 5: Preview Before Adding a Change

05

Keep a Dry Run Before Renaming or Moving Files

Safe Automation

For any future batch script that changes files, keep the preview loop as the default and require an explicit option such as --apply before performing the change. Print each quoted source and destination first, verify the list, and handle name collisions before running the modifying command.

Terminal window
destination="$HOME/linux-script-lab/archive"
while IFS= read -r -d '' file; do
printf 'Preview: %q -> %q\n' "$file" "$destination/"
done < <(find "$HOME/linux-script-lab/logs" \
-maxdepth 1 -type f -name '*.log' -print0)
❯ View Expected Console Output
Preview: /home/sam/linux-script-lab/logs/app\ one.log -> /home/sam/linux-script-lab/archive/
Preview: /home/sam/linux-script-lab/logs/app-two.log -> /home/sam/linux-script-lab/archive/

See the GNU Findutils manual on safe filename handling for details about -print0 and null-delimited processing.

Comments