When attempting to count files with specific extensions in Bash, many developers encounter issues with error handling and empty directories. The naive approach using ls | wc -l
fails in several scenarios:
# Problematic implementation
count=$(ls /import/*.zip | wc -l)
echo "Found $count zip files"
Here are three reliable methods to count files while properly handling edge cases:
Method 1: Using find with null-terminated output
# Count .zip files safely
count=$(find /import -maxdepth 1 -name "*.zip" -print0 | grep -cz "^")
if [ "$count" -gt 0 ]; then
echo "Found $count zip file(s) for import"
else
echo "No zip files found in /import directory"
fi
Method 2: Using array expansion
# Handle spaces in filenames correctly
files=(/import/*.zip)
count=${#files[@]}
# Need to subtract 1 if no files exist due to glob behavior
if [[ -e "${files[0]}" ]]; then
echo "Found $count zip file(s)"
else
count=0
echo "No matching files found"
fi
Method 3: Using shopt for nullglob
# Most elegant solution for modern bash
shopt -s nullglob
files=(/import/*.zip)
count=${#files[@]}
shopt -u nullglob
if (( count > 0 )); then
echo "Processing $count archive(s):"
for f in "${files[@]}"; do
echo " - $f"
done
else
echo "Warning: No zip archives found in import directory" >&2
fi
For more complex scenarios, consider these patterns:
# Count multiple file types
declare -A extensions=([zip]=0 [gz]=0 [tar]=0)
for ext in "${!extensions[@]}"; do
files=(/import/*.$ext)
extensions[$ext]=${#files[@]}
done
# Print summary
for ext in "${!extensions[@]}"; do
echo "$ext files: ${extensions[$ext]}"
done
# Check total files
total=$(IFS=+; echo "$((${extensions[*]}))")
if (( total > 10 )); then
echo "Warning: Found $total archives (threshold is 10)" >&2
fi
When dealing with directories containing thousands of files:
- The
find
method is generally fastest for large directories - Array methods consume more memory with many files
- For recursive searches, always use
find
with appropriate depth limits
When trying to count .zip files in a directory containing subdirectories, many developers encounter this common pitfall:
count=$(ls /import/*.zip | wc -l)
# Fails when no files exist with "No such file or directory"
Here are three professional approaches to handle this scenario properly:
1. Using find with null-terminated output
count=$(find /import -maxdepth 1 -name "*.zip" -type f -printf '.' | wc -c)
if [ "$count" -gt 0 ]; then
echo "Found $count zip files"
else
echo "No zip files found"
fi
2. Shell globbing with array
files=(/import/*.zip)
count=${#files[@]}
# Handle case where pattern doesn't match (counts as 1)
if [[ -e "${files[0]}" ]]; then
echo "Found $count zip files"
else
echo "No zip files found"
fi
3. Using shopt for nullglob
shopt -s nullglob
files=(/import/*.zip)
count=${#files[@]}
shopt -u nullglob
if [ "$count" -gt 0 ]; then
echo "Processing $count files:"
for file in "${files[@]}"; do
echo " - ${file##*/}"
done
else
echo "No files matching pattern"
fi
For more complex scenarios, consider these professional patterns:
# Count multiple extensions
count=$(find /import $-name "*.zip" -o -name "*.gz"$ -type f | wc -l)
# Recursive counting with depth limit
count=$(find /import -maxdepth 3 -name "*.zip" -type f | wc -l)
# Counting with file size filter
count=$(find /import -name "*.zip" -type f -size +1M | wc -l)
- Using
ls
with wildcards (breaks with spaces and special chars) - Not handling the case when no files match the pattern
- Forgetting to reset shell options like
nullglob
- Counting directories when only files are needed