Learning Bash

The beginning of a long road

Overview

Historically I'm a Windows developer (C/C++/C#). I've been using more and more Linux tooling and, day by day, the Linux learning is creeping in. A massive sprawling knowledge space, unfamiliar to me. But over and over again there are some basic bash scripting techniques I need, this is them.

Assumptions: you have written some kind of code before.

My examples are tested using Bash on Ubuntu on Windows. bash version 4.3.11 use bash --version to find this.

Variables

Basics

context bash
Simple someVar=a_value
Double quotes " someVar="use quotes if you need spaces"
Single quotes ' someVar='quotes with spaces'

🗒 Note: no space between variable name or equals and value.

To reference the variable prefix with a $ as below.

A variable may be constructed from others and is evaluated upon construction

h=hello
w=world
# variables expanded using double quotes
hw="$h $w"
# single quotes means no variable expansion
wrong='$h $w'

# displays `hello world`
echo $hw

# displays `hello world`
echo "$h $w"

# displays `$h $w`
echo $wrong

Clear a variable with unset, e.g. unset someVar. Alternatively use someVar=, there appears to be no difference.

🗒 Note: the lack of $ prefix when using unset.

Environment vars

A script cannot set any environment variables in the calling parent scope, just as in Windows.

A script can set environment variables in a child process through the export command.

someVar="a child process needs this"

# just the variable name no '$'
export someVar

# will be looking for env var someVar
child_process

Built in values

As with Windows there is an integer error code from a process. Use $? to read it.

Parameter expansion

The basic variable expansion as above with the echo is just the tip of the iceberg.

The motivation for this blog post was initially this very topic. I'd seen both $someVar and ${someVar} used apparently interchangeably and without rhyme or reason. This is what it is really used for.

The bash interpreter will attempt to find a variable named following the $. In some cases it is not possible to parse this, e.g.

h="hello "
# this fails
echo "$hworld"
# need to wrap the variable in {}
echo "${h}world"

Using the {} syntax we can apply transforms to the variable contents:

  • ^ uppercase
  • , lowercase
  • ~ swap case

Use 1 (for the first character only) or 2 (the whole value) of the transform symbols. e.g.

x="Abc"
y="xyZ"
echo "${x} ${x,} ${y~} ${y^^}"
# output: Abc abc XyZ XYZ

Arithmetic expressions must be wrapped inside $(()).

a=12
b=4
echo $((a+b)) $((a/b))
# output 16 3

Other expansions include extracting a substring, regular expressions, default values. See a great explanation here

Conditionals (aka tests)

🗒 Note: The history of the syntax is described in the bash hackers Wiki. Understanding that helped my understanding.

# this is a comment, you get the idea.

# strings
x="a string"
if [ $x == "a string" ]
then
    echo "x is correct"
fi

# eq works with numbers too
x=42
if [ $x -eq 42 ]
then
    echo "x is correct"
fi

# in one line
if [ $x == 42 ];then echo "x is correct";fi

🗒 Note: It is really important to leave a space between the opening [ and the first part of the expression and the last part of the expression and the closing ]

If you get the error bash: [: too many arguments, consider how the parts between the [] are being expanded as arguments to the [ aka test build-in command.

Functions

a="global value a"
b="global value b"

echo "a=$a b=$b"

a_function () {
    a="global changed within function"
    local b="locally scoped b"
    echo "Doing it...first arg was $1"
    echo "a=$a b=$b"
    return 4
}

a_function "hello"

echo "a=$a b=$b"

echo "Done $?"

Output

a=global value a b=global value b
Doing it...first arg was hello
a=global changed within function b=locally scoped b
Done 4
a=global changed within function b=global value b

Notes:

  • The spaces in the function declaration are required
  • The return value is optional but must be numeric (like a standard cli command)
  • The $? built-in variable collects the return value for the last command or function
  • arguments are by position, rather than explicitly named and accessed using $n where n is their 1 based index
  • local is used to scope variables to a function
  • globals are accessible for read and write within a function

Enough …

… for now. Looping, capturing literal expressions, using sed and curl are all really useful. I still keep using dir or ls when I mean find, grep seems to get complicated when you stray from the simple stuff and jq is super powerful but it's usage is completely ‘read the docs’ driven.

References