Issue
I am storing a command to execute in a bash array, example:
declare -a cmd=("sudo" "dnf" "update")
"${cmd[@]}"
Last metadata expiration check: 0:24:45 ago on Fri 07 Jan 2022 03:35:34 PM EST.
Dependencies resolved.
Nothing to do.
Complete!
Now, say I want to redirect the output to make it less noisy. This works:
"${cmd[@]}" &>/dev/null
But I would prefer to store the redirect with the command array so it can be added/removed like any other command in the array:
declare -a cmd=("sudo" "dnf" "update" "&>/dev/null")
"${cmd[@]}"
Last metadata expiration check: 0:29:14 ago on Fri 07 Jan 2022 03:35:34 PM EST.
No match for argument: &>/dev/null
The output isn't being redirected, the final array element is just being passed like a normal argument. Is there any way to get this work (i.e. judicious use of eval) or a better strategy?
XY statement: I am trying to use conditionals to make my program output silent. I can do this with:
silent=true
cmd=("sudo" "dnf" "update")
if silent; then
"${cmd[@]}" &>/dev/null
else # Be noisy
"${cmd[@]}"
fi
This results in lots of duplicated code over the course of my program (every debug operation needs multiple command execution lines). Instead I would prefer appending the redirection to the array, such as:
silent=true
cmd=("sudo" "dnf" "update")
$silent && cmd+=("&>/dev/null")
"${cmd[@]}"
This strategy works great for functions and arguments but not for redirections. While I can apply --quiet flags to some programs to achieve this, in some cases I would like to redirect stderr, redirect to file, etc.
Solution
How about prepending to the array?
# provide a function that wraps the content
silence() { "$@" >/dev/null 2>&1; }
if [ "$silent" = true ]; then
cmd=( silence "${cmd[@]}" )
fi
"${cmd[@]}"
Of course, you could just use that wrapper unconditionally and make it responsible for the work:
maybe_silence() {
if [ "$silent" = true ]; then
"$@" >/dev/null 2>&1
else
"$@"
fi
}
maybe_silence "${cmd[@]}"
If you really want to be able to support arbitrary redirections (and other shell syntax), then it makes sense to have a wrapper that just applies one redirection and leaves everything else unmodified.
with_redirection() {
local redirections=$1 # first argument contains redirections to perform
shift || return # remove it from "$@"
local cmd # create a local variable to store our command
printf -v cmd '%q ' "$@" # generate a string that evals to our argument list
eval "$cmd $redirections" # run the string with the redirection following
}
...so you can run:
cmd=( with_redirection '&>/dev/null' sudo dnf update )
"${cmd[@]}"
...and only &>/dev/null
is subject to eval
-like behavior, while other contents are passed normally. You can even nest this:
testfunc() { echo "this is on stderr" >&2; }
cmd=( with_redirection '>out.txt' with_redirection '2>&1' testfunc
...and you end up with this is on stderr
in out.txt
(though of course, you could also run with_redirection '>out.txt 2>&1' testfunc
to get the same effect).
Answered By - Charles Duffy Answer Checked By - Marie Seifert (WPSolving Admin)