How to Suppress cp Command Errors When Source Files Don’t Exist in Shell Scripts


2 views

When working with shell scripts for build processes, you'll often encounter this situation:

cp ./src/*/*.h ./destination

The command fails with "No such file or directory" when no matching .h files exist in the specified path. While this makes sense for interactive shell use, it's problematic for automated scripts where we want silent continuation.

Method 1: Redirect stderr to /dev/null

cp ./src/*/*.h ./destination 2>/dev/null

This suppresses all error messages from cp, including ones you might want to see. Use with caution.

Method 2: Check for files first

if ls ./src/*/*.h &>/dev/null; then
    cp ./src/*/*.h ./destination
fi

More verbose but only executes cp when files actually exist.

Method 3: Use find + xargs (MacOS/BSD compatible)

find ./src -name "*.h" -maxdepth 2 -exec cp {} ./destination \;

This won't produce errors when no files are found and handles spaces in filenames better.

Build systems often treat any command returning non-zero as a failure. For optional copy operations (like header files that might not exist in all configurations), silent failure is preferred. The methods above all return exit status 0 when no files are found.

For frequent use in scripts, consider adding this to your shell profile:

safe_copy() {
    local pattern=$1
    local dest=$2
    if ls $pattern &>/dev/null; then
        cp $pattern $dest
    fi
}

# Usage:
safe_copy "./src/*/*.h" "./include"

When writing build scripts on macOS, I frequently encounter situations where I need to copy header files (*.h) from source directories, but the command fails when no matching files exist. The standard behavior:

cp ./src/*/*.h ./destination

throws this error:

cp: ./src/*/*.h: No such file or directory

In automated build systems, we want the script to continue execution even when certain optional files are missing. The error causes the entire build process to fail, which isn't always the desired behavior.

Here are several working approaches:

# Method 1: Use find + xargs (most reliable)
find ./src -name "*.h" -print0 | xargs -0 -I {} cp {} ./destination

# Method 2: Nullglob shell option
(shopt -s nullglob; cp ./src/*/*.h ./destination)

# Method 3: Redirect stderr (quick fix but masks all errors)
cp ./src/*/*.h ./destination 2>/dev/null || true

Find + xargs approach is the most robust because:

  • Handles spaces in filenames perfectly
  • Only attempts copy when files actually exist
  • Works recursively through subdirectories

Nullglob method is cleaner but requires subshell:

(shopt -s nullglob; files=(./src/*/*.h); [[ ${#files[@]} -gt 0 ]] && cp "${files[@]}" ./destination)

For production build scripts, I recommend combining nullglob with an existence check:

#!/bin/bash
shopt -s nullglob
headers=(./src/*/*.h)
if (( ${#headers[@]} )); then
    cp "${headers[@]}" ./build/include
fi

This provides both silent failure when no files exist and proper error reporting if the cp command fails for other reasons (like permission issues).

For frequent use across projects, create a shell function:

safe_copy() {
    local pattern=$1
    local dest=$2
    shopt -s nullglob
    local files=($pattern)
    (( ${#files[@]} )) && cp "${files[@]}" "$dest"
}

# Usage:
safe_copy "./src/*/*.h" "./include"