Wednesday, September 27, 2023

Debugging bash multiline commands in vi

The ancient vi (1976), and its enhanced and slightly more modern (1991) successor vim have become unnapreciated workhorses in recent years. They come with some interesting features you expect to find in modern IDEs. Case in point is they have built-in features that help debug scripts. In today's episode, let's start with following script:

cat > multiline.sh << 'EOF'
#!/bin/bash
# Test multiline commands
# 20230926

is_this_on=true

if [ is_this_on ]; then
        echo "yes"
        ls -l \ 
                --author \
                $(pwd)
fi
EOF

Realistically, it only shows 3 things:

  • if statements respond to true/false statements (the tests we feed them return that in the end of the day)
  • The --author option offerend in modern gnu ls.
iamdeving@desktop:~/dev/scripts/shell$ ./multiline.sh 
yes
total 92
-rwxrwxr-x 1 iamdeving iamdeving iamdeving 1092 Jan 23  2020 argtest.sh
drwxrwxr-x 3 iamdeving iamdeving iamdeving 4096 Jun 28  2021 ca-stuff
-rw-rw-r-- 1 iamdeving iamdeving iamdeving  168 Jan 30  2020 commandlinetest.sh
-rwxrwxr-x 1 iamdeving iamdeving iamdeving  153 Jun 16  2021 countest.sh
[...]
iamdeving@desktop:~/dev/scripts/shell$

What is the third thing the script shows? How to break a command into multiple lines, which is done adding \ to where we want to have a break. In this case, the ls statement was broken up into 3 lines for no reason whatsoever but to show \ in action:

        ls -l \ 
                --author \
                $(pwd)

There is a potential problem in the above: if you add a space after one of the \, it will break the statement because now the \ is making the space special, not the linefeed (a.ka.a \n) character. I intentionally did that and rerun the script, leading to the following error.

iamdeving@desktop:~/dev/scripts/shell$ ./multiline.sh 
yes
ls: cannot access ' ': No such file or directory
./multiline.sh: line 11: --author: command not found
iamdeving@desktop:~/dev/scripts/shell$ 

If you are using vim, and have it configured to highlight the syntax of programming languages (it also does a great job with html, Python, github markup language, Jekyll), and others) and open the file, you will notice one of the \ is red. That indicates something is fishy around it. Moving the cursor will show there is a space after the slash:

That picture is great if

  • You have setup vi to be in "helpful editing mode." But, it is not so great to show you there is an extra space there until you manually move your cursor there (I myself highlight the entire area and see if something that should not exist is hightighted). My trick is really not that helpful unless you know to look for it.
  • You do not have vision impairements; it relies on you being able to see the colours (yes you can configure that), or being able to see at all.

Can we do something for those who do not meed the above requirements? Actually, yes. vi was created in a time where all text was one colour, usually green. So, it has aids that always work no matter the language you are editing or how fancy your terminal session is. In this case, while we have that file open, type :set list. You will not see the same file with some new characters added.

#!/bin/bash$
# Test multiline commands$
# 20230926$
$
is_this_on=true$
$
if [ is_this_on ]; then$
^Iecho "yes"$
^Ils -l \ $
^I^I--author \$ 
^I^I$(pwd)$     
fi$

The ^I represents a tab while the $ stands in for a linefeed character (we are editing a file written in Linux/UNIX/MacOS here, so it does not have carriage return characters Windows likes so much). If you look at the two lines that have a \ in the end, one of them have a space between the \ and the $ characters: we found the culprit.

If you do not want to use vi another humble option is cat:

iamdeving@desktop:~/dev/scripts/shell$ cat -A multiline.sh 
#!/bin/bash$
# Test multiline commands$
# 20230926$
$
is_this_on=true$
$
if [ is_this_on ]; then$
        echo "yes"$
        ls -l \ $
                --author \$
                $(pwd)$
fi$
iamdeving@desktop:~/dev/scripts/shell$ 

The uses for this feature do not end there. You can view special characters (besides tabs) and ensure there is nothing hidden that should not be there, which may not be as easy with helpful GUI-based IDEs.