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:
- Build a command string to be run via
eval
and pass that string to dry_run
- Quote the command's special characters in
dry_run
before printing
Using eval
Here's how you could use eval
to print exactly what is run:
dry_run() {
printf '%s\n' "$1"
[ -z "${DRY_RUN}" ] || return 0
eval "$1"
}
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:
su - webuser1 -c "cd /home/webuser1/public_html && git log -1 -p|mail -s 'Git deployment on webuser1' user@domain.com"
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:
# This function prints each argument wrapped in single quotes
# (separated by spaces). Any single quotes embedded in the
# arguments are escaped.
#
shell_quote() {
# run in a subshell to protect the caller's environment
(
sep=''
for arg in "$@"; do
sqesc=$(printf '%s\n' "${arg}" | sed -e "s/'/'\\\\''/g")
printf '%s' "${sep}'${sqesc}'"
sep=' '
done
)
}
dry_run() {
printf '%s\n' "$(shell_quote "$@")"
[ -z "${DRY_RUN}" ] || return 0
"$@"
}
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:
'su' '-' 'webuser1' '-c' 'cd /home/webuser1/public_html && git log -1 -p|mail -s '\''Git deployment on webuser1'\'' user@domain.com'
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 to su
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.):
email_admin() {
echo " Emailing admin"
cmd=$(
shell_quote cd "${GIT_WORK_TREE}"
printf '%s' ' && git log -1 -p | '
shell_quote mail -s "${mail_subject}" "${admin_email}"
)
dry_run su - "${target_username}" -c "${cmd}"
echo " Emailed"
}
Output:
'su' '-' 'webuser1' '-c' ''\''cd'\'' '\''/home/webuser1/public_html'\'' && git log -1 -p | '\''mail'\'' '\''-s'\'' '\''Git deployment on webuser1'\'' '\''user@domain.com'\'''