bash: Use a variable to store stderr|stdout redirection

Another solution could be the following:

#!/bin/bash

verbose=0

exec 3>&1
exec 4>&2

if ((verbose)); then
  echo "verbose=1"
else
  echo "verbose=0"
  exec 1>/dev/null
  exec 2>/dev/null
fi

echo "this should be seen if verbose"
echo "this should always be seen" 1>&3 2>&4

Then add 1>&3 2>&4 only to commands of which you want to see the output.


The "how" has been well explained in other answers; I want to address the "why" the OP's code doesn't work.

The keynote is that output redirections are marked before variable expansion. Redirections are actually performed after variable expansion (hence why you can redirect output to a filename which is stored in a variable), but the shell identifies the redirections for later processing before variables are expanded.

In other words, once variables are expanded it is "too late" for a redirection character (< or >, etc.) to be considered, because the shell has already identified which parts of the command string it is going to handle as redirections.

For further reading, see steps 1 and 3 listed under:

LESS='+/^SIMPLE COMMAND EXPANSION' man bash

It is not interpreting it as a "directory name", > is being quoted, so it being treated literally (more specifically, you are sending the string >dev/null 2>&1. Your only way of getting around this is using eval or spawning a new shell.

As for your "verbose" issue alluded to in your question, just do this instead:

verbose=1
if (( verbose )); then
    mkdir -v -p /foo
else
    mkdir -p /foo > /dev/null 2>&1
fi