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
Prepare Files with Different Names
Practice DataMake 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.
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
Emit a Null Separator Between Paths
File Discoveryfind 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.
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.logFound: /home/sam/linux-script-lab/logs/app-two.logStep 3: Read Each Path Safely in Bash
Use a Null-Delimited Read Loop
Iterationread -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.
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.logWould inspect: /home/sam/linux-script-lab/logs/app-two.logStep 4: Apply a Read-Only Check to Every Match
Count Lines Without Changing the Files
Batch OperationExtend 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.
count=0while 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: 2Step 5: Preview Before Adding a Change
Keep a Dry Run Before Renaming or Moving Files
Safe AutomationFor 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.
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.