5

I'm trying to add a debug option to a script of mine. Normally I want to hide any output, like warnings etc, so I put >/dev/null 2>&1 behind a lot of commands.

Now, when I want to debug my script, I would have to manually remove those, or put every command inside an if ... fi testing for a $DEBUG Variable.

I thought putting >/dev/null 2>&1 inside a Variable ($REDIR) and writing command arg1 $REDIR would do the trick. If I want to debug, I would only need to leave $REDIR empty.

But a short test on my shell showed me, that it won't work that way:

~$ echo "bla" >/dev/null 2>&1
~$ REDIR=>/dev/null 2>&1
~$ echo "bla" $REDIR
bla
~$

Using " or ' around >/dev/null 2>&1 didn't work either for obvious reasons.

So why does my idea not work here? Did I misunderstand something about putting commands etc. into Variables and calling them?

5 Answers 5

7

Redirection is not a command, therefore you can't execute it in this manner. You can get it done if you use eval, but that's opening a can of worms.

A better method to do what you're trying to do is have a function for debugging output:

function debugprint {
    if [ ! -z "$debug" ]; then
        echo "$1"
    fi
}

debugprint "$(echo 'bla' 2>&1)"

This will redirect standard error into standard output, and then call debugprint with the output as an argument. Now all you need to do is set $debug to something non-empty when you want to debug.

Of course, that's also opening a (different) can of worms (related to quoting). You may want to just use set -x instead, which may or may not do enough for your debugging needs.

2
  • Seems to be a surprisingly complicated thing I'm trying to do. Nice solution, though. Thanks.
    – Minix
    Jun 8, 2015 at 14:13
  • you might whish to run script as bash myscript arg1 arg2 (normal mode) or bash -x myscript arg1 arg2 (debug mode)
    – Archemar
    Jun 8, 2015 at 14:16
5

For such a purpose I usually define a function like run. This can correctly handle args with spaces and others in most cases.

#!/bin/bash

run() {
        if $DEBUG; then
                v=$(exec 2>&1 && set -x && set -- "$@")
                echo "#${v#*--}"
                "$@"
        else
                "$@" >/dev/null 2>&1
        fi
}

DEBUG=false
run echo "bla"

DEBUG=true
run echo "bla"
run printf "%s . %s . %s\n" bla "more bla" bla

Output:

$ bash debug.sh 
# echo bla
bla
# printf '%s . %s . %s\n' bla 'more bla' bla
bla . more bla . bla
2
  • After seeing your answer I thought: since there is no output I need, other than debug output, I could test for the $DEBUG variable and then exec >/dev/null; exec 2>&1 at the start of the script. I wonder if that would interfere with any pipes.
    – Minix
    Jun 9, 2015 at 8:33
  • @Minix Well, it's up to you. That would be OK if no granularity needed on debug control, now and in the future. Once permanently redirected, you cannot recover them later in the script, unless having saved them to yet other fds. See mikeserv's solution.
    – yaegashi
    Jun 9, 2015 at 8:59
2

In your case echo is treating $REDIR as a string argument. You want something like:

~$ echo "bla" >/dev/null 2>&1
~$ REDIR='>/dev/null 2>&1'
~$ eval "echo bla $REDIR"
~$

However, unless you are trying to do a quick and dirty hack, Wouter Verhelst has the better solution (and it's really not that long or complicated).

2

perhaps you could use a function to pipe the output to and let it do the redirection:

#! /bin/bash

DEBUGMODE="Debug"

function handleStdOut {
    if [[ "$DEBUGMODE" == "Debug" ]]
    then
        echo "Debugging..."
        cat
        echo "Done"
    else
         cat > /dev/null
    fi 
}

echo "bla" | handleStdOut
1
  • But I would still have to write 2>&1 behind every command, right?
    – Minix
    Jun 9, 2015 at 8:23
2

For every shell function I write I do this exact same thing.

fn(){ 
    echo some normal stderr debug stuff             >&2     #if $DBG 2>stderr
    dd if="\$DBG/please/report/on/this/file"                #ditto
    echo I DEFINITELY need to handle this           >&3     #always stderr
    ( PATH=; ".some" oops I expect to handle )     2>&4     #always /dev/null
    echo and the regular stuff                              #always unaffected
}   4<>/dev/null 3>&2 2>&"$((${#DBG}?3:4))"

I like that for a few reasons.

  1. Using the ${#DBG} evaluation for len always guarantees a >=0 integer value for the test - regardless of what $DBG might actually contain. This makes the math safe even if DBG=IFS=0, for example.

  2. Each time the function runs it only has to do the actual open() on /dev/null the one time - any other time I redirect to /dev/null I'm doing it to an established descriptor in #fd>&4.

  3. Debug output - as I might enable with set -x - for functions in my interactive shells is dumped by default unless I explicitly set the environment variable $DBG to any not-null value.

    • When $DBG is not-null the math expansion for the redirection will point at itself - it evaluates to 2>&3.
    • But otherwise it evaluates to 2>&4 and so goes to the open /dev/null descriptor.
    • I usually don't need to see 20 lines of execution trace for a function which I've already ok'd and saved to ~/.sh/fn/..., but the command-line in which it participates is probably another matter altogether if I've set -x.
    • It's also handy when $DBG is null/unset but set -x is enabled because it opens a per-command eval outlet in $PS4 which doesn't make it to stderr - but #fd>&3 can still get there.
  4. It enables inheritance in a sane way.

    • A function which calls others cannot affect the $DBG value in any way which might enable them to begin writing to stderr by default if it already cannot.
    • If $DBG is not set or null all child functions it calls lose even #fd3's significance unless it calls them with child_fn 2>&3 - in which case they will get the same opportunity their parent did to write to stderr explicitly.
    • It can also unset $DBG to quiet those child functions even when $DBG is already set.
    • And it can set $DBG so that top-level functions called after it will enable default stderr output.
  5. I retain a copy of stderr on #fd>&3 and so if the function must it can still write to stderr explictly on that descriptor (except as noted above).

  6. The descriptors close themselves and do not affect current shell values for fds 2,3,4 because they are only associated with the compound command which wraps the function.

    • Cleaning up with {3,4}>&- is never necessary.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .