You should use "${@}" instead of ${@} (like with echo "${@}") but that is not the reason for your problem.
The reason is that redirection takes places very early in command line parsing, before parameter substitution. Thus after the variable has put > in the command line, the shell is not looking for > any more.
An important point which I noticed after I published my answer: With a call like
run mysqldump blabla > dump.sql
the function run does not see > and dump.sql. That is probably not what you want because it prevents you from changing all the redirections with a single environment variable, as the output of echo "${@}" is redirected to the file then, too. Thus, you should use something like run --redirect dump.sql mysqldump blabla, see below.
There are two possibilities:
Stick with "$@" and use eval. This may take you to a quoting nightmare, of course. You have to quote everything except for the > so that the shell sees a bare > in the command line before it does quote removal.
Handle the redirection separately:
run --redirect dump.sql mysqldump blabla
run() {
if [ "$1" == '--redirect' ]; then
shift
redirect_target="$1"
shift
else
redirect_target='/dev/stdout' # works at least with Linux
fi
if [[ "$(printenv DRY_RUN)" = "yes" ]]
then
echo "${@}"
else
"${@}" > "$redirect_target"
fi
}
You can avoid the redirect_target='/dev/stdout' if you put the if [ "$1" == '--redirect' ] in the else branch of if [[ "$(printenv DRY_RUN)" = "yes" ]].
if [[ "$(printenv DRY_RUN)" = "yes" ]]
then
if [ "$1" == '--redirect' ]; then
# do not output "--redirect file"
shift
shift
fi
echo "${@}"
else
if [ "$1" == '--redirect' ]; then
shift
redirect_target="$1"
shift
"${@}" > "$redirect_target"
else
"${@}"
fi
fi
$(printenv DRY_RUN)? Why not just$DRY_RUN?