Short answer: see BashFAQ #50:I'm trying to put a command in a variable, but the complex cases always fail!.
Long answer: when the shell parses a command like find / ${CRITERIA}, it first parses quotes, escapes, etc; then it replaced variables like ${CRITERIA} with their values, but does not go back through them to look for quotes etc -- it's already done that. As a result, putting quotes, escapes, etc in variables doesn't do anything useful.
The best way to do this sort of thing in bash is to store the parameters in an array:
#!/bin/bash
CRITERIA=(-name "*.[ch]" -o -name "*.cpp" -o -name "*.hpp")
echo "Find command: find / ${CRITERIA} > /tmp/output.txt"
find / "${CRITERIA[@]}" > /tmp/output.txt
...but this won't work with the way you're treating $1 as an optional set of criteria. Does the script need any other arguments? If not, I'd recommend having it take the search criteria as separate arguments rather than a single one (i.e. ./findscript -name "*.c" instead of ./findscript "-name \"*.c\""), like this:
#!/bin/bash
if [ $# -gt 0 ]; then
CRITERIA=("$@")
else
CRITERIA=(-name "*.[ch]" -o -name "*.cpp" -o -name "*.hpp")
fi
echo "Find command: find / ${CRITERIA} > /tmp/output.txt"
find / "${CRITERIA[@]}" > /tmp/output.txt
EDIT: Based on your comment about needing to allow other options/arguments to the script, I see three options:
Use eval to parse the getopt argument into an array:
case "$opt" in
f) eval "CRITERIA=($OPTARG)" ;;
This has all of the usual problems associated with eval, mainly having to do with failing in weird (and possibly dangerous) ways depending on what's passed as the OPTARG. Ick.
If the script doesn't need to take any "regular" (non-option) arguments, pass the criteria as regular arguments (./script -a -b -c -d -- -name ".cpp" -o -name ".hpp"). The problem with this is that IIRC getopts doesn't read -- as a delimiter between options and regular arguments, so you'd have to do at least some of the parsing "by hand". Correction: at least in bash version 3.2.48, getopts handles -- automatically.
If the only criteria you need to pass are -name's, you can use multiple -f options to specify name patterns to build the criteria, e.g. ./script -a -b -c -d -f "*.cpp" -f "*.hpp"
CRITERIA=()
...
case "$opt" in
f) if [[ ${#CRITERIA[@]} -eq 0 ]]; then
CRITERIA=(-name "$OPTARG")
else
CRITERIA+=(-o -name "$OPTARG")
fi ;;
...
if [[ ${#CRITERIA[@]} -eq 0 ]]; then
CRITERIA=(-name "*.[ch]" -o -name "*.cpp" -o -name "*.hpp")
fi
FIND_CRITERIAandCRITERIAin your example. Typo?