Bash debugger based on vim editor
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Review
We can easy use bash options and commands to debug bash script, but sometime it is not comfortable.
If you use vim editor, then you can easy adopt your vim environment to debug your bash scripts!
You need do only 2 actions:
1. Add commands to ~/.vimrc
2. Add debugger script to ~/.vim folder
What you can do with this debugger?
You can:
1. Line to line run you script
2. Watch values of preferred variables and customize list of them
3. Create breakpoints in vim visually
4. Run script to next breakpoint
5. Enable or disable function trace
6. Run script without debugger
7. Remove all marks (breakpoints and function trace)
Keys to manage debugger
F9 - Remove all marks(breakpoints and function trace).
F8 - Execute script without debugger
F7 - Execute script in trace mode
F6 - Show/hide window with preferred variables to watch. Each variable must be defined in new line without spaces, {}, $, etc.
Example:
A
B[1]
C[@]
F5 - Place/Unplace breakpoint in current line.
F4 - Enable/Disable function trace.
Keys can be easy changed in .vimrc configuration file.
How to install debugger?
1. Add next lines to your ~/.vimrc:
:set number
" Underline current line
augroup CursorLine
au!
au VimEnter,WinEnter,BufWinEnter * setlocal cursorline
au WinLeave * setlocal nocursorline
augroup END
" Write&Quit to ~/.vim/variables if it lost focus
augroup VariablesFile
au WinLeave ~/.vim/variables wq
augroup END
" Trace script
fu! Trace()
" Save output of 'sign place' to file
redir > ~/.vim/places
execute "silent sign place file=" . expand("%:p")
redir end
" Save script to temporary file
execute ":w! ~/.vim/bash.debug.sh"
" Run debugger
execute ":! ~/.vim/debuger.sh"
endfunction
" Run script w/o trace
fu! Execute()
silent execute ":!clear"
" Save script to temporary file
execute ":w! ~/.vim/bash.debug.sh"
" Set executing permitions
silent execute ":! chmod 755 ~/.vim/bash.debug.sh"
" Run script
execute ":! ~/.vim/bash.debug.sh"
" Remove temporary script
silent execute ":! rm -f ~/.vim/bash.debug.sh"
endfunction
" Show/hide window with variables
fu! Variables()
" If current file is './vim/variables'
if expand('%:p:h:t') == ".vim"
if expand('%:t') == "variables"
" Save&Quit
execute ":wq!"
else
" Open new window
copen
" Load file with variables
execute ":e ~/.vim/variables"
endif
else
" Open new window
copen
" Load file with variables
execute ":e ~/.vim/variables"
endif
endfunction
" Enable/Disable breakpoint
fu! Breakpoint()
" Get current line
let currentrow = line(".")
" Remove file
silent execute ":! rm -f ~/.vim/places"
" Save output of 'sign place' to file
redir >~/.vim/places
execute ":silent sign place file=" . expand("%:p")
redir end
" Parse file and save sign's name to variable signname
let signname=system("grep '=".currentrow.".*id=.*breakpoint' ~/.vim/places | awk '{printf $3}' | sed 's/^.*=//'")
if empty(signname)
" If not exist sign name in current line
" Define breakpoint
execute ":sign define breakpoint" . currentrow . " text=BP texthl=Search linehl=Search"
" Place sign to current line
execute ":sign place ".currentrow." line=".currentrow." name=breakpoint".currentrow." file=".expand("%:p")
else
" If exist sign name in current line then undefine it
exec ":sign undefine ".signname
endif
" Remove file
silent execute ":! rm -f ~/.vim/places"
" Redraw screen
redraw!
endfunction
" Enable/Disable function trace
fu! FuncTrace()
" Remove file
silent execute ":! rm -f ~/.vim/places"
" Save output of 'sign place' to file
redir >~/.vim/places
execute ":silent sign place file=" . expand("%:p")
redir end
" Parse file and save sign's name to variable signname
let signname=system("grep '=1.*id=1.*functrace' ~/.vim/places | awk '{printf $3}' | sed 's/^.*=//'")
if empty(signname)
" If not exist sign name in current line
" Define breakpoint
execute ":sign define functrace text=FT texthl=Error"
execute ":sign place 1 line=1 name=functrace file=" . expand("%:p")
else
" If exist sign name in current line then undefine it
execute ":sign undefine functrace"
endif
" Remove file
silent execute ":! rm -f ~/.vim/places"
" Redraw screen
redraw!
endfunction
" Unmark all signs
fu! Unmark()
" Remove all signs
execute ":sign unplace *"
" Redraw screen
redraw!
endfunction
" Undefine all marks
nmap <F9> :call Unmark()<CR>
" Run script without debuging
nmap <F8> :call Execute()<CR>
" Run script with debuging
nmap <F7> :call Trace()<CR>
" Show or hide variables list
nmap <F6> :call Variables()<CR>
" Mark/Unmark BreakPoint
nmap <F5> :call Breakpoint()<CR>
" Mark/Unmark Function trace
nmap <F4> :call FuncTrace()<CR>
Comment
:set number and augroup CursorLine block is not critical and can be moved if wish.
You must be owner of this file.
2. Create debugger.sh script in ~/.vim/ folder.
#!/bin/bash
########
# Bash debugger for Vim v. 1.0
# Created by Oleksandr Mishchenko (c) 2018
# sasha.mishchenko@gmail.com
# Published under GPL license
########
clear
echo Executing in trace mode.
echo "Press 'Space' key to execute one line, Press 'g' key to execute to next break point."
if grep functrace ~/.vim/places &>/dev/null
then
sed "1a set -o functrace;trap 'rm -f ~/.vim/{places,bash.debug2.sh} &>/dev/null' EXIT;trap 'typeset -A mydebug_BP;while read mydebug_LINE; do ((\$mydebug_LINE>0)) 2>/d
ev/null && mydebug_BP[\$mydebug_LINE]=1;done < <(cat ~/.vim/places | grep \"id=.*breakpoint\" | awk \"{ print \\\\\$1 }\" | sed \"s/^.*=//\");while read mydebug_VAR; do
echo -en \"\\\e[0;32m\$mydebug_VAR:\\\e[1;32m\${!mydebug_VAR}\\\e[0m \";done < ~/.vim/variables;echo; echo -e \"\\\e[0;33m\$((LINENO-1)) \\\e[1;33m\$BASH_COMMAND\\\e[0
m\"; if [[ \"\${mydebug_BP[\$((LINENO-1))]}\" == 1 ]]; then mydebug_KEY=\"\"; fi; until [[ \"\$mydebug_KEY\" == \" \" || \"\$mydebug_KEY\" == \"g\" ]]; do read -sN1 myd
ebug_KEY;done; [[ \"\$mydebug_KEY\" == \" \" ]] && mydebug_KEY=\"\"' DEBUG" ~/.vim/bash.debug.sh > ~/.vim/bash.debug2.sh
else
sed "1a trap 'rm -f ~/.vim/{places,bash.debug2.sh} &>/dev/null' EXIT;trap 'typeset -A mydebug_BP;while read mydebug_LINE; do ((\$mydebug_LINE>0)) 2>/dev/null && mydebu
g_BP[\$mydebug_LINE]=1;done < <(cat ~/.vim/places | grep \"id=.*breakpoint\" | awk \"{ print \\\\\$1 }\" | sed \"s/^.*=//\");while read mydebug_VAR; do echo -en \"\\\e[
0;32m\$mydebug_VAR:\\\e[1;32m\${!mydebug_VAR}\\\e[0m \";done < ~/.vim/variables;echo; echo -e \"\\\e[0;33m\$((LINENO-1)) \\\e[1;33m\$BASH_COMMAND\\\e[0m\"; if [[ \"\${m
ydebug_BP[\$((LINENO-1))]}\" == 1 ]]; then mydebug_KEY=\"\"; fi; until [[ \"\$mydebug_KEY\" == \" \" || \"\$mydebug_KEY\" == \"g\" ]]; do read -sN1 mydebug_KEY;done; [[
\"\$mydebug_KEY\" == \" \" ]] && mydebug_KEY=\"\"' DEBUG" ~/.vim/bash.debug.sh > ~/.vim/bash.debug2.sh
fi
rm -f ~/.vim/bash.debug.sh &>/dev/null
chmod 700 ~/.vim/bash.debug2.sh
~/.vim/bash.debug2.sh
Set permition to execute it
chmod 755 debugger.sh
Debugger's code is not easy to read, because it must be created in 1 line. This line will be inserted in your script for debugging.
It is safe. It insert trap command which executes after each line in bash script. This trap show variables and control execution to next breakpoints.
Known issues
1. Breakpoints does not work on empty lines. Also breakpoints can not work on return, fi, then, else and other second part of composite bash operators. If need, you can use operator : . Command : (no operation) do nothing. This is DEBUG signal behaviour of command trap in bash.
2. It is not configured to debug 2 different scripts under 1 user. In this case will be executed and traced last loaded script.
3. Debugger use own variables, which starts from mydebug_. I think you will not use this prefix.))) If you use variable with similar name, but it is not declared, then your variable can be predefined with not empty value.
Debugger's command will be as second line of your script in trace mode. It always inserts after she-bang line.