14

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.

Gyom
  • 3,773
  • 5
  • 29
  • 38
  • 3
    Possible duplicate of [Bash set +x without it being printed](http://stackoverflow.com/questions/13195655/bash-set-x-without-it-being-printed) – Beetle Jan 04 '17 at 17:55

5 Answers5

14

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.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
7

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
Dave Dopson
  • 41,600
  • 19
  • 95
  • 85
  • I LOVE this for reasons other than what caused me to google this question! Thank you for not sticking to OP question Description. (It really annoys me when people complain about answers that loosely apply to OP Topic. It's about the 1,000,000s who will come after the 1 OP.) – Bruno Bronosky Nov 16 '20 at 18:00
  • 2
    Thanks, although I'd say that I am answering the OP, it's just that I'm answering the unasked question of how to accomplish what they were trying to accomplish with their "set -x" and "set +x" pairs, rather than being constrained by the literal question that they asked. I've found that to be a useful tactic in many professional settings. People tend to ask for something hyper-specific that's merely an example of what a solution to their problem might look like. – Dave Dopson Nov 28 '20 at 23:10
1

Next command disables 'xtrace' option:

$ set +o xtrace

Jc.ahn
  • 19
  • 1
  • Is there any reason to prefer that over `set +x`? – Charles Duffy Apr 23 '19 at 13:43
  • 2
    To be clear, the reason this isn't responsive is that the OP asked for "silently". This isn't silent; `set +o xtrace` is included in the trace log, just as `set +x` is in the trace log in the OP's original code (which is what they asked the question to know how to solve). – Charles Duffy Jan 02 '20 at 16:01
1

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.

bobbogo
  • 14,989
  • 3
  • 48
  • 57
0

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.

shellter
  • 36,525
  • 7
  • 83
  • 90