dikiy_mujchina: (Default)
[personal profile] dikiy_mujchina

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.
Мітки:

Про мене

dikiy_mujchina: (Default)
dikiy_mujchina

December 2019

S M T W T F S
1234 567
891011121314
15161718192021
22232425262728
293031    

Мітки

За стиль дякую

Показати приховане

No cut tags
Page generated Jun. 18th, 2025 12:03 pm
Powered by Dreamwidth Studios