Basics of Bash programming, aka shell scripting

Bash, an old language, first written by the ancient Romans onto stone slabs.

Inspirational Bash quotes to get your juices flowing:

Fat guy who knows C++. Hire me to replace @HipsterHacker’s code with a set of very small shell scripts.


bash is a cursed hammer, when wielded everything looks like a nail, especially your thumb.


Bash isn’t actually very trendy language, as a front end developer you don’t really need to know it, it’s more of a system administrator language. That said, it’s really good for little tasks done in the command line, like automating database creation, or server block or vhosts creation, or to take a simple backup of a file or a whole file system etc. I quite like it in a weird way, and think it’s a good addition to your toolbelt.


Bash supports variable like any other language.

Setting variables

some_string="hello I am string"

Global variables are usually uppercased, it’s just a naming conventions, though. Try keeping these to minimum:


Variables can always be assigned to a new value, sometimes unintentionally, that can fought by making it readonly:

readonly PROG_NAME=$0

If a variable is defined inside a function, and is not needed outside the function, it should be declared as local:

some_func() {
    local string="Hello world!"

That $string variable stays inside the function since, it’s defined as local, and is only needed in the function. Keep globals to minimum, they make programming cryptic.

Using variables

Access them by prefixing the variable with a dollar symbol:

string="Cheesy peas!"
echo $string

If another character is “touching” the end of the variable, it needs to be interpolated, here’s how to do that:

echo path/to/file/some-${photo}.jpg


As simple as:

say_hello() {
    echo "Hello!"

Usage is as dumbfoundedly banal:


Function arguments

Arguments in a function are not defined like they are in, say, JavaScript. They can’t be named, they’re represented by a number only:

say_something() {
    echo $1 # Refer the first parameter as 1
    echo $2 # And the second as 2, and so on

# Usage:
say_something Hello You

# Output:

If there’s more parameters, they’d be $4, $5… and so on. Since variables don’t have names, and if they’re many, it gets cryptic indeed. Good new is that we can make them more intelligible:

say_something() {
    # Vars
    local greeting=$1 # Note the local
    local user=$2

    # That looks pretty clear
    echo $greeting $user

# Use it:
say_something Hello You

# Outputs:
Hello You

Also, note the local keyword, it makes the variables accessible only in that function. A kind of a precautionary defensive best practice, you know.

Spaces in parameter names, use quotes:

say_something "Good day" "Mr. Slartibartfast"
# Outputs:
Good day Mr. Slartibartfast

Here’s another, quite pointless, example:

change_owner() {
    local filename=$1
    local user=$2
    local group=$3

    chown $user:$group $filename

Access all parameters with $@:

say_something() {
    echo $@

# Use it:
say_something Hello You

# Outputs:
Hello You

$# means the number of arguments passed to the function:

say_something() {
    # Vars
    local expected_args=2
    local greeting=$1 # Note the local
    local user=$2

    if [ $# -ne $expected_args ]; then
        echo "Usage: greeting user"
        echo $greeting $user

Returning values from functions

The most obvious thing is to echo out something in a function:

helloer() {
    echo "Hello!"

Many times you just want to return the function. Bash doesn’t return values from functions like most other programming languages. But, you can assign a value to a global variable and then use that variable:

some_fn() {

# Then use it like any other variable
echo "$output is a word."

# Outputs
something is a word

Read more on this subject on this great article.

If else logic

Let’s use these three conditions as examples (scroll down for more condition examples):

  • [ -z $var ]: checks if variable is empty, this’ll be our example condition.
  • [ "$var" = true ]: checks if var is set to true.
  • [ -d $var ]: checks if directory.
if [ -z $var ]
    echo "Something"

Can also be written like this:

if [ -z $var ]; then
    echo "Something"

Else if

if [ -z $var ]
    echo "Something"
elif [ "$var" = true ]
    echo "Something else"
    echo "None matched"

AND &&, and OR ||

Using ‘AND’ && and ‘OR’ ||. In this we need use the double bracket syntax:

if [[ -z $var ]] || [[ -d $dir ]]
    echo "Something"

That checks if the variable $var has a value OR variable $dir is directory. Same thing for AND logic:

if [[ -z $var ]] && [[ -d $dir ]]
    echo "Something"

Make it intelligible

Stuff like this: if [[ -z $var ]] && [[ ! -d $dir ]] looks a bit complex and daunting. Bash is an old language and not the most intelligible for that matter, but it can be made really clear and readable with little effort.

Everything is a function, everything

Let’s take the above example, it has two conditions.

  1. Check if variable is empty
  2. Check if is directory

Let’s create two aptly named functions:

is_empty() {
    local var=$1
    [[ -z $var ]]

is_dir() {
    local dir=$1
    [[ -d $dir ]]

Put them in good use:

if is_empty $var && is_dir $dir
    echo "Something"

Jesus Christ! That’s like reading plain English! Even a non programmer can understand what’s going on there, we went from jargon to English with very little effort. From now on in this article we’re going to use this approach on everything.

You can bake any conditional command into a function like that, scroll down to see all the if expressions in Bash.

Using colors and text styles

Like in front end we use hex and rgb, bash uses Ansi colors, the color codes look like this:

No color     0m
Black        0;30     Dark Gray     1;30
Blue         0;34     Light Blue    1;34
Green        0;32     Light Green   1;32
Cyan         0;36     Light Cyan    1;36
Red          0;31     Light Red     1;31
Purple       0;35     Light Purple  1;35
Brown/Orange 0;33     Yellow        1;33
Light Gray   0;37     White         1;37

Then you’d use them like this:

echo -e 'e[0;32m'Green text
# It seems to be working without the leading zero also
echo -e 'e[32m'Green text

After echoing the green value '\e[32m', all text after that would be green, better stop the green with no color:

echo -e 'e[0;32m'Green text'33[0m'

These won’t work in OS X bash, it just outputs the color codes as text, they need to be escaped with \033 and not with \e, like so:

echo -e '33[0;32m'Green text'33[0m'

That should work in Linux systems also (as far as I know, drop a comment if you have more knowledge on this).

To make it more humane, the colors can be turned into functions:

green() {
    echo -e '33[32m'$1'33[m'

red() {
    echo -e '33[31m'$1'33[m'

# Usage
red "This is some red text"

The colors, and text styling, can be variables. Here’s a whole set of them:

RCol='33[0m' # Text Reset

# Regular        Bold              Underline         High Intensity    BoldHigh Intens    Background        High Intensity BGs
Bla='33[0;30m';  BBla='33[1;30m';  UBla='33[4;30m';  IBla='33[0;90m';  BIBla='33[1;90m';  On_Bla='33[40m';  On_IBla='33[0;100m';
Red='33[0;31m';  BRed='33[1;31m';  URed='33[4;31m';  IRed='33[0;91m';  BIRed='33[1;91m';  On_Red='33[41m';  On_IRed='33[0;101m';
Gre='33[0;32m';  BGre='33[1;32m';  UGre='33[4;32m';  IGre='33[0;92m';  BIGre='33[1;92m';  On_Gre='33[42m';  On_IGre='33[0;102m';
Yel='33[0;33m';  BYel='33[1;33m';  UYel='33[4;33m';  IYel='33[0;93m';  BIYel='33[1;93m';  On_Yel='33[43m';  On_IYel='33[0;103m';
Blu='33[0;34m';  BBlu='33[1;34m';  UBlu='33[4;34m';  IBlu='33[0;94m';  BIBlu='33[1;94m';  On_Blu='33[44m';  On_IBlu='33[0;104m';
Pur='33[0;35m';  BPur='33[1;35m';  UPur='33[4;35m';  IPur='33[0;95m';  BIPur='33[1;95m';  On_Pur='33[45m';  On_IPur='33[0;105m';
Cya='33[0;36m';  BCya='33[1;36m';  UCya='33[4;36m';  ICya='33[0;96m';  BICya='33[1;96m';  On_Cya='33[46m';  On_ICya='33[0;106m';
Whi='33[0;37m';  BWhi='33[1;37m';  UWhi='33[4;37m';  IWhi='33[0;97m';  BIWhi='33[1;97m';  On_Whi='33[47m';  On_IWhi='33[0;107m';


Possible usage:

error_message() {
    echo "$Red Something went wrong. $RCol"

All the if expressions in bash

And here’s a table of all the if expressions in Bash.

Primary Meaning
[ -a FILE ] True if FILE exists.
[ -b FILE ] True if FILE exists and is a block-special file.
[ -c FILE ] True if FILE exists and is a character-special file.
[ -d FILE ] True if FILE exists and is a directory.
[ -e FILE ] True if FILE exists.
[ -f FILE ] True if FILE exists and is a regular file.
[ -g FILE ] True if FILE exists and its SGID bit is set.
[ -h FILE ] True if FILE exists and is a symbolic link.
[ -k FILE ] True if FILE exists and its sticky bit is set.
[ -p FILE ] True if FILE exists and is a named pipe (FIFO).
[ -r FILE ] True if FILE exists and is readable.
[ -s FILE ] True if FILE exists and has a size greater than zero.
[ -t FD ] True if file descriptor FD is open and refers to a terminal.
[ -u FILE ] True if FILE exists and its SUID (set user ID) bit is set.
[ -w FILE ] True if FILE exists and is writable.
[ -x FILE ] True if FILE exists and is executable.
[ -O FILE ] True if FILE exists and is owned by the effective user ID.
[ -G FILE ] True if FILE exists and is owned by the effective group ID.
[ -L FILE ] True if FILE exists and is a symbolic link.
[ -N FILE ] True if FILE exists and has been modified since it was last read.
[ -S FILE ] True if FILE exists and is a socket.
[ FILE1 -nt FILE2 ] True if FILE1 has been changed more recently than FILE2, or if FILE1 exists and FILE2 does not.
[ FILE1 -ot FILE2 ] True if FILE1 is older than FILE2, or is FILE2 exists and FILE1 does not.
[ FILE1 -ef FILE2 ] True if FILE1 and FILE2 refer to the same device and inode numbers.
[ -o OPTIONNAME ] True if shell option “OPTIONNAME” is enabled.
[ -z STRING ] True of the length if “STRING” is zero.
[ -n STRING ] or [ STRING ] True if the length of “STRING” is non-zero.
[ STRING1 == STRING2 ] True if the strings are equal. “=” may be used instead of “==” for strict POSIX compliance.
[ STRING1 != STRING2 ] True if the strings are not equal.
[ STRING1 < STRING2 ] True if “STRING1” sorts before “STRING2” lexicographically in the current locale.
[ STRING1 > STRING2 ] True if “STRING1” sorts after “STRING2” lexicographically in the current locale.
[ ARG1 OP ARG2 ] “OP” is one of -eq, -ne, -lt, -le, -gt or -ge. These arithmetic binary operators “ARG2”, respectively. “ARG1” and”ARG2″are integers.

Table lifted from here.

Club-Mate, the beverage →