A good[ish] website
Web development blog, loads of UI and JavaScript topics
This post outlines some basic Bash programming concepts, such as: passing in params to a script, what is the shebang, how to define variables and functions, how to set immutable and scoped variables, how to handle conditional logic etc.
Bash isn’t necessarily a very trendy language, it’s a bit like a Lada in that sense, uncool but something that is there for you always.
Bash scripts are files that you call from the command line.
Say, if we would call a create_database.sh
script like so:
$ create_database.sh db_name bob 5weetp45w0rd
In the file we can then access the script parameters with numbers $0...
. 0 being the script name, ofter referred as prog_name
:
#!/bin/bash
echo $0 # create_database.sh
echo $1 # db_name
echo $2 # bob
echo $3 # 5weetp45w0rd
This #!/bin/bash
in the beginning of the file. The characters hash and exclamation mark #!
signals the beginning of the interpreter directive. It should be the very first line of the file.
The shebang is followed by the interpreter /bin/bash
, an absolute path to a binary. The shebang can also look different i.e.: #!/usr/bin/env bash
, depending what bash version you’re running (for example bash 4) or what system you’re on.
Bash supports variable like any other language.
some_string="foo"
Global variables are usually written in all caps, it’s just a naming conventions, though. Generally it’s good to keep globals to minimum:
SOME_GLOBAL="foo"
Variables can always be assigned to a new value, sometimes unintentionally, that can prevented by making it readonly
:
readonly SOME_GLOBAL="foo"
If a variable is defined inside a function, and is not needed outside the function, it can be declared as local:
hello_function() {
local greeting="Hello world!"
}
That $greeting
variable stays inside the function since it’s defined as local
, and is only needed in the function. It’s nice that the variable can be scoped to the function, because: globals make programming cryptic
, as they say.
Access bash variables by prefixing the variable name with a dollar symbol:
string="Cheesy peas!"
echo $string
If another character is "touching" the end of the variable, it needs to be interpolated:
file_name=photo
echo path/to/file/some-${photo}.jpg
We already brushed function earlier, but here’s a simple example again:
say_hello() {
echo "Hello!"
}
Call the function like so:
say_hello
# Prints "Hello!"
Arguments in a function are not defined like they are in, say, JavaScript. They can’t be named, they’re represented by a number, same way as script params are:
say_something() {
echo $1 # Refer the first parameter as 1
echo $2 # And the second as 2, and so on
}
# Usage:
say_something Hello You
# Hello
# You
If there’s more parameters, they’d be $4
, $5
... and so on. Since variables don’t have names, and if there’s a multitude of them, it might get cryptic. That number mess can be made more intelligible by reassigning the numbers to more human readable variables:
say_something() {
# Vars
local greeting=$1 # Note the local
local user=$2
# That looks pretty clear
echo $greeting $user
}
# Use it:
say_something Hello You
# Hello You
The local
keyword makes the variables accessible only in that function. A kind of a precautionary defensive best practice, you know.
Spaces in parameter names, use quotes:
say_something "Good day" "Mr. Slartibartfast"
# Outputs: Good day Mr. Slartibartfast
Here’s another, quite pointless example:
change_owner() {
local filename=$1
local user=$2
local group=$3
chown $user:$group $filename
}
Access all parameters with $@
:
say_something() {
echo $@
}
# Use it:
say_something Hello You
# Outputs:
Hello You
$#
means the number of arguments passed to the function:
say_something() {
# Vars
local expected_args=2
local greeting=$1
local user=$2
if [ $# -ne $expected_args ]; then
echo "Usage: greeting user"
exit
else
echo $greeting $user
fi
}
You could echo
out something in a function:
helloer() {
echo "Hello!"
}
But many times you just want to return something rather. Bash doesn’t return values from functions like most other programming languages do. But, you can assign a value to a global variable and then use that variable:
some_fn() {
output="something"
}
# Then use it like any other variable
echo "$output is a word."
# something is a word
Read more on this subject on this great article.
Let’s use these three conditions as examples (scroll down for more condition examples):
[ -z $var ]
[ "$var" = true ]
[ -d $var ]
if [ -z $var ]
then
echo "Something"
fi
Can also be written like this:
if [ -z $var ]; then
echo "Something"
fi
The "unless" statement can be done with the elif
keyword:
if [ -z $var ]
then
echo "Something"
elif [ "$var" = true ]
echo "Something else"
else
echo "None matched"
fi
Using 'AND' &&
and 'OR' ||
. In this we need use the double bracket syntax:
if [[ -z $var ]] || [[ -d $dir ]]
then
echo "Something"
fi
That checks if the variable $var
has a value OR variable $dir
is directory. Same thing for AND logic:
if [[ -z $var ]] && [[ -d $dir ]]
then
echo "Something"
fi
Stuff like this: if [[ -z $var ]] && [[ ! -d $dir ]]
might look a bit thick. Assigning stuff to functions can help make things more readable (or just learn them LOL).
Let’s take the above example, it has two conditions:
Let’s create two aptly named functions:
is_empty() {
local var=$1
[[ -z $var ]]
}
is_dir() {
local dir=$1
[[ -d $dir ]]
}
Use them like so:
if is_empty $var && is_dir $dir
then
echo "Something"
fi
We went from jargon to English with very little effort.
You can bake any conditional command into a function like that, scroll down to see all the if expressions in Bash.
Bash uses Ansi colors, the color codes look like this:
No color 0m
Black 0;30 Dark Gray 1;30
Blue 0;34 Light Blue 1;34
Green 0;32 Light Green 1;32
Cyan 0;36 Light Cyan 1;36
Red 0;31 Light Red 1;31
Purple 0;35 Light Purple 1;35
Brown/Orange 0;33 Yellow 1;33
Light Gray 0;37 White 1;37
Then you’d use them like this:
echo -e 'e[0;32m'Green text
# It seems to be working without the leading zero also
echo -e 'e[32m'Green text
After the green value '\e[32m'
, all text will be green, regardless of new lines etc. Better stop the green with no color:
echo -e 'e[0;32m'Green text'33[0m'
These won’t work in OS X bash, it just outputs the color codes as text, they need to be escaped with \033
and not with \e
, like so:
echo -e '33[0;32m'Green text'33[0m'
That should work in Linux systems also (as far as I know, drop a comment if you have more knowledge on this).
To make it more humane, the colors can be turned into functions:
green() {
echo -e '33[32m'$1'33[m'
}
red() {
echo -e '33[31m'$1'33[m'
}
# Usage
red "This is some red text"
The colors and text styling can be set to variables. Here’s a whole set of them:
RCol='33[0m' # Text Reset
# Regular Bold Underline High Intensity BoldHigh Intens Background High Intensity BGs
Bla='33[0;30m'; BBla='33[1;30m'; UBla='33[4;30m'; IBla='33[0;90m'; BIBla='33[1;90m'; On_Bla='33[40m'; On_IBla='33[0;100m';
Red='33[0;31m'; BRed='33[1;31m'; URed='33[4;31m'; IRed='33[0;91m'; BIRed='33[1;91m'; On_Red='33[41m'; On_IRed='33[0;101m';
Gre='33[0;32m'; BGre='33[1;32m'; UGre='33[4;32m'; IGre='33[0;92m'; BIGre='33[1;92m'; On_Gre='33[42m'; On_IGre='33[0;102m';
Yel='33[0;33m'; BYel='33[1;33m'; UYel='33[4;33m'; IYel='33[0;93m'; BIYel='33[1;93m'; On_Yel='33[43m'; On_IYel='33[0;103m';
Blu='33[0;34m'; BBlu='33[1;34m'; UBlu='33[4;34m'; IBlu='33[0;94m'; BIBlu='33[1;94m'; On_Blu='33[44m'; On_IBlu='33[0;104m';
Pur='33[0;35m'; BPur='33[1;35m'; UPur='33[4;35m'; IPur='33[0;95m'; BIPur='33[1;95m'; On_Pur='33[45m'; On_IPur='33[0;105m';
Cya='33[0;36m'; BCya='33[1;36m'; UCya='33[4;36m'; ICya='33[0;96m'; BICya='33[1;96m'; On_Cya='33[46m'; On_ICya='33[0;106m';
Whi='33[0;37m'; BWhi='33[1;37m'; UWhi='33[4;37m'; IWhi='33[0;97m'; BIWhi='33[1;97m'; On_Whi='33[47m'; On_IWhi='33[0;107m';
Possible usage:
error_message() {
echo "$Red Something went wrong. $RCol"
}
And here’s a table of all the if expressions in Bash:
Primary | Meaning |
---|---|
[ -a FILE ] | True if FILE exists. |
[ -b FILE ] | True if FILE exists and is a block-special file. |
[ -c FILE ] | True if FILE exists and is a character-special file. |
[ -d FILE ] | True if FILE exists and is a directory. |
[ -e FILE ] | True if FILE exists. |
[ -f FILE ] | True if FILE exists and is a regular file. |
[ -g FILE ] | True if FILE exists and its SGID bit is set. |
[ -h FILE ] | True if FILE exists and is a symbolic link. |
[ -k FILE ] | True if FILE exists and its sticky bit is set. |
[ -p FILE ] | True if FILE exists and is a named pipe (FIFO). |
[ -r FILE ] | True if FILE exists and is readable. |
[ -s FILE ] | True if FILE exists and has a size greater than zero. |
[ -t FD ] | True if file descriptor FD is open and refers to a terminal. |
[ -u FILE ] | True if FILE exists and its SUID (set user ID) bit is set. |
[ -w FILE ] | True if FILE exists and is writable. |
[ -x FILE ] | True if FILE exists and is executable. |
[ -O FILE ] | True if FILE exists and is owned by the effective user ID. |
[ -G FILE ] | True if FILE exists and is owned by the effective group ID. |
[ -L FILE ] | True if FILE exists and is a symbolic link. |
[ -N FILE ] | True if FILE exists and has been modified since it was last read. |
[ -S FILE ] | True if FILE exists and is a socket. |
[ FILE1 -nt FILE2 ] | True if FILE1 has been changed more recently than FILE2 , or if FILE1 exists and FILE2 does not. |
[ FILE1 -ot FILE2 ] | True if FILE1 is older than FILE2 , or is FILE2 exists and FILE1 does not. |
[ FILE1 -ef FILE2 ] | True if FILE1 and FILE2 refer to the same device and inode numbers. |
[ -o OPTIONNAME ] | True if shell option "OPTIONNAME" is enabled. |
[ -z STRING ] | True of the length if "STRING" is zero. |
[ -n STRING ] or [ STRING ] | True if the length of "STRING" is non-zero. |
[ STRING1 == STRING2 ] | True if the strings are equal. "=" may be used instead of "==" for strict POSIX compliance. |
[ STRING1 != STRING2 ] | True if the strings are not equal. |
[ STRING1 < STRING2 ] | True if "STRING1" sorts before "STRING2" lexicographically in the current locale. |
[ STRING1 > STRING2 ] | True if "STRING1" sorts after "STRING2" lexicographically in the current locale. |
[ ARG1 OP ARG2 ] | "OP" is one of -eq , -ne , -lt , -le , -gt or -ge . These arithmetic binary operators "ARG2", respectively. "ARG1" and"ARG2"are integers. |
Table lifted from here.
This just the very basics.
Comments would go here, but the commenting system isn’t ready yet, sorry.