Bashegod - an introduction to bashscripting

Posted by

Bash shell
Text awkwardly placed somewhat to the left of the center in a shell. Well, that's not what you see everyday!

Bash is the default shell for Mac OS X and Linux. The name is an acronym for Bourne-again Shell, and is the command processor you typically use when you fire up "Terminal". Join the OP on his travel in this ancient and confusing environment, as he unveils the mysteries of bash-scripting.

I wanted to make a script that could let me show a “presentation” in Terminal and what other tool is better suited than bash scripting? Coming from OOP and high-level script languages, the difference was brutal. Did you know that 0 equals true in bash scripting? Yes, its like that. All the time.

Introduction

Just about everything you do in bash scripting is execute commands. You write a command, probably with arguments, and then get some output. You can store the output in variables or use them right away by piping the output to some new command, or stuffing a command in the argument list of a different command. You can see a list of commands in the terminal by listing the files in the directory “/bin/”

You see a cosy list of familiar commands. "ls" and "cd" and a few more you may have used. Now try "ls /usr/bin". This list is slightly longer. There are a couple more places where commands exist. The shell knows where they are because the paths to these directories are written as a colon separated list in a global variable named "PATH". Lets take a look at the content of path by using the command “echo”.

In my case I get this:

Analysing the string above, I see it checks these directories:

Every time you write in the shell and hit enter, the shell looks through the directories, and if it finds the command you have written, it uses it. If a command is in the first directory, it will use that, even if there are other commands with the same name in the other directories. You can add more paths to the path by “concatenating” “:/path/here” to either the start or the end of the variable.

Exit status

All commands give an exit status when they are done to signalize that the command executed successfully or not. If the signal is 0, then the command executed successfully, and if the signal is any number other than 0, an error happened. You can test this by printing out “$?”, which is a special global variable that holds the last executed command's exit status.

Builtins

Builtins are commands that are in the bash-shell. You can see available builtins if you look up “bash” in the manual pages:

Here you will find the familiar “if”, “for” and “while” with some more commands you might use in your bash scripting.

Writing bash script

The first thing to do in a bash script is to declare that you want to run this script with the bash-shell by writing !#/bin/bash on the first line.

To be able to run this script, you have to add execute permissions:

Write "./" to run the script:

Command substitution

Since nearly everything you will do is issue commands, you often need a way to save the output from a command. You can do this by writing your command in “$()” or by wrapping it with “`” in the assignment of a variable. Or you can use the output directly as a argument in a command.

Note that “ ` ” is not single quote, but something rare as a backtick.

Variables

To declare a variable, write a name and assign it a value. It’s important to not have any white spaces on either sides of the equal sign “=“. To use the variable, prefix the variable with a “$” sign. If you’re not prefixing it, bash will be using the letters from the variable name instead.

Bash doesn’t understand the increment sign “++” or decrement sign “—“. Bash doesn’t even understand directly inputted math expressions like “1 + 1”. So if you try to do calculations to a variable like this “MYVAR=1 + 1” it will fail. To calculate in bash, you have to use a command. There are many different calculation commands you can use. I will mainly use “expr”.

Im using “expr” command to calculate the new value.

Arrays

To use values in the array, you have to write it in a special way. Otherwise it will be treated as a path.

If

If is a builtin command which works like this:

It starts with if and ends with fi (if backwards). Test commands can be any command. If the command's exit status is 0 then it is true.

To test strings and numbers we have to use square brackets.

The operators “=“, “>” and “<“ are used only for strings. If you want to compare integers you must use the these special things: “-eq”, “-gt”, “-ge”, “-lt” and “-le”.

The weird thing with the if test above, is that the “[“ is actually a command. If you list the commands in the /bin directory you can see it there. And you can read the documentation with “man [“. The “[“ is the command and everything after it is an argument for that command. That is why the command fails if you forget to add spaces between “[“ and the next argument, even though it looks like a “traditional” if() test. The “[“ command sends back "0" as an exit status if it was successful.

while

While syntax uses "while", "do" and "done".


for

There are two types of for loops. One resembles what is typically known as a for-each loop, while the second one is the more traditional "as long as int i is lesser than something increment by 1"-loop. For loops use the syntax "for", "do", "done".

case

The “case” is the builtin equivalent to a switch. It evaluates a variable and finds a matching value. The values are embraced by parentheses and the default value is the “ * “ char. Any value that does not find a match will execute the commands after the star sign. Since each match takes multiple commands, divided by one semicolon, you have to terminate a match with double semicolons. End case with “esac” (case backwards).

Functions

You can write functions in shell-scripting. They are called “subroutines”, and have to be declared before you use them. You can also import subroutines that are written in other files. The “import script“ command is “source”. After you have written them, you can execute them the same way that yo execute other commands.

The same as with normal commands, you write arguments as a space separated list after the subroutine name. All the arguments are available in the subroutine as “$1”, “$2” … etc. If you need local variables you have to explicitly write “local” before the variable.

The presentation tool

You should have some knowledge of bash-scripting by now, which will be handy when I show my leet presentation tool. It is called “zab”, and it takes a single parameter. The parameter is a file, which should be written in the special “zab” format. The reason I called it zab is mostly because I wanted a short name - and “zab” isn’t taken by any other command.

You can check out the tool at github: zab at github.

The script is divided in three parts. The first part defines subroutines. The second part initializes and provides feedback for invalid arguments. The third part is the program loop.

Initialization

The first thing the script does is to fetch the first argument and store in in a variable called “FILE”. Then it checks if the file is empty and if the file exists. The “-z” checks if the string is zero, and the -f checks if the file exists and is a regular file. If everything is OK, the script continues to clear the shell window, and to reset all previously appended styles. The “tput” command has different screen functions. The “tput em” is clearing the style, and “tput cl” is clearing the shell-window.

The loop

The “read” command waits for input. The “-n 1” parameter means it will only take 1 character before it returns. The “-s INPUT” means it will store the input value in the “INPUT” variable.

The while loop will not stop until the input is a “q” character. And the case inside the while-loop will execute the appropriate subroutines. When these subroutines are done, the script will wait for the next input.

Subroutines

In the subroutines, you find an inline execution of the command “awk”. Awk is a text scanning and processing command: a different kind of “grep”. It takes an input file and a pattern, and outputs the strings that match the pattern from the file. The only thing I do with the awk command is write out single lines with a row number with the “NR” parameter.

The rest of the subroutines mainly increase or decrease the “current_line” variable, and check for format-tokens.

More about bash

If you want to find out more about shell-scripting, you can check out the resources, which I found very useful, below.

Comments