How to Count Specific File Types in Bash with Error Handling and Conditional Logic


13 views

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