开发者

Bash - interpreting the contents of a variable

How can I make Bash interpret the contents of开发者_开发百科 a variable as I/O redirects and not simply pass those contents to the command being executed. Take this script for example:

#!/bin/bash
test "$1" == '--log' && LOGGING="2>&1 | tee time.log" || LOGGING=""
date $LOGGING

The desired behavior is that when I run this script with the --log option bash wil execute

$ date 2>&1 | tee time.log

If I don't specify --log then it simply outputs date without creating a log. Instead it passes the contents of $LOGGING to date as a CLI argument resulting in an error:

 date: extra operand `|' Try `date
 --help' for more information.

Is there way to do this without writing something like

#!/bin/bash
test "$1" == '--log' && date 2>&1 | tee time.log || date

The actual application is obviously much more complicated than just calling "date" so I'd like to avoid copying and pasting that command twice in an if else just to append the redirect and logging commands.


If your script is rather long and you want to log all stdout and stderr when --log is passed in, I suggest using exec to redirect everything. See this excellent article:

http://www.linuxjournal.com/content/bash-redirections-using-exec

#!/bin/bash
if [[ "$1" == '--log' ]]; then
    npipe=/tmp/$$.tmp
    trap "rm -f $npipe" EXIT
    mknod $npipe p
    tee <$npipe log &
    exec 1>&-
    exec 1>$npipe
fi

date
# and any other commands after this will be logged too.

The interesting thing about this approach is that you can also prepend all logged lines with a timestamp using perl or gawk or some other utility:

#!/bin/bash
if [[ "$1" == '--log' ]]; then
    npipe=/tmp/$$.tmp
    trap "rm -f $npipe" EXIT
    mknod $npipe p
    perl -pne 'print scalar(localtime()), " ";' < $npipe | tee time.log &
    exec 1>&-
    exec 1>$npipe 2>&1
fi

echo hello world
echo hello world 2

After running this, time.log will contain:

$ cat time.log 
Wed Jun  8 13:28:45 2011 hello world
Wed Jun  8 13:28:45 2011 hello world 2

The drawback here is that the timestamp are printed to your terminal too.


You can use eval:

eval date $LOGGING


The problem is that by putting the command in a variable, you are effectively converting everything to strings instead of leaving it as Bash keywords. Try appending -x to the shebang line:

$ ./test.sh --log
+ test --log == --log
+ LOGGING='2>&1 | tee time.log'
+ date '2>&1' '|' tee time.log
date: extra operand `|'
Try `date --help' for more information.

Try this:

#!/bin/bash -x
logging() {
    if [ "$1" == '--log' ]
    then
        cat 2>&1 | tee time.log
    else
        cat
    fi
}
date | logging "$1"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜