2
#!/usr/bin/env bash

exec {BASH_XTRACEFD}>./xtrace.log
declare -p BASH_XTRACEFD
set -x
{ : "how do you hide this in ./xtrace.log?"; } 2>/dev/null # fail
# { :; } "${BASH_XTRACEFD:-2}">/dev/null # syntax error
$ ./above_script.bash
declare -- BASH_XTRACEFD="10"

$ cat ./xtrace.log
+ : 'how do you hide this in ./xtrace.log?'

After a block, a literal, hardcoded file descriptor number works, but you cannot use a variable value.

{ : something; } 2>/dev/null works, it outputs nothing in xtrace, when BASH_XTRACEFD is unset, but:

{ : "How do you hide this from xtrace?"; } "${BASH_XTRACEFD:-2}">/dev/null produces a syntax error.

Is there any way, to silence the call, when BASH_XTRACEFD is set?

2
  • 1
    That 2> /dev/null discards all errors in addition of the xtrace output, which kind of defeats the purpose of using $BASH_XTRACEFD in the first place which is about having the traces separate from errors. Commented Sep 18 at 8:22
  • @StéphaneChazelas I tried to address this problem and moved the conclusion part to a separate answer (as I was instructed to do) Commented Sep 18 at 20:53

4 Answers 4

2

It apparently can be done, but you will need to know the file descriptor, or to plan ahead.


Knowing the file descriptor:

exec 5>xtrace.log
BASH_XTRACEFD=5

set -x

: before

( : test1 ; ) 5>/dev/null  
{ : test2 ; } 5>/dev/null  
: test3 5>/dev/null  

: after

For me, this shows only "before", "test3", and "after" in the log.


planning ahead:

exec {BASH_XTRACEFD}>xtrace.log
exec {null}>/dev/null

echo BASH_XTRACEFD=$BASH_XTRACEFD null=$null

set -x

: before 

BASH_XTRACEFD=$null : test1

: after

For me, this shows only "before" and "after" in the log.

4
  • 1
    I finally found the documentation for the {varname}>file construct (section 3.6). It is wrong, in that it says "the shell will allocate a file descriptor greater than 10", and it allocates 10. (bash 5.1.4) Commented Sep 6 at 14:40
  • Your comment would be more useful to others reading it if it provided a link to the documentation you found. Commented Sep 7 at 11:47
  • @EdMorton I found/read the Info page. You can see this particular portion at manpagez.com/info/bashref/bashref-4.2/bashref_40.php Most people seem to publish the man page, and I wouldn't want to publish a link to that. Commented Sep 7 at 18:40
  • Your planning ahead approach only works for builtin commands (try replacing : with /bin/true for instance), and beware that for special builtin commands (such as :), when in POSIX mode, the BASH_XTRACEFD=$null persists afterwards (so with POSIXLY_CORRECT=1 bash ./that-script, the : after would not show in xtrace.log). Commented Sep 13 at 19:32
1

This seems to work:

exec {trace}>> xtrace.log {null}<>/dev/null
shopt -s expand_aliases
alias 'hide{={ {' \
      '}hide=} <&$((BASH_XTRACEFD=null, 0)); ((BASH_XTRACEFD=trace, !$?)); }'
BASH_XTRACEFD=$trace
set -o xtrace

echo this shows in log

hide{ echo this does not; }hide

echo this also shows in log

hide{ echo this fails; (exit 42); }hide ||
  echo "It failed indeed but the exact exit status is lost: $?"

echo It works | hide{ sed 's/works/also & great/'; }hide
hide{ echo It works great; }hide | sed 's/works/also &/'

Then:

$ bash ./that-script
this shows in log
this does not
this also shows in log
this fails
It failed indeed but the exact exit status is lost: 1
It also works great
It also works great
$ cat xtrace.log
+ echo this shows in log
+ echo this also shows in log
+ echo 'It failed indeed but the exact exit status is lost: 1'
+ echo It works
+ sed 's/works/also &/'

The trick being to assign $null (the fd opened on /dev/null) to $BASH_XTRACEFD as part of what ends up a no-op <&0 dummy redirection using an arithmetic expansion.

You lose the exact value of the exit status of the hidden command though.

I'd still use a fixed fd above 2 instead of a dynamically assigned one and not bother with that kind of nonsense.

1

In bash redirections [n]>word the [n] must be a literal number, it cannot be a variable. The part after the redirection operator can be a variable. You can do this:

exec {BASH_XTRACEFD}>./xtrace.log
declare -p BASH_XTRACEFD
set -x
{ : "how do you hide this in ./xtrace.log?"; } 2>/dev/null
{ :; } {BASH_XTRACEFD}>/dev/null # a syntax error
{ : "how do you hide this in ./xtrace.log?"; } {BASH_XTRACEFD}>>./xtrace.log
foo=bar
1
  • 1
    Beware each {BASH_XTRACEFD}> file redirection creates a new fd. You'd want to add a {BASH_XTRACEFD}>&- before each (except the first) to close the previous one and avoid them accumulate. Commented Sep 14 at 5:49
1

This is my current best effort to answer my own question. Thank you to everyone else, who answered, it's all based on your ideas!

#!/usr/bin/env bash

: shown 1

{
    exec 2>&3 # restore stderr

    : hidden

    echo error 1>&2

    case $fd_xtrace in
        2) unset BASH_XTRACEFD ;;
        *) BASH_XTRACEFD=$fd_xtrace ;;
    esac
} \
    3>&2 \
    2>/dev/null \
    4>&2 \
    4>&$((fd_xtrace=${BASH_XTRACEFD:-2}, BASH_XTRACEFD=4))
# ^ syntax compatible with Bash 3.0 (tested; possibly compatible with earlier
#   versions as well), but it only starts working (in the sense of hiding
#   xtrace output) from Bash >= 4.1 on.

: shown 2

Output:

$ bash -x ./script.bash
+ : shown 1
error
+ : shown 2

$ { BASH_XTRACEFD=80 bash -x ./script.bash; } 80>&2
+ : shown 1
error
+ : shown 2

$ { BASH_XTRACEFD=80 bash -x ./script.bash 2>/dev/null; } 80>&2
+ : shown 1
+ : shown 2

$ docker run -it --rm -v .:/mnt:ro bash:4.1 bash -x /mnt/script.bash
+ : shown 1
error
+ : shown 2

$ docker run -it --rm -v .:/mnt:ro bash:4.1 bash -c '{ BASH_XTRACEFD=80 bash -x /mnt/script.bash; } 80>&2'
+ : shown 1
error
+ : shown 2

$ docker run -it --rm -v .:/mnt:ro bash:3.0 bash -x /mnt/script.bash
+ : shown 1
+ : hidden
+ echo error
error
+ case $fd_xtrace in
+ unset BASH_XTRACEFD
+ : shown 2

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.