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"