12

I am writing a Bash script where I want all commands to be echoed as they occur. I know that I need to use set -x and set +x to toggle this behaviour off and on, respectively (SOF post here). However, it doesn't echo everything, namely, I/O redirects.

For example, let's say I have this very simple Bash script:

set -x
./command1 > output1
./command2 arg1 arg2 > output2

This will be the output

+ ./command1
+ ./command2 arg1 arg2

Bash is not echoing my stdout redirect to output1 and output2. Why is this? How can I achieve this behaviour? Perhaps is there a shopt option that I must set in the script?

NOTE: I also noticed that pipes will not print as expected. For example, if I were to use this command:

set -x
./command3 | tee output3

I will get this output:

+ tee output3
+ ./command3

How do I make the commands be echoed exactly in the way they are written instead of having the pipe reordered by the script?

Community
  • 1
  • 1
ecbrodie
  • 11,246
  • 21
  • 71
  • 120

3 Answers3

6

Neither set -x nor the DEBUG trap will provide full information when certain constructs are used.

Examples are:

ex1.sh

for i in a b c
do
  echo $i # Where does the output go?
done > outfile # Here!

ex2.sh

c="this"
case "$c" in
  this) echo sure ;; # Where does the output go?
  that) echo okay ;;
esac > choice # Here!

ex3.sh

what="up"
while [ "$what" = "up" ]
do
    echo "Going down!" # Where does the output go?
    what="down"
    echo "Newton rulezzz!" > thelaw
done > trying # Here!

and many more like these, let alone all kinds of nested variants. I don't know of an easy way to handle these, except to enter into the land of full Bash script parsing, which is a minefield...

EDIT: If Bash-specific features are not required, and backwards compatibility with the Bourne shell will do, the Korn shell (ksh, tested with version 93u+ 2012-08-01) does a bit better on showing information for redirects:

$ ksh -x ex1.sh 
+ 1> outfile
+ echo a
+ echo b
+ echo c

$ ksh -x ex2.sh 
+ c=this
+ 1> choice
+ echo sure

$ ksh -x ex3.sh 
+ what=up
+ 1> trying
+ [ up '=' up ]
+ echo 'Going down!'
+ what=down
+ echo 'Newton rulezzz!'
+ 1> thelaw
+ [ down '=' up ]
Melebius
  • 6,183
  • 4
  • 39
  • 52
ack
  • 7,356
  • 2
  • 25
  • 20
6

You should rather use set -v.

-v Print shell input lines as they are read.

The output seems to meet your expectation.

$ set -v
$ ./command1 > output1
./command1 > output1
sh: ./command1: No such file or directory
$ ./command2 arg1 arg2 > output2
./command2 arg1 arg2 > output2
sh: ./command2: No such file or directory
$ ./command3 | tee output3
./command3 | tee output3
sh: ./command3: No such file or directory
Melebius
  • 6,183
  • 4
  • 39
  • 52
4

It isn't possible with set -x. Your only option is to view the current command through a DEBUG trap.

trap 'printf %s\\n "$BASH_COMMAND" >&2' DEBUG

This won't show the precise arguments as set -x will. Combining them should give you the full picture, though it's a lot of debug output.

ormaaj
  • 6,201
  • 29
  • 34
  • But what about FreeBSD system? Because I can't do such a trick there due to the absence of DEBUG signal. Any suggestions? – vwvolodya May 06 '14 at 11:41