Why Bash is Hard to Use

The Bourne Again Shell, and like many shell programming languages, has many quirks. Compared to many scripting languages, bash has:

Required White-space

Whitespace is required around branch keywords: if, elif, when, case. Otherwise the token is considering a command on the filesystem.

bash

if [ -f /etc/passwd ]; then
if[ -f /etc/passwd ]; then
[ is actually a hardlink to /bin/test.

python

if file_exists("/etc/passwd"):
if(file_exists("/etc/passwd") and (1==1)):

Prohibited White-space

Variables may not have white-space around an equal sign =. Variable values may contain whitespace, but you have to quote it to keep it properly preserved.

bash

computer=$bob
computer="$host and $domain"
computer = bob
computer = $host and $domain

python

computer = "host"

Expansion Brackets

Expansion brackets are not unique to bash. Languages such as PHP, Perl and Python use them, too. But in bash, they are required for indexing into an array. Various characters can get interpreted as word characters, so when combining series of variables joined by punctuation, base safe and use brackets. / - _ .

bash

computer="$host$domain"
computer="${host}.${domain}"
computer="${host}_${domain}
computer=$host_$domain

python

computer = f"{host}.{domain}"

Parenthesis, Curly and Square Brackets

Creating Arrays

Bash needs parenthesis to create arrays:

bash

computer=("host1" "host2")
computer+=("host3")
computer={ "host1", "host2"}
computer=[ "host1", "host2"]

python

computer = hosts[2]

Reading Arrays

Bash needs curly brackets to see into arrays:

bash

computer=${hosts[2]}
for host in "${computer[@]}"; do ...
computer=$hosts[2]
for host in $computer; do ...
for host in $computer[@]; do ...

$hosts evaluates to ${hosts[0]}

python

computer = hosts[2]

Subshells

Execute a list of commands in a subshell

bash

echo "Doing work:"
( xeyes& firefox& kate& )

python

commands=[ "xeyes", "firefox", "kate" ]
for cmd in commands:
    os.system( cmd )

Command Substitution

Bash can capture the output of a command in a variable using backticks or parens: $( )

bash

kernel=`uname -r`
kname=`uname -s`
echo "$kname $kernel `uname -m` $(uname -p)"

python

commands=[ "uname -r", "uname -s" ]
subp = subprocess.run(commands,
                      stdout=subprocess.PIPE,
                      text=TRUE)
print(subp.stdout)

Tests and Comparisons

Bash has two ways of doing tests and comparisons:

  • [ or /bin/test
  • [[ ]] and (( )) Square brackets are used for string comparisons, parens are used for numerical comparisons. But don't forget that single parens are for subshells, and [ ] are for POSIX tests.

    bash

    kernel=`uname -r`
    kname=`cat /dev/null`
    if [[ "$kname" == "$kernel" ]]; then ...
    
    year=$(date | cut -d' ' -f 7)
    if (( $year > 2000 )); then ...
    
    if [ $year -gt 2000 ]; then ...

    python

    if a > 1: ...
    if a == "1": ...  

    Line-noise characters

    You can spend a lot of time in the bash(1) learning expansions: here are some gems:

    Where are my dictionaries?

    Associative arrays were added to bash close to year 2002.

    bash

    declare -A hosts
    hosts=([one]="192.168.45.1" [two]="192.168.45.2")
    hosts+=([three]="192.168.45.3")
    echo ${hosts[one]}
    
    hosts=() # must declare -A
    hosts[one]="192.168.45.1" # use +=([one]=)
    hosts[one] = "192.168.45.1" # no spaces
    

    python

    hosts={
        "one": "192.168.45.1",
        "two": "192.168.45.2" }
    hosts["three"] = "192.168.45.3"