Gyom
Gyom

Reputation: 3920

how to silently disable xtrace in a shell script?

I'm writing a shell script that loops over some values and run a long command line for each value. I'd like to print out these commands along the way, just like make does when running a makefile. I know I could just "echo" all commands before running them, but it feels inelegant. So I'm looking at set -x and similar mechanisms instead :

#!/bin/sh

for value in a long list of values
do
    set -v
    touch $value # imagine a complicated invocation here
    set +v
done

My problem is: at each iteration, not only is the interresting line printed out, but also the set +x line as well. Is it somehow possible to prevent that ? If not, what workaround do you recommend ?

PS: the MWE above uses sh, but I also have bash and zsh installed in case that helps.

Upvotes: 15

Views: 7430

Answers (5)

bobbogo
bobbogo

Reputation: 15483

This seems to be to do with when the redirection to stderr takes effect.

To get the effect you require, simply run the set in a list:

echo $-
{ set +x; } 2> /dev/null
echo $-

giving you

himxBHs
himBHs

Quite a bit cheaper than a subshell.

Upvotes: 1

shellter
shellter

Reputation: 37288

I thought of

 set -x >/dev/null 2>&1; echo 1; echo 2; set +x >/dev/null 2>&1

but got

+ echo 1
1
+ echo 2
2
+ 1> /dev/null 2>& 1

I'm surprised by these results. .... But

set -x ; echo 1; echo 2; set +x
+ echo 1
1
+ echo 2
2

looks to meet your requirement.

I saw similar results when I put each statement on its only line (excepting the set +x)

IHTH.

Upvotes: 0

Jc.ahn
Jc.ahn

Reputation: 19

Next command disables 'xtrace' option:

$ set +o xtrace

Upvotes: 1

Dave Dopson
Dave Dopson

Reputation: 42704

You want to try using a single-line xtrace:

function xtrace() {
  # Print the line as if xtrace was turned on, using perl to filter out
  # the extra colon character and the following "set +x" line.
  (
    set -x
    # Colon is a no-op in bash, so nothing will execute.
    : "$@"
    set +x
  ) 2>&1 | perl -ne 's/^[+] :/+/ and print' 1>&2
  # Execute the original line unmolested
  "$@"
}

The original command executes in the same shell under an identity transformation. Just prior to running, you get a non-recursive xtrace of the arguments. This allows you to xtrace the commands you care about without spamming stederr with duplicate copies of every "echo" command.

# Example
for value in $long_list; do
  computed_value=$(echo "$value" | sed 's/.../...')
  xtrace some_command -x -y -z $value $computed_value ...
done

Upvotes: 7

Charles Duffy
Charles Duffy

Reputation: 295472

Sandbox it in a subshell:

(set -x; do_thing_you_want_traced)

Of course, changes to variables or the environment made in that subshell will be lost.

If you REALLY care about this, you could also use a DEBUG trap (using set -T to cause it to be inherited by functions) to implement your own set -x equivalent.

For instance, if using bash:

trap_fn() {
  [[ $DEBUG && $BASH_COMMAND != "unset DEBUG" ]] && \
    printf "[%s:%s] %s\n" "$BASH_SOURCE" "$LINENO" "$BASH_COMMAND"
  return 0 # do not block execution in extdebug mode
}
trap trap_fn DEBUG

DEBUG=1
# ...do something you want traced...
unset DEBUG

That said, emitting BASH_COMMAND (as a DEBUG trap can do) is not fully equivalent of set -x; for instance, it does not show post-expansion values.

Upvotes: 16

Related Questions