The reason you can't cause redirection to occur by expanding "$HideErrors"
is that symbols like >
aren't treated specially after being produced by parameter expansion. This is actually very good, because such symbols appear in text you might want to expand and use literally.
This holds whether or not you quote $HideErrors
. The result of parameter expansion is subject to word splitting and globbing when the expansion is unquoted, but that's it.
As for what to do about it, there are numerous ways to achieve conditional redirection. For a very simple command, it may be reasonable write the whole command twice, once in each branch of a case
or if
-else
construct. This soon becomes burdensome, however, and the command you showed is certainly a case where that would not be ideal.
Of the approaches that let you avoid repeating yourself, there are two I especially recommend, because they are quite clean and easy to get right. You'd want to use just one of these, not both at once for the same command and redirection.
Store the command instead of the redirection. Instead of attempting to store the redirection in a variable and applying parameter expansion, store the command in a shell function. Then write a case
or if
-else
, in which the function is called with the redirection on one branch and without it on the other.
If you conceptualize your command as code that you want to write once but run under multiple circumstances, then a function is the natural solution. This is what I usually do. It has the benefit of requiring neither a subshell nor manual storage and resetting of state.
With your code:
launch() {
google-chrome --headless --disable-gpu --dump-dom \
"$RobWebAddress" > "$DownloadName"
}
case $fCron in
true) launch 2>/dev/null;;
*) launch;; # Get silly error messages when running from terminal
esac
You can apply whatever spacing you like, or if
-else
instead if you prefer. Note that launch
automatically uses the caller's RobWebAddress
and DownloadName
variables, even if they are local variables, because Bash is dynamically scoped, unlike most programming languages which are lexically scoped.
Run the command in a subshell and conditionally apply the redirection to exec
. This is what steeldriver commented about, but inside (
)
to keep the effect local. When the exec
builtin is run with no arguments, it doesn't replace the current shell with a new process, but instead applies any of its redirections to the current shell.
(It's also possible to keep track of what standard error was and restore it, without using a subshell and thus without sacrificing the ability to modify the current shell's environment. I'll leave the details of that to other answers, though.)
With your code:
(
# Suppress silly error messages unless running from terminal
case $fCron in true) exec 2>/dev/null;; esac
google-chrome --headless --disable-gpu --dump-dom \
"$RobWebAddress" > "$DownloadName"
)
After the closing )
, standard error is in effect restored to whatever it was before, because it's only really being redirected in the subshell, and not in the parent shell. This, too, works fine with the existing shell variables, since subshells get a copy of those. Although I prefer to use a shell function, I admit this method may require less code.
Both methods work regardless of what file or device standard error starts out as, including in the case of redirections applied to shell functions that call the code that contains the conditional behavior, as well as the case (mentioned in your edit) where standard error for the whole script has already been redirected by a previous exec 2>&fd
or exec 2> path
. That the path was produced by process substitution is no problem.
[[ $fCron == true ]] && exec 2>/dev/null
instead