I am trying to implement a dry run kind of mechanism for my script and facing the issue of quotes getting stripped off when a command is passed as an argument to a function and resulting in unexpected behavior.
dry_run () {
echo "$@"
#printf '%q ' "$@"
if [ "$DRY_RUN" ]; then
return 0
fi
"$@"
}
email_admin() {
echo " Emailing admin"
dry_run su - $target_username -c "cd $GIT_WORK_TREE && git log -1 -p|mail -s '$mail_subject' $admin_email"
echo " Emailed"
}
Output is:
su - webuser1 -c cd /home/webuser1/public_html && git log -1 -p|mail -s 'Git deployment on webuser1' [email protected]
Expected:
su - webuser1 -c "cd /home/webuser1/public_html && git log -1 -p|mail -s 'Git deployment on webuser1' [email protected]"
With printf enabled instead of echo:
su - webuser1 -c cd\ /home/webuser1/public_html\ \&\&\ git\ log\ -1\ -p\|mail\ -s\ \'Git\ deployment\ on\ webuser1\'\ [email protected]
Result:
su: invalid option -- 1
That shouldn't be the case if quotes remained where they were inserted. I have also tried using "eval", not much difference. If i remove the dry_run call in email_admin and then run script, it work great.
"$@"
should work. In fact it works for me in this simple test case:Output:
Edited to add: the output of
echo $@
is correct. The"
is a meta-character and not part of the parameter. You can prove that it is correctly working by addingecho $5
todry_run()
. It will output everything after-c
Try using
\"
instead of just"
.This is not a trivial problem. Shell performs quote removal before calling the function, so there's no way the function can recreate the quotes exactly as you typed them.
However, if you just want to be able to print out a string that can be copied and pasted to repeat the command, there are two different approaches you can take:
eval
and pass that string todry_run
dry_run
before printingUsing
eval
Here's how you could use
eval
to print exactly what is run:Output:
Note the crazy amount of quoting -- you've got a command within a command within a command, which gets ugly quickly. Beware: The above code will have problems if your variables contain whitespace or special characters (like quotes).
Quoting Special Characters
This approach enables you to write code more naturally, but the output is harder for humans to read because of the quick-and-dirty way
shell_quote
is implemented:Output:
You can improve the readability of the output by changing
shell_quote
to backslash-escape special characters instead of wrapping everything in single quotes, but it's hard to do correctly.If you do the
shell_quote
approach, you can construct the command to pass tosu
in a safer way. The following would work even if${GIT_WORK_TREE}
,${mail_subject}
, or${admin_email}
contained special characters (single quotes, spaces, asterisks, semicolons, etc.):Output:
That's tricky, you might try this other approach I've seen:
that way you just set DRY_RUN to either blank or "echo" at the top of your script and it either does it or just echoes it.
Nice challenge :) It should be "easy" if you have bash recent enough to support
$LINENO
and$BASH_SOURCE
Here is my first attempt, hoping it suits your needs: