How to restore previous 'set -x' option in bash - bash

I'm trying to debug my scripts. For example a.sh call b.sh.
#a.sh
echo "in a.sh"
source b.sh
#b.sh
echo "in b.sh"
If I'm sure b.sh is OK and just want to debug a.sh, I run as
bash -x a.sh
How to disable the display '-x' setting in b.sh, maybe modify b.sh as
#b.sh
x_option=$(get -x) # if there is such function
set +x
echo "in b.sh"
[ $x_optoin = 1 ] && set -x

From the bash reference manual:
The current set of options may be found in $-.
Which means you can look in that value for the current state of -x.

Related

Can I put a breakpoint in shell script?

Is there a way to suspend the execution of the shell script to inspect the state of the environment or execute random commands?
alias combined with eval gives you basic functionality of breakpoints in calling context:
#!/bin/bash
shopt -s expand_aliases
alias breakpoint='
while read -p"Debugging(Ctrl-d to exit)> " debugging_line
do
eval "$debugging_line"
done'
f(){
local var=1
breakpoint
echo $'\n'"After breakpoint, var=$var"
}
f
At the breakpoint, you can input
echo $var
followed by
var=2
then Ctrl-d to exit from breakpoint.
Due to eval in the while loop, use with caution.
Bash or shell scripts do not have such debugging capabilities as other programming languages like Java, Python, etc.
We can put the echo "VAR_NAME=$VAR_NAME" command in the code where we want to log the variable value.
Also, a little bit more flexible solution is to put this code somewhere at the beginning in the shell script we want to debug:
function BREAKPOINT() {
BREAKPOINT_NAME=$1
echo "Enter breakpoint $BREAKPOINT_NAME"
set +e
/bin/bash
BREAKPOINT_EXIT_CODE=$?
set -e
if [[ $BREAKPOINT_EXIT_CODE -eq 0 ]]; then
echo "Continue after breakpoint $BREAKPOINT_NAME"
else
echo "Terminate after breakpoint $BREAKPOINT_NAME"
exit $BREAKPOINT_EXIT_CODE
fi
}
export -f BREAKPOINT
and then later, at the line of code where we need to break we invoke this function like this:
# some shell script here
BREAKPOINT MyBreakPoint
# and some other shell script here
So then the BREAKPOINT function will log some output then launch /bin/bash where we can run any echo or some other shell command we want. When we want to continue running the rest of the shell script (release breakpoint) we just need to execute exit command. If we need to terminate script execution we would run exit 1 command.
There exist solutions like bash-debug.
A poor-man's solution which works for me is the interactive shell.
By adding three lines of code, you can introspect and alter variables as follows:
Let's assume, that you have the script test.bash
A=FOO
export B=BAR
echo $A
echo $B
$ test.bash
FOO
BAR
If you add an interactive shell at line 3, you can look around and inspect variables which have been exported before:
A=FOO
export B=BAR
bash -c "$SHELL"
echo $A
echo $B
$ test.bash
$ echo $A
$ echo $B
BAR
$ exit
FOO
BAR
If you want to see all variables in your interactive shell, you have to add set -a to the preamble of your script, such that all variables and functions are exported:
set -a
A=FOO
export B=BAR
bash -c "$SHELL"
echo $A
echo $B
$ test.bash
$ echo $A
FOO
$ echo $B
BAR
$ exit
FOO
BAR
Note, that you cannot change the variables in your interactive shell. The only solution for me is to source an additional script of variables, which will be sourced rightafter the interactive shell
set -a
A=FOO
export B=BAR
bash -c "$SHELL"
source /tmp/var
echo $A
echo $B
$ test.bash
$ echo "export A=alice" > /tmp/var
$ echo "export B=bob" >> /tmp/var
$ exit
alice
bob

Is there way to check if shell script is executed with -x flag

I am trying to check in the script, that if that script is executed with -x flag which is for the debugging for shell scripts.
Is there any way to check that in script itself that -x is set.
I want to conditionally check that and do something if that is set.
Use:
if [[ $- == *x* ]]; then
echo "debug"
else
echo "not debug"
fi
From Bash manual:
($-, a hyphen.) Expands to the current option flags as specified upon invocation, by the set builtin command, or those set by the shell itself (such as the -i option).
The portable way to do this (without bashisms like [[ ]]) would be
case $- in
(*x*) echo "under set -x"
esac
You can trap the DEBUG signal, like so:
trap "do_this_if_it_is_being_debugged" DEBUG
function do_this_if_it_is_being_debugged() {
...
}
Note this needs to be executed before the set -x is being executed
Look for xtrace in $SHELLOPTS.
For example:
if grep -q xtrace <<<"$SHELLOPTS"; then
DO_SOMETHING;
fi

how to silently disable xtrace in a shell script?

I'm writing a shell script that loops over some values and run a long command line for each value. I'd like to print out these commands along the way, just like make does when running a makefile. I know I could just "echo" all commands before running them, but it feels inelegant. So I'm looking at set -x and similar mechanisms instead :
#!/bin/sh
for value in a long list of values
do
set -v
touch $value # imagine a complicated invocation here
set +v
done
My problem is: at each iteration, not only is the interresting line printed out, but also the set +x line as well. Is it somehow possible to prevent that ? If not, what workaround do you recommend ?
PS: the MWE above uses sh, but I also have bash and zsh installed in case that helps.
Sandbox it in a subshell:
(set -x; do_thing_you_want_traced)
Of course, changes to variables or the environment made in that subshell will be lost.
If you REALLY care about this, you could also use a DEBUG trap (using set -T to cause it to be inherited by functions) to implement your own set -x equivalent.
For instance, if using bash:
trap_fn() {
[[ $DEBUG && $BASH_COMMAND != "unset DEBUG" ]] && \
printf "[%s:%s] %s\n" "$BASH_SOURCE" "$LINENO" "$BASH_COMMAND"
return 0 # do not block execution in extdebug mode
}
trap trap_fn DEBUG
DEBUG=1
# ...do something you want traced...
unset DEBUG
That said, emitting BASH_COMMAND (as a DEBUG trap can do) is not fully equivalent of set -x; for instance, it does not show post-expansion values.
You want to try using a single-line xtrace:
function xtrace() {
# Print the line as if xtrace was turned on, using perl to filter out
# the extra colon character and the following "set +x" line.
(
set -x
# Colon is a no-op in bash, so nothing will execute.
: "$#"
set +x
) 2>&1 | perl -ne 's/^[+] :/+/ and print' 1>&2
# Execute the original line unmolested
"$#"
}
The original command executes in the same shell under an identity transformation. Just prior to running, you get a non-recursive xtrace of the arguments. This allows you to xtrace the commands you care about without spamming stederr with duplicate copies of every "echo" command.
# Example
for value in $long_list; do
computed_value=$(echo "$value" | sed 's/.../...')
xtrace some_command -x -y -z $value $computed_value ...
done
Next command disables 'xtrace' option:
$ set +o xtrace
I thought of
set -x >/dev/null 2>1; echo 1; echo 2; set +x >/dev/null 2>&1
but got
+ echo 1
1
+ echo 2
2
+ 1> /dev/null 2>& 1
I'm surprised by these results. .... But
set -x ; echo 1; echo 2; set +x
+ echo 1
1
+ echo 2
2
looks to meet your requirement.
I saw similar results when I put each statement on its only line (excepting the set +x)
IHTH.

How to keep verbosity when sourcing from a bash script

I need to check the run of a bash script, with a source call, something like:
#!/bin/bash
some code here
source script_b.sh
more code here
I run:
$bash -x script_a.sh
and I get,
+ some echo here
+ script_b.sh
+ some more echo here
But all echoes are from script_a.sh. All the code from script_b.sh is hidden, so I can not trace what is really happening.
Is there any way I can check the execution of script_b.sh within script_a.sh?
You could try "bash -x script_b.sh" inside of the parent script.
Edit:
This worked for me. If you run the parent script with bash -x you will see everything for both. "set -x" will set the debug flag for the environment...in the script? I'm not sure, and fifo is still magic to me.
echo "start of script"
set -x
mkfifo fifo
cat /dev/null < fifo | fifo > source .bash_profile
rm fifo
echo "end of script"

Can you emulate "set -e" and "set -x" via the environment?

Many of my test scripts begin:
set -e
test -n "$V" && set -x
Rather than putting those lines ( or sourcing a common script ) in each script, I'd
like to get that functionality through the environment. Is there a portable way to use environment settings to cause sh to behave as if "set -e" or "set -x" has been called? Is there a non-portable (ie shell-specific) way to do the same?
(I've tagged this question as automake because that's the framework I'm in at the moment and would like to be able to put something in TESTS_ENVIRONMENT that will allow me to omit those lines from each script, but clearly the question is not automake specific.)
Just add this to your scripts:
eval "${ENABLE_DEBUG}"
Now you can set the env variable ENABLE_DEBUG to set -e ; test -n "$V" && set -x to enable debugging or you can leave it unset.
Note that this fails if you have the option "fail for undefined variables" active (set -u or set -o nounset). If that is the case, you either need to check that the variable is set or use bash with:
eval "${ENABLE_DEBUG}"
that sets the variable to :, the "do nothing" command.
Answering the automake part.
If you have
TESTS = foo.test bar.test baz.test
the Makefile generated will have a test target roughly like
test:
...
$(TEST_ENVIRONMENT) $(srcdir)/foo.test
$(TEST_ENVIRONMENT) $(srcdir)/bar.test
$(TEST_ENVIRONMENT) $(srcdir)/baz.test
...
You can actually set TEST_ENVIRONMENT to a command that will start your shell scripts with sh -xe or sh -e.
If all tests are shell scripts, this can be a simple as setting
TEST_ENVIRONMENT = $(SHELL) -e $${V+-x}
if not all tests are shell scripts, you can have
TEST_ENVIRONMENT = $(srcdir)/run
and write a script run such as:
#!/bin/sh
case $1 in
*.py)
exec python "$#";;
*.test)
exec sh -x ${V+-x} "$#";;
*)
echo "Unknown extension" >&2
exit 2;;
esac

Resources