-
Notifications
You must be signed in to change notification settings - Fork 4
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.
Use declare
instead of local
so can be relocated in any context.
Declare variables simply and then assign to the output of a subshell:
declare foo
foo=$(bar)
declare -a ray
ray=( $(ls foo*) )
This allows the error to cascade so that Bash exits (assumes set -e
).
Docker has turned Bash into a mainstream programming language. Bash is the only programming language availabple 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.
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
(.
).
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.
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
http://mywiki.wooledge.org/BashPitfalls
Here is an example of a modern Bash script:
set -euo pipefail
vdi=$(realpath -e "${1:?usage: $0 virtual-device-id}")
uuid_re='^UUID: *(.+)'
file_re='^Location: *(.+)'
VBoxManage list hdds | expand | while IFS= read -r l; do
if [[ $l =~ $uuid_re ]]; then
u=${BASH_REMATCH[1]}
elif [[ $l =~ $file_re && ${BASH_REMATCH[1]} == $vdi ]]; then
VBoxManage closemedium disk "$u" --delete
break
fi
done
And, in Python:
import os.path
import re
import subprocess
import sys
assert len(sys.argv) > 1, \
'usage: {} virtual-device-id'.format(sys.argv[0])
vdi = os.path.abspath(sys.argv[1])
assert os.path.exists(vdi), \
'{}: file does not exist'.format(vdi)
uuid_re = re.compile(r'^UUID:\s*(.+)')
file_re = re.compile(r'^Location:\s*' + re.escape(vdi) + '$')
o = subprocess.check_output(['VBoxManage', 'list', 'hdds'])
for l in o.splitlines():
m = uuid_re.search(l)
if m:
u = m.group(1)
elif file_re.search(l):
subprocess.check_call(['VBoxManage', 'closemedium', 'disk', u, '--delete'])
break
And, in Perl:
use strict;
use warnings;
use File::Spec ();
die("usage: $0 virtual-device-id\n")
unless $ARGV[0];
my($vdi) = File::Spec->rel2abs($ARGV[0]);
my($uuid_re) = qr{^UUID:\s*(.+)};
my($file_re) = qr{^Location:\s*\Q$vdi\E$}i;
my($u);
foreach my $l (`VBoxManage list hdds`) {
chomp($l);
if ($l =~ $uuid_re) {
$u = $1;
}
elsif ($l =~ $file_re) {
my($c) = [qw(VBoxManage closemedium disk), $u, '--delete'];
my($e) = system($c);
die("@$c: exit=$e\n")
unless $e == 0;
last
}
}
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.
Mac OS X is stuck on version 3.2.57. Here are some differences:
- ${foo,,} (make lower case) not supported
- [[ $a
&& $b ]] requires backslashes:
[[ $a
&& $b ]]