167

Does anyone know if we can say set +x in bash without it being printed:

set -x command set +x 

traces

+ command + set +x 

but it should just print

+ command 

Bash is Version 4.1.10(4). This is bugging me for some time now - output is cluttered with useless set +x lines, making the trace facility not as useful as it could be.

1
  • 1
    This doesn't answer your question, but when you run your script why not: script.sh 2>&1 | grep -v 'set +x' Commented Nov 4, 2012 at 16:16

7 Answers 7

254

I had the same problem, and I was able to find a solution that doesn't use a subshell:

set -x command { set +x; } 2>/dev/null 
Sign up to request clarification or add additional context in comments.

8 Comments

Great answer, just a note: without a semicolon after the command this won't work; and with a semicolon but without spaces to the braces, a syntax error will be raised.
This zeroes the exit status.
@GarthKidd Exit status is zeroed with every successful command. set +x is such a successful command
In cases where you need the exit status of command, this variation is a solution: { STATUS=$?; set +x; } 2>/dev/null. Then inspect $STATUS in subsequent lines at your leisure.
Separately, here's a version golfed slightly further: { set +x; } 2>&-. That closes fd 2 outright rather than make it point at /dev/null. Some programs don't handle that well when they try to print to stderr, which is why /dev/null is good style in general; but the shell's set -x tracing handles it just fine, so it works perfectly here, and it does make this incantation a bit shorter.
|
73

You can use a subshell. Upon exiting the subshell, the setting to x will be lost:

( set -x ; command ) 

7 Comments

Well, thanks... good point. Actually I'm aware of "the sub-shell trick." I hoped it could be easier than that. It involves substantial changes in the code, making the code complexer and less readable. IMHO that would be worse than living with the set +x lines...
I don't see how ( set -x \n command \n ) is any worse than set -x \n command \n set +x.
@chepner: You cannot set variables.
...and you cannot cd: it doesn't change the current directory in the parent shell.
Sorry, I'm not sure what I thought your actual objection was. It's been a long week...
|
9

I hacked up a solution to this just recently when I became annoyed with it:

shopt -s expand_aliases _xtrace() { case $1 in on) set -x ;; off) set +x ;; esac } alias xtrace='{ _xtrace $(cat); } 2>/dev/null <<<' 

This allows you to enable and disable xtrace as in the following, where I'm logging how the arguments are assigned to variables:

xtrace on ARG1=$1 ARG2=$2 xtrace off 

And you get output that looks like:

$ ./script.sh one two + ARG1=one + ARG2=two 

7 Comments

Clever trick (though you don't need the /dev/stdin part). The caveat is that turning on alias expansion in scripts can have unwanted side-effects.
You're right. I've edited the answer to remove the superfluous /dev/stdin. I'm not aware of any specific side effects, since the non-interactive environment shouldn't load any files that define aliases. What side effects might there be?
That's a good point - I forgot that aliases aren't inherited, so the risk is much smaller than I thought (hypothetically, your script could be sourcing third-party code that happens to define aliases, but I agree that's probably not a real-world concern). +1
@AndreasSpindler Could you elaborate on why you think this technique is more likely to leak more sensitive information?
...basically because aliases and functions are high-level constructs. set +x is much harder (if not impossible) to compromise. But it is still a good and straight solution - probably the best one so far.
|
9

How about a solution based on a simplified version of @user108471:

shopt -s expand_aliases alias trace_on='set -x' alias trace_off='{ set +x; } 2>/dev/null' trace_on ...stuff... trace_off 

1 Comment

How about this? function () { set_plus_x='{ set +x; } 2>/dev/null' }
4

This is a combination of a few ideas that can enclose a block of code and preserves the exit status.

#!/bin/bash shopt -s expand_aliases alias trace_on='set -x' alias trace_off='{ PREV_STATUS=$? ; set +x; } 2>/dev/null; (exit $PREV_STATUS)' trace_on echo hello trace_off echo "status: $?" trace_on (exit 56) trace_off echo "status: $?" 

When executed:

$ ./test.sh + echo hello hello status: 0 + exit 56 status: 56 

2 Comments

Nice effort but I think the () around exit not necessary. Ok. Maybe that's paranoid, but if this code is used in general you have a good attack vector: redefine trace_on and trace_off and inject code that reads executed commands. If you use such "utilties" alone it is instructive, but if the code is used with others you have to consider whether the benefits of such non-standardized functions outweigh the disadvantages. Personally I've settled with { set +x; } 2>/dev/null as this construct is commonly understood and too does not change the exit status.
Thanks. I've shared the approach with my colleagues and they love how it keeps both the output and the code less noisy. Regarding the () around the exit 56; that was simply so that I could demonstrate that the exit status of the subprocess was accessible in the bash script; without the parens, it would not have become a subprocess and the script would have exited at that point with status=65.
2

Here is another one. Often you only want to trace a particular command in your script. Then why not write a function that does just that?

> call() { set -x; "$@"; { set +x; } 2>/dev/null; } > call uname -a + uname -a CYGWIN_NT-6.1-WOW W530 3.1.7(0.340/5/3) 2020-08-22 19:03 i686 Cygwin > call make -j8 *.mak + make -j8 some_name.mak 

We can improve this further by returning (and tracing) the exit code of the called command:

> call() { local rc; set -x; "$@"; rc=$?; { set +x; } 2>/dev/null; return $rc; } > call true && echo yes + true + rc=0 yes > call false && echo yes + false + rc=1 

Here is a real life example that calls the Rhapsody CLI program to generate source code from a .rcl-file with command-line options:

die() { local c=${1:--1} m=${2:-'Died'} echo "$m at ${BASH_SOURCE[1]}:${FUNCNAME[1]} line ${BASH_LINENO[0]} (exit $c)" >&2 exit $c } call() { local rc; set -x; "$@"; rc=$?; { set +x; } 2>/dev/null; return $rc; } call "$opt_rhapsodycl" -f $rclfile || die $? 'Rhapsody license server not reachable' 

For example, in case of failure prints something like:

 + path/to/RhapsodyCL.exe -f configuration.rcl + rc=127 Rhapsody license server not reachable at ./Build:main line 167 (exit 127) 

Or in case of success the script continues. With these two functions (call and die) we can write very compact and readable one-liners that also produce nice tracing output.

1 Comment

A very clever idea! Hint: if you put rc=$? inside the { set +x; } it will be silenced too: call() { local rc; set -x; "$@"; { rc=$?; set +x; } 2>/dev/null; return $rc; }
0

Somewhat related to the question is how one can get (logical) comments to also show in the traced output, to perhaps provide an explanation of what high-level operation the commands of the script are performing.

They way I usually do this is, if you don't mind minimal overhead, is:

some_script.sh

#!/usr/bin/env bash set -x ... : This shows a directory listing ls ... 

Sample Output

+ : This shows a directory listing + ls test.sh 

You can of course also enclose the parameters to the : built-in with either single or double quotes, with double quotes (or no quotes) having the advantage that you also get variable expansion (and other side effects) to occur during the output.

This method can then be used as a trace-only in-line verbose message tool that enables you to inject additional debug trace messages within the regular trace output, which would not get outputted when trace is disabled.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.