17

Is there any way to redirect stdout and stderr via variable like adding command options in script?

For example I have a script:

#!/bin/bash -x
TEST=">/dev/null 2>&1"
OPT='-p -v'
mkdir $OPT 123/123/123 $TEST

I can see that OPT is replaced with -p without any problems and bash interprets it as option. But redirection interprets as directories name.

$ ./test.sh 
+ TEST='>/dev/null 2>&1'
+ OPT='-p -v'
+ mkdir -p -v 123/123/123 '>/dev/null' '2>&1'
mkdir: created directory `123/123'
mkdir: created directory `123/123/123'
mkdir: created directory `>/dev'
mkdir: created directory `>/dev/null'
mkdir: created directory `2>&1'

Is there any way to say bash, that $VAR is redirection, not a dirs names.

PS. May be I'm on wrong way, but I want to make optional verbose or non verbose output from my script. But I need some output even in non-verbose mode, therefore I can't just redirect whole stdout and stderr, only from some commands inside of my script.

16

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.

  • Great. This is exactly the thing I watched for. Thank you. – rush Jan 10 '12 at 13:45
5

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
3

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
1

You have a lot of spaces in your variables, which will not be evaluated properly. You will want to use eval to set that up.

#!/bin/bash -x
TEST=">/dev/null 2>&1"
OPT='-p -v'
eval mkdir $OPT 123/123/123 $TEST

This will allow $OPT to be split into two arguments (-p and -v) instead of one (-p -v) and the same with $TEST. Also changed to use /dev/null since it is very unlikely that you will have a dev directory in the current directory.

0

enzotib's answer uses some good file descriptor magic, but a simpler solution would be to just use eval the line (which does add process overhead, however):

$ rm /tmp/foo
$ ll /tmp/foo
ls: cannot access /tmp/foo: No such file or directory
$ FOO=">/tmp/foo"
$ date $FOO
date: invalid date '>/tmp/foo'
$ cat /tmp/foo
cat: /tmp/foo: No such file or directory
$ eval date $FOO
$ cat /tmp/foo
Thu Dec 13 14:08:17 EST 2018

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.