Either use an array and execute it as the arguments to a simple command.
deploy_source_code() {
exec_cmd=(find ./lib ./tools -type f -regex '.*\.\(cpp\|c\|h\)$'
-exec cp --parents {} "${args[destdir]}" \;)
if [ "${args[verbose]}" = "true" ]; then
exec_cmd+=(-print)
fi
"${exec_cmd[@]}"
}
Or a string and evaluate it as shell code:
deploy_source_code() {
exec_cmd="find ./lib ./tools -type f -regex '.*\.\(cpp\|c\|h\)$' \
-exec cp --parents {} \"\${args[destdir]}\" \;"
if [ "${args[verbose]}" = "true" ]; then
exec_cmd+=" -print"
fi
eval "$exec_cmd"
}
Note that in the code above, it's important to make sure the ${args[destdir]} is not expanded at the time of assignment as otherwise its content would be passed to eval to be evaluated as shell code! It can be hard to use eval safely, I'd go with the array approach especially considering you're already using associative arrays.
In your approach, you were using the split+glob operator on your string to make the arguments of a simple command. That didn't work because the last argument to find was \; instead of just ; (and you'd also have problems with the literal quote characters passed to find). You could use the split+glob operator as:
deploy_source_code() {
exec_cmd="find ./lib ./tools -type f -regex .*\.\(cpp\|c\|h\)$ \
-exec cp --parents {} ${args[destdir]} ;"
if [ "${args[verbose]}" = "true" ]; then
exec_cmd+=" -print"
fi
IFS=" " # split on space
set -f # disable glob
$exec_cmd # invoke the split+glob operator
}
But that means it won't work if ${args[destdir]} contains space characters. You could always replace those spaces with a character that is less likely to occur in ${args[destdir]} like : or newline.