How to pass 2>/dev/null as a variable?

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.


Why does a hard-coded argument work but not an argument as a variable?

Because syntax items aren't interpreted from expanded variable values. That is, variable expansion isn't the same as replacing the variable reference with the text of the variable in the command line. (Stuff like ;, |, && and quotes etc. are also not special in the values of variables.)

What you could do is to use aliases, or use the variable to hold just the target of the redirection.

Aliases are just a text replacement, so they can hold syntactic items, like operators and keywords. In a script, you'd need to shopt expand_aliases, since by default they're disabled in non-interactive shells. So, this prints 2 (only):

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2

(And you could also alias jos=if niin=then soj=fi and then write all your if-statements in Finnish. I'm sure anyone reading the script would love you.)

Alternatively, write the redirection always, but control just the target with a variable. You'll need a no-op target for the case where you don't want to change where the output goes, though but /dev/stderr should work in that case. Actually, adding 2> /dev/stderr isn't a no-op because of the way Linux treats fd's opened from /proc/<pid>/fd as independent from the original. This affects the positioning of the write position and will mess up the output if it goes to a regular file.

It should work in append mode, though (or if stderr goes to a pipe or to a terminal):

#!/bin/sh
exec 2>/tmp/error.log
dst=/dev/null
ls -l /nosuchfile-1 2>> "$dst"     # this doesn't print
dst=/dev/stderr
ls -l /nosuchfile-2 2>> "$dst"
ls -l /nosuchfile-3 2>> "$dst"

So to repeat: 2> /dev/stderr can break.


Question title: "How to pass 2>/dev/null as a variable?" This can actually be done using eval

joshua@nova:/tmp$ X=">/dev/null"
joshua@nova:/tmp$ echo $X
>/dev/null
joshua@nova:/tmp$ eval echo $X
joshua@nova:/tmp$ eval echo hi
hi
joshua@nova:/tmp$ eval echo hi $X
joshua@nova:/tmp$ echo hi $X
hi >/dev/null
joshua@nova:/tmp$ 

So we can rewrite as

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
local RobWebAddress2
local DownloadName2
[[ $fCron == true ]] && HideErrors="2>/dev/null"
RobWebAddress2='"$RobWebAddress"'
DownloadName2='>"$DownloadName"'

eval google-chrome --headless --disable-gpu --dump-dom \
    $RobWebAddress2 $DownloadName2 "$HideErrors"

Where the indirect variable access prevents expansion from happening too soon on the rest of the command line.

Double quotes in variables work just fine.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$