29

I would like to set -x temporarily in my script and then return in to the original state.

Is there a way to do it without starting new subshell? Something like

echo_was_on=.......
... ...
if $echo_was_on; then set -x; else set +x; fi
jmster
  • 943
  • 1
  • 12
  • 25

7 Answers7

31

You can check the value of $- to see the current options; if it contains an x, it was set. You can check like so:

old_setting=${-//[^x]/}
...
if [[ -n "$old_setting" ]]; then set -x; else set +x; fi

In case it's not familiar to you: the ${} above is a Bash Substring Replacement, which takes the variable - and replaces anything that's not an x with nothing, leaving just the x behind (or nothing, if there was no x)

cincodenada
  • 2,877
  • 25
  • 35
Kevin
  • 53,822
  • 15
  • 101
  • 132
15
reset_x=false
if ! [ -o xtrace ]; then
    set -x
    reset_x=true
fi

# do stuff

"$reset_x" && set +x

You test a shell option with the -o test (using [ as above or with test -o). If the xtrace option isn't set (set +x), then set it and set a flag to turn it off later.

In a function, you could even have set a RETURN trap to reset the setting when the function returns:

foo () {
    if ! [ -o xtrace ]; then
        set -x
        trap 'set +x' RETURN
    fi

    # rest of function body here
}
Kusalananda
  • 14,885
  • 3
  • 41
  • 52
  • 1
    What shells support it? It doesn't seem to be listed among POSIX shell test operators: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html. – skozin Jun 05 '18 at 19:49
  • 3
    @skozin `bash` supports it. The question was specifically about `bash`, both in title, and by tags. – Kusalananda Jun 05 '18 at 20:00
  • Oops, sorry, missed it :) Yup, for Bash it seems like a proper way to do it. – skozin Jun 05 '18 at 20:15
  • @skozin No worries. – Kusalananda Jun 05 '18 at 20:19
  • 1
    Updated my answer with link to this one. – skozin Jun 06 '18 at 13:36
  • 1
    @Kusalananda, I can't quickly figure out why your solution only works when I use double brackets instead of your suggested single ones. If I just leave single as in your post, then no matter if xtrace enabled or not the if [ ! -o xtrace]; always true. I'm on Ubuntu 20.04, bash --version: 5.0.17(1)-release, thanks! – Dmitry Shevkoplyas Nov 24 '21 at 18:16
  • 1
    @DmitryShevkoplyas Thanks for finding an interesting bug! The `-o` test tests whether an option is set or not, but if you use `! -o`, the `-o` test is interpreted as "or" (it changes meaning with the number of arguments inside `[ ... ]`). The solution is to switch to `[[ ... ]]` as you figured out, or to do as I now do in my answer, i.e. to negate the result of the test outside the brackets. – Kusalananda Nov 24 '21 at 21:47
14

Or in a case statement

 case $- in
   *x* ) echo "X is set, do something here" ;;
   * )   echo "x NOT set" ;;
 esac
Qix - MONICA WAS MISTREATED
  • 14,451
  • 16
  • 82
  • 145
shellter
  • 36,525
  • 7
  • 83
  • 90
10

Here are re-usable functions, based on @shellter's and @glenn jackman's answers:

is_shell_attribute_set() { # attribute, like "e"
  case "$-" in
    *"$1"*) return 0 ;;
    *)    return 1 ;;
  esac
}


is_shell_option_set() { # option, like "pipefail"
  case "$(set -o | grep "$1")" in
    *on) return 0 ;;
    *)   return 1 ;;
  esac
}

Usage example:

set -e
if is_shell_attribute_set e; then echo "yes"; else echo "no"; fi # yes

set +e
if is_shell_attribute_set e; then echo "yes"; else echo "no"; fi # no

set -o pipefail
if is_shell_option_set pipefail; then echo "yes"; else echo "no"; fi # yes

set +o pipefail
if is_shell_option_set pipefail; then echo "yes"; else echo "no"; fi # no

Update: for Bash, test -o is a better way to accomplish the same, see @Kusalananda's answer.

skozin
  • 3,789
  • 2
  • 21
  • 24
3

Also:

case $(set -o | grep xtrace | cut -f2) in
    off) do something ;;
    on)  do another thing ;;
esac
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
2

less verbose

[ ${-/x} != ${-} ] && tracing=1 || tracing=0
untore
  • 603
  • 8
  • 16
2

This should answer exactly the question, provided your okay with the bash extension (non POSIX) and you bash version supports it.

[[ $- =~ x ]] && echo_was_on=true || echo_was_on=false
user327407
  • 103
  • 1
  • 4
  • 3
    Since you are the only answer citing POSIX, the [POSIX way](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02) to do it could be `[ -z "${-%%*x*}" ] && echo on`; which would be working for ash, dash, etc. – bufh Jan 20 '23 at 16:48
  • 2
    Thanks @bufh for the POSIX way. All four Remove Pattern operators can work here. Also without double quotes. `[ -z ${-%*x*} ]`, `[ -z ${-%%*x*} ]`, `[ -z ${-#*x*} ]`, `[ -z ${-##*x*} ]`. Please correct me if I am wrong. – Hrishikesh Kadam Jan 27 '23 at 08:48