It may seem straight forward to debug scripts in Bash when writing simple and short shell scripts. Though, the complexity tends to increase when maintaining a larger code base or when collaborating with your peers. In such cases, following consistent steps to debug scripts is critical to ensure a quick debugging process.
This post covers various special properties of the Bash Shell and how you can use them to efficiently debug scripts.
What Are The 3 Types Of Scripting Errors
Debugging a Bash shell script may be tedious at times. By following the 5 simple steps in this post you will prevent yourself from some major headaches.
There are three basic types of errors when programming and it remains true in shell scripts.
Syntax Error
Usually, Syntax Errors are the easiest errors to solve in a Bash script. They can often be found without executing the shell script. The most common syntax errors include:- Improper use of square brackets or parentheses in a Bash If Statement
- Incorrect syntax when using a Bash Loop or a Bash Array
- Incorrect use of a compound command when defining a Bash Function
Runtime Error
A Runtime Error will be the next level of errors. The shell script runs with no syntax errors but fails to execute reliably certain tasks. The most common runtime errors in a shell script include:- Division by zero or use of a string/float variable in a Bash Arithmetic Expression
- Incorrect subscript when dynamically populating a Bash Associative Array
- Parsing incorrectly a file or command output like when processing a CSV file in Bash or Counting Files in a Directory
- Running with incorrect sudo permissions or tty settings
Logic Error
Logic Errors are often the most difficult to troubleshoot as it relates to design flaws and logic mistakes. Debugging such errors can be difficult unless having a thorough understanding of the end-to-end execution of the script and the expected behavior. The most common logic errors in a shell script include:- Incorrect use of a test operator in a
Conditional Statement like using
-z
instead of-n
in a if condition - Incorrect use of an Arithmetic operator like multiplying instead of diving a number
- Incorrect condition to exit from a
Bash Loop like when using a
while
loop instead of anuntil
loop - Incorrectly manipulating Linux dates with different timezones
- Showing an incorrect message to the shell prompt or in logs
- Incorrect use of a test operator in a
Conditional Statement like using
The 5 Steps To Debug a Script in Bash
Step 1: Use a Consistent Debug Library
Whatever is the purpose of your shell scripts, whether you work solo or collaborating with teammates, it is essential to be consistent and systematic in your debugging process to troubleshoot and debug a shell script quickly.
You can systematically source a shared library that contains all your debugging settings by using the source and dot commands but this approach is not ideal if you inherited someone else problem and don’t want or cannot change the original code.
An alternative is to use the $BASH_ENV environment variable which is used by Bash to define an init file to read before executing a script. You can leverage it to create an environment for debugging purposes and define specific shell options or debugging traps.
The $ENV variable is similar to the $BASH_ENV
. It is used when the shell runs in POSIX compatibility mode.
### Define Debug environment
### Filename: my-debug-env
trap 'echo "$BASH_COMMAND" failed with error code $?' ERR
#!/usr/bin/env bash
### Example Script
### Filename: example-debug
echo "Example Script..."
bad_command &> /dev/null
### Example output with no debug env
[me@linux ~]$ ./example-debug
Example Script...
### Example output with the debug env
[me@linux ~]$ BASH_ENV=./my-debug-env ./example-debug
Example Script...
bad_command &> /dev/null failed with error code 127
In the next steps, we will keep enhancing the content of the BASH_ENV
init script ./my-debug-env
. The benefit of this technique is that no matter what script you are going to debug and which Linux server you are on, you will have consistent settings, traps, and logging (unless overridden by the script itself).
Step 2: Check For Syntax Error
Finding a syntax error only after executing a shell script can be frustrating and this is not ideal in a case where the command executed takes a long time to run or create a lot of other artifacts.
Let’s take a simple but common syntax error, with the incorrect use of the single square bracket and a missing then
keyword.
#!/usr/bin/env bash
# Filename: ./example-syntax-error
echo "This got executed"
if [-z "$v" ];
echo "There is a typo"
fi
## Output
[me@linux ~]$ ./example-syntax-error
This got executed
./test: line 12: syntax error near unexpected token `fi'
./test: line 12: `fi'
There is two options in Bash to catch this kind of syntax errors without executing the script. First, by using the shell noexec
option. Second, by using a static analysis tool.
Use The noexec Shell Option
The Bash shell provides the noexec
shell option which can be set either with set -o noexec
or using set -n
. This option will make the Bash shell only read the commands but will not execute them, neither will it do a variable assignment. In short, this is a basic “no-op” or “dry run” mode.
⚠️ Once the
noexec
option is executed by theset
builtin command, any other commands after that will NOT be executed, this include any followingset
command.
You can easily include a small condition and the use of an
environment variable to automatically enable the noexec
option to check the syntax of your script. This can be done directly in your script source, or better in your debug environment init script my-debug-env
as mentioned in step 1 by using the BASH_ENV
variable. This can be convenient in a CI/CD pipeline before deploying your script in production.
### Define Debug environment
### Filename: my-debug-env
if [[ -v NOOP ]]; then
echo "Run NOOP mode"
set -o noexec # same as set -n
fi
trap 'echo "$BASH_COMMAND" failed with error code $?' ERR
#!/usr/bin/env bash
# Filename: example-syntax-error
echo "This got executed"
if [-z "$v" ];
echo "There is a typo"
fi
## Output
[me@linux ~]$ NOOP=1 BASH_ENV=my-debug-env ./example-syntax-error
Run NOOP mode
./example-syntax-error: line 6: syntax error near unexpected token `fi'
./example-syntax-error: line 6: `fi'
Now, you can get the shell syntax error exposed without executing any of the commands in the script.
👉 This method will not be able to catch syntax errors in a shell script imported with the source or dot commands since the commands will not be executed in the
noexec
mode. Each script should be tested individually for syntax errors.
Use A Static Analysis Tool: ShellCheck
One of the problems with the noexec
mode is that it will just output the raw syntax errors which are not always easy to interpret and diagnose. A better approach is to use a static analysis tool like
ShellCheck which has become a defacto standard when it comes to Bash scripts static analysis.
With our previous syntax error example, shellcheck
provides us with much more friendly information and helpful recommendations on how to fix the errors.
[me@linux ~]$ shellcheck ./example-syntax-error
In ./example-syntax-error line 9:
if [-z "$v" ];
^-- SC1049: Did you forget the 'then' for this 'if'?
^-- SC1073: Couldn't parse this if expression. Fix to allow more checks.
^-- SC1035: You need a space after the [ and before the ].
In ./example-syntax-error line 11:
fi
^-- SC1050: Expected 'then'.
^-- SC1072: Unexpected keyword/token. Fix any mentioned problems and try again.
For more information:
https://www.shellcheck.net/wiki/SC1035 -- You need a space after the [ and ...
https://www.shellcheck.net/wiki/SC1049 -- Did you forget the 'then' for thi...
https://www.shellcheck.net/wiki/SC1050 -- Expected 'then'.
Once we fix those obvious syntax errors, shellcheck
will go a step further and even provide notices on variables not properly assigned which allow us to prevent some possible runtime errors.
[me@linux ~]$ shellcheck ./example-syntax-error
In example-xtrace line 9:
if [[ -z "$v" ]]; then
^-- SC2154: v is referenced but not assigned.
For more information:
https://www.shellcheck.net/wiki/SC2154 -- v is referenced but not assigned.
👉 There is a lot that you can do with
shellcheck
to reduce the number of errors in your bash shell scripts and help you debug quickly. I will generally use explicitly the following syntaxshellcheck -s bash -o all -xa <your_script>
to get the benefit of all the possible recommendations (option-o all
) and ensure sourced files are also checked (options-xa
).
Step 3: Trace Your Script Command Execution
Another helpful shell option to use is the xtrace
option that can be enabled with set -o xtrace
or set -x
. When your Bash script runs in trace mode all the commands and their arguments get printed out before being executed. This is helpful to debug runtime and logic errors in a script.
📎 You can replace the default
+
character used in thextrace
output by changing the Bash Prompt $PS4 Variable.
Alternatively, you can use the shell verbose
option instead by using set -o verbose
or set -v
. The main difference with xtrace
is that the verbose mode will only display the shell input lines as they are read. You will not see the arguments passed to the commands which is usually helpful when trying to debug a bash script. You may find useful to use both.
### Define Debug environment
### Filename: my-debug-env
if [[ -v TRACE ]]; then
echo "Run TRACE mode"
set -o xtrace # same as set -x
fi
if [[ -v NOOP ]]; then
echo "Run NOOP mode"
set -o noexec # same as set -n
fi
trap 'echo "$BASH_COMMAND" failed with error code $?' ERR
#!/usr/bin/env bash
# Filename: ./example-xtrace
echo "This got executed"
v=$1
if [[ -z "${v}" ]]; then
echo "\$v is empty"
fi
## Output
[me@linux ~]$ NOOP=1 TRACE=1 BASH_ENV=my-debug-env ./example-xtrace
Run TRACE mode
+ [[ -v NOOP ]]
+ echo 'Run NOOP mode'
Run NOOP mode
+ set -o noexec
[me@linux ~]$ TRACE=1 BASH_ENV=my-debug-env ./example-xtrace
Run TRACE mode
+ [[ -v NOOP ]]
+ echo 'This got executed'
This got executed
+ [[ -z '' ]]
+ echo '$v is empty'
$v is empty
This kind of tracing output is fine but it can quickly make it harder to debug a script as all the script output gets mixed up on the terminal. The simple and obvious solution is to redirect the standard errors to a different file by using for example TRACE=1 ./example-xtrace 2>error.log
. Though, if your script makes use of the standard error output, then you still have the debug trace mixed with standard errors.
To solve this problem, Bash can write the trace output generated by the xtrace
shell option to a given file descriptor as specified by the $BASH_XTRACEFD variable. This allows the tracing output to be separated from the script standard output and standard error messages. Our following example use a file xtrace.out
but you can instead use a shell redirection to a logging utility like logger
or syslog
.
The file descriptor is closed when the $BASH_XTRACEFD
variable is unset or assigned a new value. Unsetting $BASH_XTRACEFD
or assigning it the empty string causes the trace output to be sent to the standard error.
⚠️ Setting
$BASH_XTRACEFD
to the standard error file descriptor (2) and then unsetting it will result in the standard error being closed.
[me@linux ~]$ lsof -p $$ # List all current open files
...
[me@linux ~]$ ls /proc/$$/fd # Use /dev/fd/ on mac
0 1 2 255
[me@linux ~]$ exec 4>xtrace.out # Could be replaced by: exec 4> >(logger -t $0)
[me@linux ~]$ ls /proc/$$/fd
0 1 2 255 4
[me@linux ~]$ BASH_XTRACEFD=4
[me@linux ~]$ set -x
[me@linux ~]$ less xtrace.out
++ update_terminal_cwd
++ local url_path=
++ local i ch hexch LC_CTYPE=C LC_ALL=
++ (( i = 0 ))
...
[me@linux ~]$ set +x
[me@linux ~]$ unset BASH_XTRACEFD
[me@linux ~]$ ls /proc/$$/fd
0 1 2 255
Our previous example can then be updated to make use of the BASH_XTRACEFD
variable when tracing is enabled.
### Define Debug environment
### Filename: my-debug-env
if [[ -v TRACE ]]; then
echo "Run TRACE mode"
exec 4>./xtrace.out # Could be replaced by: exec 4> >(logger -t $0)
BASH_XTRACEFD=4
set -o xtrace # same as set -x
fi
if [[ -v NOOP ]]; then
echo "Run NOOP mode"
set -o noexec # same as set -n
fi
trap 'echo "$BASH_COMMAND" failed with error code $?' ERR
#!/usr/bin/env bash
# Filename: ./example-xtrace
echo "This got executed"
v=$1
if [[ -z "${v}" ]]; then
echo "\$v is empty"
fi
## Output
[me@linux ~]$ TRACE=1 BASH_ENV=my-debug-env ./example-xtrace
Run TRACE mode
This got executed
$v is empty
[me@linux ~]$ cat xtrace.out
+ [[ -v NOOP ]]
+ echo 'This got executed'
+ v=
+ [[ -z '' ]]
+ echo '$v is empty'
Step 4: Use The Extended Debug Mode
Using a debugger is often necessary to understand runtime or logic errors in a software program, this can also be true with a large shell script.
📎 The need for a debugger may also be a major red flag that you are writing in the wrong language. Bash is not always a good fit for every job. Bash scripts should stay simple and straight forward.
The Bash shell can run in an extended debug mode by using the shell extdebug
option which enables the interactive Bash debugger to investigate and debug your script step by step.
When using shopt -s extdebug
, the shell will also automatically enable the set
builtin option errtrace
and functrace
for error tracing in
bash functions. With the errtrace
and functrace
options, you get access to a DEBUG
trap, a RETURN
trap, and the Bash
special variables BASH_ARGC
, BASH_ARGV
, and BASH_ARGV0
.
⚠️ The
extdebug
option depends on the bashdb utility. If it’s not installed you will get the errorwarning: cannot start debugger; debugging mode disabled
. Thebashdb
command may not be available on your platform and may require a manual install. On Ubuntu 20.04, you can use this bashdb PPA and on Mac you can usebrew install bashdb
. If you are using VS codium or VS code, you can use the vscode-bash-debug extension which includesbashdb
.
Additionally to the extdebug
option, you can call the bash debugger directly from the command line using one of the command below:
bashdb [options] [--] script-name [script options]
bashdb [options] -c execution-string
bash --debugger [bash-options...] script-name [script options]
👉 If you don’t want or cannot use the bashdb debugger, you can replace the use of
shopt -s extdebug
to the shell debug/trace options withset -o errtrace
andset -o functrace
which provide most of the functionality discussed below, except for the interactive step-by-step debugger.
To handle the debugger mode in our current example, you can add a small block to the my-debug-env
startup script which will set the shell options as necessary depending on a custom
environment variable DEBUGGER
to be set or not.
if [[ -v DEBUGGER ]]; then
shopt -s extdebug
else
set -o errtrace
set -o functrace
fi
It’s important to have the errtrace
and functrace
options enable one way or another as it will make sure the ERR
, DEBUG
, RETURN
traps are inherited by the command substitution, shell functions, and subshells.
[me@linux ~]$ trap 'echo "ERR trap from ${FUNCNAME:-MAIN} context."' ERR
[me@linux ~]$ false
ERR trap from MAIN context.
[me@linux ~]$ fn() { false; }; fn ; echo "fn exit code is $?"
ERR trap from MAIN context.
fn exit code is 1
[me@linux ~]$ fn() { false; true; }; fn ; echo "fn exit code is $?"
fn exit code is 0
[me@linux ~]$ set -o errtrace
[me@linux ~]$ fn() { false; true; }; fn ; echo "fn exit code is $?"
ERR trap from fn context.
fn exit code is 0
The most common command inside the Bash debugger, bashdb
, will be the set linetrace on
to print every command executed, print var
to display the current assigned value of the variable var, the step n
(or s n
) to get to the next action by n step (n=1 if not provided) and cont
(or c
) to continue running the full script.
[me@linux ~]$ DEBUGGER=1 BASH_ENV=my-debug-env ./example-debug param1
bash debugger, bashdb, release 5.0-1.1.2
Copyright 2002-2004, 2006-2012, 2014, 2016-2019 Rocky Bernstein
This is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
(/home/jdoe/example-debug:3):
3: echo $BASH_ENV
bashdb<0> set linetrace on
bashdb<1> step
my-debug-env
(/home/jdoe/example-debug:4):
4: set -o
bashdb<2> cont
(/home/jdoe/example-debug:16):
level 1, subshell 0, depth 0: echo "Example Script..."
Example Script...
(/home/jdoe/example-debug:17):
level 1, subshell 0, depth 0: echo "Main BASH_ARGC=${BASH_ARGC[@]} BASH_ARGV=${BASH_ARGV[@]}"
Main BASH_ARGC=1 BASH_ARGV=param1
...
The $BASH_ARGC and $BASH_ARGV variables are arrays containing information about the script and functions positional parameters. $BASH_ARGC
contains the number of parameters in each frame of the current bash execution call stack ordered from the last to the first call, while $BASH_ARGV
contains all of the parameters in the current bash execution call stack ordered from the last to first call.
### Example Script
#!/usr/bin/env bash
shopt -s extdebug
debug() {
echo "Func: ${BASH_ARGC[@]} ${BASH_ARGV[@]}"
}
echo "Main: ${BASH_ARGC[@]} ${BASH_ARGV[@]}"
debug c
### Example output
[me@linux ~]$ ./example-argdebug a b
Main: 2 b a
Func: 1 2 c b a
The $BASH_ARGV0 is identical to the special parameter $0
and expands to the name of current the shell or shell script. Assigning a new value to $BASH_ARGV0
will also update the $0
special parameter. $BASH_ARGV0
will lose its special properties when being unset. This variable is available since
Bash version 5.
[me@linux ~]$ echo $0 $BASH_ARGV0
/usr/local/bin/bash /usr/local/bin/bash
Step 5: Provide Meaningful Debug Logs
Finally, similar to how important it is to write quality bash comments, it is as important to have meaningful debug logs and there are a few bash environment variables that you may want to leverage to debug your script quickly.
The $BASH_COMMAND contains the name of the command currently being executed or about to be executed, except when the shell is executing a command as the result of a trap, then the value is set to the command executed at the time of the trap. This variable is mostly useful for debug logs or error logs.
[me@linux ~]$ trap 'echo "$BASH_COMMAND" failed with error code $?' ERR
[me@linux ~]$ bad_command &>/dev/null
bad_command &> /dev/null failed with error code 127
The $BASH_LINENO and $BASH_SOURCE are
bash array variables. Those variables are often used for debugging and logging purposes and works in conjunction with the
bash function variable $FUNCNAME
. The $BASH_SOURCE
variable contains the source filenames where the corresponding shell function names in the $FUNCNAME
array variable are defined. The $BASH_LINENO
variable contains the line numbers in source files where each corresponding member of $FUNCNAME
was invoked.
The $LINENO contains the line number in the script or shell function currently executing.
### Example script
### Filename: example-debug
#!/usr/bin/env bash
debug() {
echo "Func BASH_SOURCE: ${!BASH_SOURCE[@]} ${BASH_SOURCE[@]}"
echo "Func BASH_LINENO: ${!BASH_LINENO[@]} ${BASH_LINENO[@]}"
echo "Func FUNCNAME: ${!FUNCNAME[@]} ${FUNCNAME[@]}"
echo "Func LINENO: ${LINENO}"
}
echo "Example Script..."
echo "Main BASH_SOURCE: ${!BASH_SOURCE[@]} ${BASH_SOURCE[@]}"
echo "Main BASH_LINENO: ${!BASH_LINENO[@]} ${BASH_LINENO[@]}"
echo "Main FUNCNAME: ${!FUNCNAME[@]} ${FUNCNAME[@]}"
echo "Main LINENO: ${LINENO}"
debug
### Example output
[me@linux ~]$ ./example-debug
Example Script...
Main BASH_SOURCE: 0 ./example-debug
Main BASH_LINENO: 0 0
Main FUNCNAME:
Main LINENO: 13
Func BASH_SOURCE: 0 1 ./example-debug ./example-debug
Func BASH_LINENO: 0 1 12 0
Func FUNCNAME: 0 1 debug main
Func LINENO: 7
Some other
shell variables that may be worth using include the BASHPID
, PID
, BASH
, SHELL
, BASHOPTS
, SHELLOPTS
, POSIXLY_CORRECT
, and BASH_COMPAT
. Those variables may be helpful to print at the beginning or end of the log ouput to know what is the current environment in which your script is running. Often, you may assume some shell option to be enabled or find out that you are running in a compatibility mode that disables some features you expect to have.
A Complete Example
Based on those detailed steps, we can produce a simple Bash debug environment init script to be used whenever we want to debug or profile a bash script.
👉 Feel free to change the below example to include the level of debugging information that you want so it can fit your needs.
### Define Debug environment
### Filename: my-debug-env
PS4='+[$0:$LINENO] '
if [[ -v DEBUGGER ]]; then
shopt -s extdebug
else
set -o errtrace
set -o functrace
fi
if [[ -v TRACE ]]; then
echo "Run TRACE mode"
exec 4>./xtrace.out
BASH_XTRACEFD=4
set -o xtrace # same as set -x
fi
if [[ -v NOOP ]]; then
echo "Run NOOP mode"
set -o noexec # same as set -n
fi
debug() {
echo "[ DEBUG ]| BASH_COMMAND=${BASH_COMMAND}"
echo " | BASH_ARGC=${BASH_ARGC[@]} BASH_ARGV=${BASH_ARGV[@]}"
echo " | BASH_SOURCE: ${!BASH_SOURCE[@]} ${BASH_SOURCE[@]}"
echo " | BASH_LINENO: ${!BASH_LINENO[@]} ${BASH_LINENO[@]}"
echo " | FUNCNAME: ${!FUNCNAME[@]} ${FUNCNAME[@]}"
}
trap 'echo ERR trap from ${FUNCNAME:-MAIN} context. $BASH_COMMAND failed with error code $?' ERR
trap 'debug' DEBUG
We can give it a try with our simple example script.
#!/usr/bin/env bash
# Filename: ./example-xtrace
echo "This got executed"
v=$1
if [[ -z "${v}" ]]; then
echo "\$v is empty"
fi
## Debug Output
[me@linux ~]$ TRACE=1 BASH_ENV=my-debug-env ./example-xtrace param1
Run TRACE mode
[ DEBUG ]| BASH_COMMAND=echo "This got executed"
| BASH_ARGC=1 BASH_ARGV=param1
| BASH_SOURCE: 0 1 my-debug-env ./example-xtrace
| BASH_LINENO: 0 1 3 0
| FUNCNAME: 0 1 debug main
This got executed
[ DEBUG ]| BASH_COMMAND=v=$1
| BASH_ARGC=1 BASH_ARGV=param1
| BASH_SOURCE: 0 1 my-debug-env ./example-xtrace
| BASH_LINENO: 0 1 4 0
| FUNCNAME: 0 1 debug main
[ DEBUG ]| BASH_COMMAND=[[ -z "${v}" ]]
| BASH_ARGC=1 BASH_ARGV=param1
| BASH_SOURCE: 0 1 my-debug-env ./example-xtrace
| BASH_LINENO: 0 1 5 0
| FUNCNAME: 0 1 debug main
# XTRACE Logs
[me@linux ~]$ cat xtrace.out
+[bash:20] [[ -v NOOP ]]
+[bash:33] trap 'echo ERR trap from ${FUNCNAME:-MAIN} context. $BASH_COMMAND failed with error code $?' ERR
+[bash:34] trap debug DEBUG
++[./example-xtrace:3] debug
++[./example-xtrace:26] echo '[ DEBUG ]| BASH_COMMAND=echo "This got executed"'
++[./example-xtrace:27] echo ' | BASH_ARGC=1 BASH_ARGV=param1'
++[./example-xtrace:28] echo ' | BASH_SOURCE: 0' '1 my-debug-env' ./example-xtrace
++[./example-xtrace:29] echo ' | BASH_LINENO: 0' '1 3' 0
++[./example-xtrace:30] echo ' | FUNCNAME: 0' '1 debug' main
+[./example-xtrace:3] echo 'This got executed'
++[./example-xtrace:4] debug
++[./example-xtrace:26] echo '[ DEBUG ]| BASH_COMMAND=v=$1'
++[./example-xtrace:27] echo ' | BASH_ARGC=1 BASH_ARGV=param1'
++[./example-xtrace:28] echo ' | BASH_SOURCE: 0' '1 my-debug-env' ./example-xtrace
++[./example-xtrace:29] echo ' | BASH_LINENO: 0' '1 4' 0
++[./example-xtrace:30] echo ' | FUNCNAME: 0' '1 debug' main
+[./example-xtrace:4] v=param1
++[./example-xtrace:5] debug
++[./example-xtrace:26] echo '[ DEBUG ]| BASH_COMMAND=[[ -z "${v}" ]]'
++[./example-xtrace:27] echo ' | BASH_ARGC=1 BASH_ARGV=param1'
++[./example-xtrace:28] echo ' | BASH_SOURCE: 0' '1 my-debug-env' ./example-xtrace
++[./example-xtrace:29] echo ' | BASH_LINENO: 0' '1 5' 0
++[./example-xtrace:30] echo ' | FUNCNAME: 0' '1 debug' main
+[./example-xtrace:5] [[ -z param1 ]]
By using this approach, while being an unlikely use case, you can also debug Bash commands from the command line using the -c
option.
[me@linux ~]$ y=9
[me@linux ~]$ TRACE=1 BASH_ENV=my-debug-env bash -c "x=1; y=$y; echo \$((x+y))"
Run TRACE mode
[ DEBUG ]| BASH_COMMAND=x=1
| BASH_ARGC=0 BASH_ARGV=
| BASH_SOURCE: 0 my-debug-env
| BASH_LINENO: 0 0
| FUNCNAME: 0 debug
[ DEBUG ]| BASH_COMMAND=y=9
| BASH_ARGC=0 BASH_ARGV=
| BASH_SOURCE: 0 my-debug-env
| BASH_LINENO: 0 0
| FUNCNAME: 0 debug
[ DEBUG ]| BASH_COMMAND=echo $((x+y))
| BASH_ARGC=0 BASH_ARGV=
| BASH_SOURCE: 0 my-debug-env
| BASH_LINENO: 0 0
| FUNCNAME: 0 debug
10
[me@linux ~]$ cat xtrace.out
+[bash:20] [[ -v NOOP ]]
+[bash:33] trap 'echo ERR trap from ${FUNCNAME:-MAIN} context. $BASH_COMMAND failed with error code $?' ERR
+[bash:34] trap debug DEBUG
++[bash:0] debug
++[bash:26] echo '[ DEBUG ]| BASH_COMMAND=x=1'
++[bash:27] echo ' | BASH_ARGC=0 BASH_ARGV='
++[bash:28] echo ' | BASH_SOURCE: 0 my-debug-env'
++[bash:29] echo ' | BASH_LINENO: 0 0'
++[bash:30] echo ' | FUNCNAME: 0 debug'
+[bash:0] x=1
++[bash:0] debug
++[bash:26] echo '[ DEBUG ]| BASH_COMMAND=y=9'
++[bash:27] echo ' | BASH_ARGC=0 BASH_ARGV='
++[bash:28] echo ' | BASH_SOURCE: 0 my-debug-env'
++[bash:29] echo ' | BASH_LINENO: 0 0'
++[bash:30] echo ' | FUNCNAME: 0 debug'
+[bash:0] y=9
++[bash:0] debug
++[bash:26] echo '[ DEBUG ]| BASH_COMMAND=echo $((x+y))'
++[bash:27] echo ' | BASH_ARGC=0 BASH_ARGV='
++[bash:28] echo ' | BASH_SOURCE: 0 my-debug-env'
++[bash:29] echo ' | BASH_LINENO: 0 0'
++[bash:30] echo ' | FUNCNAME: 0 debug'
+[bash:0] echo 10