Skip to content
Rob Nagler edited this page Nov 9, 2018 · 10 revisions

Bash

Bash is available in all modern systems. Bash has also evolved with new syntax that fixes many of the word splitting and substitution problems.

Dockerization

Docker has turned Bash into a maintstream programming language. Bash is the only programming language available on all distros, even busybox. That means that Dockerfiles are essentially bash scripts. Entry points are bash scripts. And so on.

However people have not embraced bash. The Docker hub doesn't allow you to build from a bash script. It relies on a Dockerfile existing. This yields very awkward Dockerfile/bash code.

Also, people don't understand how to use Bash well so they are using a mix of modern and old versions. Anything using Docker is building with a modern distro, which has Bash 4.2 or 4.3.

Docker allows us to rely on modern bash.

Modularization

C is still a popular programming language. There are no name spaces, and neither are there in Bash. That's ok. Just be disciplined as in C.

Imports can then just use source (.).

Variables

declare -g is the way you should declare global variables. If you don't, it's like local, and you'll get unbound variable errors if you source the file in a function.

Problems

Bug in unbound variables?

$ set -e
$ set -u
$ x=([y]=1)
bash: y: unbound variable

You can't even do this:

$ x[y]=1
bash: y: unbound variable

Useful Links

http://mywiki.wooledge.org/BashPitfalls

Comparison

Here is an example of a modern Bash script:

#!/bin/bash
set -euo pipefail
vdi=$1
VBoxManage list hdds | while read l; do
    if [[ ! $l =~ ^[^:]+:[[:space:]]*(.+) ]]; then
        continue
    fi
    if [[ UUID == $BASH_REMATCH[1] ]]; then
        u=BASH_REMATCH[2]
    elif [[ Location == $BASH_REMATCH[1] && $vdi == $BASH_REMATCH[2] ]]; then
        uuid=$u
        break
    fi
done
if [[ -n $uuid ]]; then
    VBoxManage closemedium disk "$uuid" --delete
fi

And, in Python:

import re
import subprocess
vdi = sys.argv[1].lower()
o = subprocess.check_output(['VBoxManage', 'list', 'hdds'])
uuid = None
for l in o.splitlines():
    m = re.search(r'^([^:]+):\s*(.+)', l)
    if not m:
        continue
    t = m.group(1).lower()
    if t == 'uuid':
        u = m.group(2)
    elif t == 'location' and vdi == m.group(2).lower():
        uuid = u
        break
if uuid:
    subprocess.check_call(['VBoxManage', 'closemedium', 'disk', uuid, '--delete']);

And, in Perl:

use strict;
use warnings;
my($file_re) = qr{^Location:\s*\Q$ARGV[1]\E$}i;
my($uuid, $u);
foreach my $x (`VBoxManage list hdds`) {
    chomp($x);
    if ($x =~ /^UUID:\s*(\S+)/i) {
        $u = $1;
    }
    elsif ($x =~ $file_re) {
        $uuid = $u;
        last;
    }
}
if ($uuid) {
    system([qw(VBoxManage closemedium disk), $uuid, '--delete']);
}

The are some subtle differences between the various implementations, but they all do the same thing. The differences can be subtle, e.g. the Bash version doesn't compare the file insensitively and the Perl version doesn't check the result of the VBoxManage calls.

Clone this wiki locally