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 usingunset
.
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
- Bash Hackers Wiki: http://wiki.bash-hackers.org/
- Functions: https://ryanstutorials.net/bash-scripting-tutorial/bash-functions.php