3
votes

I have a bash script which executes about 20 commands and for debugging purposes I find myself scrolling through the output a lot. Unfortunately bash doesn't tell me which part of the output is part of what command. When I use "set -x" in the script it at least prints some information on what it just executed, but I don't really like the output it generates.

For instance, if I have this script:

#!/bin/bash

set -x
echo "foo"
if [ "$ASD" == "QWE" ] ; then
    echo "bar"
fi

I would like the output to be something like this:

echo "foo"
foo
echo "bar"
bar

Or maybe:

echo "foo"
foo
if [ "value_of_ASD" == "QWE" ] ; then
echo "bar"
bar
fi

Instead of printing the commands in bold, highlighting with a color would also be okay. But I don't just want to have "+" characters in front of the commands and I also don't like the if statements showing up like '[' value_of_ASD == QWE ']'.

How can I accomplish that with bash?

At the moment the output looks like this btw:

+ echo foo
foo
+ '[' value_of_ASD == QWE ']'
+ echo bar
bar

Edit:

One idea I had was to write a script that I would source in the very beginning of the main script and then let the sourced script parse the main one. Something like this:

source_me.sh

#!/bin/bash
SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/$(basename $0)"
FORMAT_SET_BOLD='\033[0;1m'
FORMAT_RESET='\033[0m'

cat $SCRIPT_PATH | tail -n "+$START_LINE" | while read line; do
    printf "${FORMAT_SET_BOLD}${line}${FORMAT_RESET}\n"
    eval "${line}"
done

exit 0;

main.sh

#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh

echo "Foo"
echo "Bar"
echo "+Hello"

The output in that case is:

echo "Foo"
Foo
echo "Bar"
Bar
echo "+Hello"
+Hello

But this method will fail if I use more complex code that goes over multiple lines (if statements, loops etc):

#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh

echo "Foo"

if [ "$FOOBAR" == "" ] ; then
    echo "Bar"
fi

echo "+Hello"

In this case I get:

echo "Foo"
Foo

if [ "$FOOBAR" == "" ] ; then
./source_me.sh: eval: line 9: syntax error: unexpected end of file
echo "Bar"
Bar
fi
./source_me.sh: eval: line 8: syntax error near unexpected token ´fi'
./source_me.sh: eval: line 8: ´fi'

echo "+Hello"
+Hello

4
Write another script you can pipe the output to that changes a line beginning with + to be bold using your terminal emulator.lurker
The commands that are being executed often produce output that includes + characters. So that's not an option.Forivin
I don't believe there is anyway to do this, besides rolling a script that does what @lurker suggests. If you can interpret the set -x output by looking at it, then you can write a script to do the same. It won't be pretty though.JNevill
@Forivin I understand completely. It's just that, to my knowledge, there is no way to change the format of the -x output (at least no built in way). You could roll something lightweight and pipe it to that, it may not be perfect, but it may help with the set -x eye sore like ./yourscript.sh | awk '/^\+/{printf "%c[1;32m %s\n",27, $0} /^[^\+]/{printf "%c[1;30m %s\n",27,$0}' where it will highlight lines starting with + in green and the rest in a lovely gray.JNevill
You could always get the bash source and hack that. It's available. :)lurker

4 Answers

2
votes

You can change the + to a string of your desire by setting PS4, e.g.:

PS4="# "; set -x
1
votes

Note that set -x has no innate knowledge of your terminal, which would be required to send the correct characters to create bold or coloured text.

If you can capture all output through a single file handle, you can probably do this with a pipe:

$ /path/to/your/script 2>&1 | sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/"

You might want to man tput for more information about the tool we're using to get bold type.

The viability of this depends on many factors I don't know about your environment. YMMV, may contain nuts.


Note:

You might think it was better to capture stderr separately with a line like this:

$ /path/to/your/script 2> >(sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/")

Alas, this doesn't behave consistently, because stderr passing through the pipe may not be submitted to the terminal before subsequent stdout from the script itself. Note also that this solution, because it uses process substitution, is a bashism and is not portable to POSIX.

1
votes

I would like to extend rubo77's answer with a few examples that I think deserve a separate answer:

Plain text prefix

So the basic example is to set PS4 to some plain text, e.g.:

PS4="# "; set -x

Which will result in:



Color & extra line text prefix

But because you can use special characters and ANSI escape codes you can for example add a new line before each new command and print the prefix in a color, e.g.:

PS4="\n\033[1;33m>>>\033[0m "; set -x

Result:



Dynamic color prefix

Finally you can make the command prefix call other programs with each use, which you can use to add a timestamp, e.g.:

# yes, there have to be single quotes below, not double!
PS4='\033[1;34m$(date +%H:%M:%S)\033[0m '; set -x

Result:

0
votes

What you are looking for is set -v.

-v prints out the line the interpreter just read.

-x prints out the post-parsing results of the line that the interpreter just read.

file x:

set -vx
foo=1
echo $foo
set +vx

Executing:

$: . x
foo=1
++ foo=1
echo $foo
++ echo 1
1
set +vx
++ set +vx