150

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.

Mateusz Piotrowski
  • 8,029
  • 10
  • 53
  • 79
Andreas Spindler
  • 7,568
  • 4
  • 43
  • 34
  • 1
    This doesn't answer your question, but when you run your script why not: `script.sh 2>&1 | grep -v 'set +x' ` – cdarke Nov 04 '12 at 16:16

7 Answers7

224

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
Joshua Dwire
  • 5,415
  • 5
  • 29
  • 50
McJoey
  • 2,388
  • 1
  • 12
  • 7
  • 12
    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. – sdaau Feb 28 '14 at 08:28
  • 9
    This zeroes the exit status. – Garth Kidd Jan 06 '16 at 00:21
  • 12
    @GarthKidd Exit status is zeroed with every successful command. `set +x` is such a successful command – Daniel Alder Dec 18 '17 at 10:49
  • 7
    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. – Greg Price Sep 11 '18 at 21:33
  • 7
    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. – Greg Price Sep 12 '18 at 03:10
  • 3
    Please explain this spell. – greatvovan Aug 29 '20 at 00:27
  • 3
    A spell indeed. `{ }` groups commands but `set` is a ["builtin"](https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html) and not a command. It appers that `set +x 2>/dev/null` does nothing (the output is still printed). So we trick it into a command by using `{ }`. That realization is a nice by-catch of this solution. – Andreas Spindler Mar 17 '21 at 19:16
  • I don't think being a builtin matters. The issue is that the x-trace for a command isn't part of the stderr from that command. The x-trace is a separate thing, produced before the command even runs. A line like `$my_command 2>/dev/null` only redirects the stderr _from_ `$my_command` into /dev/null. So the x-trace isn't captured by the `2>/dev/null`. Whereas `{my_command_group} 2>/dev/null` means that all the stderr produced inside the braces gets captured and sent to /dev/null. And apparently that _does_ include the x-trace – Douglas Winship Aug 02 '23 at 10:01
65

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

( set -x ; command )
choroba
  • 231,213
  • 25
  • 204
  • 289
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
user108471
  • 2,488
  • 3
  • 28
  • 41
  • 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. – mklement0 Jul 07 '14 at 16:04
  • 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? – user108471 Jul 07 '14 at 16:31
  • 1
    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 – mklement0 Jul 07 '14 at 16:34
  • ...except for security. Tracing is a very common feature, and some trace information might be sensitive. – Andreas Spindler Nov 11 '14 at 06:40
  • 1
    @AndreasSpindler Could you elaborate on why you think this technique is more likely to leak more sensitive information? – user108471 Jan 09 '15 at 20:27
  • 1
    ...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. – Andreas Spindler Jul 04 '15 at 20:02
  • Here's a one-liner variation which omits the function: `alias xtrace='{ read -t 0&&read;[[ $REPLY == on ]]&&set -x||set +x; } 2>&- <<<'` (though best avoided if you use the default REPLY variable in read) – mr.spuratic Aug 05 '22 at 10:31
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
Oliver
  • 27,510
  • 9
  • 72
  • 103
2

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
  • 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. – Andreas Spindler Aug 20 '20 at 07:16
  • 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. – user3286792 Oct 13 '20 at 01:46
0

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.

Andreas Spindler
  • 7,568
  • 4
  • 43
  • 34
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.

Michael Goldshteyn
  • 71,784
  • 24
  • 131
  • 181