5

In a bash script I have a log() function that is used in several places, as well as a logs() function that diverts lots of line to log(). When I run parts of the script with set -x, obviously all commands within logs() and log() are traced too.

I would like to define logs() and log() such that at least their content, at best even their call is suppressed from set -x output.

3 Answers 3

4

You cannot change how the function is invoked from inside the function, but you could define the function to invoke a subshell which then immediately turns off tracing; notice the parenthesis surrounding the body instead of the typical braces:

log() (
  set +x
  # rest of log()
)

Invoking log() then generates only the call itself (from the existing set -x code) and the set +x call, not the rest of the subsequent commands in the function. The existing set -x setting is restored once the function exits.

2
  • You do need to set it back on at the end of log().
    – Chiel
    Commented Mar 11, 2022 at 11:44
  • 2
    Are you sure? The function is defined to run in a subshell (the second set of parenthesis), so the local settings there go away with that shell.
    – Jeff Schaller
    Commented Mar 11, 2022 at 11:51
3

A quick and dirty hack that should work in all shells is to (temporarily) make your log an external script instead of a function.

In bash you could also use a trap '...' DEBUG and shopt -s extdebug combination, which is much more versatile than set -x. Example:

debug() {
        local f=${FUNCNAME[1]} d=${#FUNCNAME[@]} c=$BASH_COMMAND
        if [ "$NOTRACE" ]; then
                case $debug_skip in ''|$d) debug_skip=;; *) return;; esac
                eval "case \$c in $NOTRACE) debug_skip=\$d; return; esac"
        fi

        # before the 1st command in a function XXX
        case $c in $f|"$f "*) return;; esac

        printf >&2 "%*s(%s) %s\n" $((d * 2 - 4)) "" "$f" "$c"
}

(Of course, you can dispense with the strange format + indenting and make it completely set-x-like; you can also redirect it to another file instead of mixed with the stderr from commands.)

Then:

NOTRACE='"log "*'
shopt -s extdebug
trap debug DEBUG

log(){ log1 "$@"; }; log1(){ log2 "$@"; }
log2(){ log3 "$@"; }; log3(){ echo "$@"; }

foo(){ foo1 "$@"; }; foo1(){ foo2 "$@"; }
foo2(){ foo3 "$@"; }; foo3(){ echo "$@"; }

bar(){ case $# in 0) ;; *) echo "$1"; shift; bar "$@";; esac; }

foo 1 2 3
log 7 8 9
bar 1 2 3 4

will result in:

(main) foo 1 2 3
  (foo) foo1 "$@"
    (foo1) foo2 "$@"
      (foo2) foo3 "$@"
        (foo3) echo "$@"
1 2 3
7 8 9
(main) bar 1 2 3 4
  (bar) case $# in
  (bar) echo "$1"
1
  (bar) shift
    (bar) case $# in
    (bar) echo "$1"
2
    (bar) shift
      (bar) case $# in
      (bar) echo "$1"
3
      (bar) shift
        (bar) case $# in
        (bar) echo "$1"
4
        (bar) shift
          (bar) case $# in
1

To suppress xtrace output, just use set -x. To turn it back on, use set -x. To detect if it's on, use shopt -q -o xtrace. eg

log() {
  if shopt -q -o xtrace; then TRACE_IS_ON=yes; else TRACE_IS_ON=no; fi
  set +x # turn xtrace off
  ...
  [[ "$TRACE_IS_ON" == "yes" ]] && set +x
}

There's still some 'chatter' but that's the best I can do. To supress seeing the calls to log() you can surround each call to log() with a similar code instead of inside the function.

1
  • 1
    set -x does not suppress xtrace output, set +x does.
    – l0b0
    Commented Dec 12, 2022 at 23:53

You must log in to answer this question.

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