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


2 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