5

I've found that the results of my bash script will change depending upon if I execute it with debugging or not (i.e. invoking set -x). I don't mean that I get more output, but that the result of the program itself differs.

I'm assuming this isn't the desired behavior, and I'm hoping that you can teach me how to correc this.

The bash script below is a contrived example, I tried reducing the logic from the script I'm investigating so that the problem can be easily reproducible and obvious.

#!/bin/bash

# Base function executes command (print working directory) stores the value in
# the destination and returns the status.
function get_cur_dir {
    local dest=$1
    local result

    result=$((pwd) 2>&1)
    status=$?

    eval $dest="\"$result\""
    return $status
}

# 2nd level function uses the base function to execute the command and store
# the result in the desired location. However if the base function fails it
# terminates the script. Yes, I know 'pwd' won't fail -- this is a contrived
# example to illustrate the types of problems I am seeing.
function get_cur_dir_nofail {
    local dest=$1
    local gcdnf_result
    local status

    get_cur_dir gcdnf_result
    status=$?
    if [ $status -ne 0 ]; then
        echo "ERROR: Command failure"
        exit 1
    fi

    eval dest="\"$gcdnf_result\""
}


# Cause blarg to be loaded with the current directory, use the results to
# create a flag_file name, and do logic with the flag_file name.
function main {
    get_cur_dir blarg

    echo "Current diregtory is:$blarg"

    local flag_file=/tmp/$blarg.flag

    echo -e ">>>>>>>> $flag_file"

    if [ "/tmp//root.flag" = "$flag_file" ]; then
        echo "Match"
    else
        echo "No Match"
    fi
}


main

.

.

When I execute without the set -x it works as I expect as illustrated below:

Current diregtory is:/root
>>>>>>>> /tmp//root.flag
Match

.

.

However, when I add the debugging output with -x it doesn't work, as illustrated below: root@psbu-jrr-lnx:# bash -x /tmp/example.sh

+ main
+ get_cur_dir blarg
+ local dest=blarg
+ local result
+ result='++ pwd
/root'
+ status=0
+ eval 'blarg="++ pwd
/root"'
++ blarg='++ pwd
/root'
+ return 0
+ echo 'Current diregtory is:++ pwd
/root'
Current diregtory is:++ pwd
/root
+ local 'flag_file=/tmp/++ pwd
/root.flag'
+ echo -e '>>>>>>>> /tmp/++ pwd
/root.flag'
>>>>>>>> /tmp/++ pwd
/root.flag
+ '[' /tmp//root.flag = '/tmp/++ pwd
/root.flag' ']'
+ echo 'No Match'
No Match
root@psbu-jrr-lnx:#  
John Rocha
  • 1,656
  • 3
  • 19
  • 30
  • 1
    Try `result=$((pwd) 2>&1)` => `result=$(pwd 2>&1)` – Joachim Isaksson Apr 29 '13 at 16:37
  • That did it Joachim. Do you want to change your comment to an answer so I can credit you? I had used the original syntax after reading http://stackoverflow.com/questions/3130375/bash-script-store-stderr-in-variable, but upon re-reading the post more carefully I see that they had double re-direction. – John Rocha Apr 29 '13 at 16:47
  • The blatant overuse of `eval` complicates your script. As far as I can tell, `eval dest="\"$gcdnf_result\""` should just be `dest=$gcdnf_result` and I have a hunch the rest of the logic could also be simplified quite a bit for improved stability and maintainability. – tripleee Apr 29 '13 at 17:47
  • Hello triplee. The use eval is necessary for causing the value to be returned in the passed in parameter. This technique is cited from a couple different sites that I stumbled across. From http://www.linuxjournal.com/content/return-values-bash-functions the explanation is "The eval statement basically tells bash to interpret the line twice, the first interpretation above results in the string result='some value' which is then interpreted once more and ends up setting the caller's variable." – John Rocha Apr 29 '13 at 22:34
  • FYI -- in bash 4.0, you can explicitly send output from `set -x` to a different file descriptor, by putting its number in `BASH_XTRACEFD`. Thus, `exec 3>trace.log; BASH_XTRACEFD=3` will avoid any potential for errors of this type. (In 4.1, you can use `exec {BASH_XTRACEFD}>trace.log` to allocate a free FD, but its number in a variable, and run the redirection all in one line). – Charles Duffy Aug 03 '15 at 16:50

3 Answers3

4

I think what happens is you capture the debugging logging output produced by the shell when you run it with set -x, this line, for example, does it:

 result=$((pwd) 2>&1)

In the above line you shouldn't really need to redirect standard error to standard output, so remove 2>&1.

piokuc
  • 25,594
  • 11
  • 72
  • 102
  • Thanks for the suggestion. But I DO need to capture standard error. It's true that for this example I don't need to capture it, but in the production code we are using an underlying command (that replaces PWD) can fail and we need to see/capture that failure message. But I also understand what you are saying. Its that the debug output comes as stderr and because I'm capturing it, it causes this problem. Now to figure out how to do both! ;) – John Rocha Apr 29 '13 at 16:41
3

Changing...

result=$((pwd) 2>&1)

...into...

result=$(pwd 2>&1)

...will allow you to capture the output of pwd without capturing the debug info generated by set -x.

Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294
0

The reason the the $PWD variable exists is to free your script from having to run a separate process or interpret its output (which in this case has been modified by -x). Use $PWD instead.

stark
  • 12,615
  • 3
  • 33
  • 50
  • 1
    Thanks for the suggestion Stark, but your answer missed the point of the question. The example above is contrived so that it's easy to illustrate the problem. I choose `pwd` for the example, but in reality we have many other commands that can pass/fail that get used in place of `pwd`. – John Rocha Apr 29 '13 at 17:06