I am working on a shell script that constructs a complex command from variables, e.g. like this (with a technique that I've learned from the Bash FAQ):
#!/bin/bash
SOME_ARG="abc"
ANOTHER_ARG="def"
some_complex_command \
${SOME_ARG:+--do-something "$SOME_ARG"} \
${ANOTHER_ARG:+--with "$ANOTHER_ARG"}
This script dynamically adds the parameters --do-something "$SOME_ARG"
and --with "$ANOTHER_ARG"
to some_complex_command
if these variables are defined. So far this is working fine.
But now I also want to be able to print or log the command when I'm running it, for example when my script is run in a debug mode. So when my script runs some_complex_command --do-something abc --with def
, I also want to have this command inside a variable so I can e.g. log it to the syslog.
The Bash FAQ demonstrates a technique to use the DEBUG
trap and the $BASH_COMMAND
variable (for example for debugging purposes) for this purpose. I've tried that with the following code:
#!/bin/bash
ARG="test string"
trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG"
echo "Command was: ${COMMAND}"
This works, but it doesn't expand the variables in the command:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
I guess I have to use eval to expand echo "$ARG"
to echo test string
(at least I haven't found a way without eval
yet). The following does work:
eval echo "Command was: ${COMMAND}"
It produces the following output:
host ~ # ./test.sh
test string
Command was: echo "$ARG"
Command was: echo test string
But I'm not really certain if I can use eval
safely like this. I've unsuccessfully tried to exploit some things:
#!/bin/bash
ARG="test string; touch /x"
DANGER='$(touch /y; cat /etc/shadow)'
trap 'COMMAND="$BASH_COMMAND"; trap - DEBUG' DEBUG
echo "$ARG" $DANGER
echo "Command was: ${COMMAND}"
eval echo "Command was: ${COMMAND}"
It seems to handle this well, but I'm curious if someone else sees an issue that I've missed.
Best Answer
One possibility is to make a wrapper function that will at the same time print the command and execute it, as follows:
So that in your script you can do:
No need to fiddle with the trap. The drawback is that it adds some
debug
keywords all over your code (but that should be fine, it's common to have such stuff, like asserts, etc.).You can even add a global variable
DEBUG
and modify thedebug
function like so:Then you can call your script as:
or
or just
depending whether you want to have the debug info or not.
I capitalized the
DEBUG
variable because it should be treated as an environment variable.DEBUG
is a trivial and common name, so this might clash with other commands. Maybe call itGNIOURF_DEBUG
orMARTIN_VON_WITTICH_DEBUG
orUNICORN_DEBUG
if you like unicorns (and then you probably like ponies too).Note. In the
debug
function, I carefully formatted each argument withprintf '%q'
so that the output will be correctly escaped and quoted so as to be reusable verbatim with a direct copy and paste. It will also show you exactly what the shell saw as you'll be able to figure out each argument (in case of spaces or other funny symbols). This function also uses direct assignment with the-v
switch ofprintf
so as to avoid unnecessary subshells.