So I can quite happily build up a command line into a bash array, and then execute it with quotes and get each argument nicely quoted:
declare -a cmd_args
cmd_args=("-p" "dir path/with spaces")
mkdir "${cmd_args[@]}"
echo dir*/*
But how can I get this echoed to the screen in a way that makes sense - i.e. showing the user a command that they could then type, or that I could keep in a log file for future reference? All these look (basically) the same:
echo runnimg mkdir with arguments ${cmd_args[@]}
echo runnimg mkdir with arguments "${cmd_args[@]}"
echo "runnimg mkdir with arguments '${cmd_args[@]}'"
echo "runnimg mkdir with arguments '${cmd_args[*]}'"
==> runnimg mkdir with arguments '-p dir path/with spaces'
Which is clearly the wrong command. This is not showing the user a command that they could then type, or that I could keep in a log file and reproduce at a future date. I want to see:
runnimg mkdir with arguments '"-p" "dir path/with spaces"'
I thought about using cat<<EOF
:
cat<<EOF
"${cmd_args[@]}"
EOF
but in fact, that generates one big quote around the whole argument list! What gives here? How could this ever be my intent? If it was, then I have "${cmd_args[*]}"
.
So that is the challenge. Print the command in a way that the user can say, "yes, that is the correct command".
Sorry to those saying "%p\n", but while that might be OK for a log file, but still a pain to go back and format it "unabiguously" in order to retest the command, it isn't really good enough for the interactive "this is the command feedback to the user".
Perhaps there is an answer already, but if so it is swamped by all the "always quote your arguments" type answers.
Based on BashFAQ/050:
#!/bin/bash
trap 'printf RUNNING:\ %s\\n "$BASH_COMMAND" >&2' DEBUG
foo () {
printf '%s\n' "$@" > /dev/null
}
foo bar baz
foo 'qux bazinga' '1 2' 3 '4 5'
foo "I'm going home"
The DEBUG
trap outputs:
RUNNING: foo bar baz
RUNNING: foo 'qux bazinga' '1 2' 3 '4 5'
RUNNING: foo "I'm going home"
Showing the quotes as they appear in the script.
The function foo
is set to output to /dev/null
so we can ignore its output. It's just a stand-in for whatever commands your script may actually be running.
Here is a version that puts the trap
code in a function and calls that function to turn the trap on and off. This is useful if you only want the trap to handle certain sections of code:
#!/bin/bash
dbt () {
if [[ $1 == on ]]
then
trap 'printf RUNNING:\ %s\\n "$BASH_COMMAND" >&2' DEBUG
elif [[ $1 == off ]]
then
trap '' DEBUG
else
printf '%s\n' "Invalid action: $1"
fi
}
foo () {
printf '%s\n' "$@" > /dev/null
}
foo bar baz
dbt on
foo 'qux bazinga' '1 2' 3 '4 5'
foo "I'm going home"
now=$(date)
dbt off
declare -a array_b
dbt on
array_b=(a b 'c d' e)
which outputs:
RUNNING: foo 'qux bazinga' '1 2' 3 '4 5'
RUNNING: foo "I'm going home"
RUNNING: now=$(date)
RUNNING: dbt off
RUNNING: array_b=(a b 'c d' e)
Another way to trace execution of a script is to use set -x
.
Print a trace of simple commands, for commands, case commands, select commands, and arithmetic for commands and their arguments or associated word lists after they are expanded and before they are executed. The value of the PS4 variable is expanded and the resultant value is printed before the command and its expanded arguments.