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 (ie 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.
Dynamic silent redirection
#!/usr/bin/env sh
cmd() {
silent=$1
if [ true = "$silent" ]
then out=/dev/null
else out=/dev/stdout
fi
sudo dnf update > "$out"
}
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).
You have to use eval
to process shell operations in the variable expansion.
declare -a cmd=("sudo" "dnf" "update" "&>/dev/null")
eval "${cmd[@]}"
If you want to avoid duplicate "${cmd[@]}"
code, you can use a subshell:
(
[[ $silent == true ]] && exec &>/dev/null
exec "${cmd[@]}"
)
I suggest the use of exec
if cmd
will always be an external command.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.