This is a reference for bash scripting. For more general instructions take a look here.
Bash operators are mainly used in if statements, to check against a variable or a hard coded object.
Example:
#!/bin/bash
name=Foo
if [ -e $name ]
then
echo File Exist
else
echo File doesnot exist
fi
| Operator | Checks for | Returns |
|---|---|---|
(-a) |
File exits DEPRECATED | True if it exits, otherwise false |
-b |
File is a block device | True if the file is a block device, otherwise false |
-c |
File is a character device | True if it is a character device, otherwise false |
-d |
File is a directory | True if it is a directory, otherwise false |
-e |
File exits | True if it exits, otherwise false |
-ef |
Files are hard links to the same file | True if files before and after operator are hard links to the same file, otherwise false |
-f |
File is regular file | True if regular, false if directory, device file, or non existant |
-g |
Set-group-id (sgid) flag set on directory[1] | True if flag is set, otherwise false |
-G |
Group-id of file is equal to the current user's | True group-id is equal to the user's, otherwise false |
-h |
File is a symbolic link | True if file is a symbolic link, otherwise false |
-k |
Sticky bit set[2] | True if bit is set, otherwise false |
-L |
File is a symbolic link | True if file is a symbolic link, otherwise false |
-N |
File is modified since last read | True file is modified since last read, otherwise false |
-nt |
File is newer than other file | True if file preceding operator is newer than file behind the operator, otherwise false |
-ot |
File is older than other file | True if file preceding operator is older than file behind the operator, otherwise false |
-O |
Current user is owner of file | True if user is owner, otherwise false |
-p |
File is a pipe | True if file is a pipe, otherwise false |
-r |
File has read permission (for the user running the test) | True if it has read access, otherwise false |
-s |
File Size | True if file > 0, otherwise false |
-S |
File is a socket | True if file is a socket, otherwise false |
-t |
File (descriptor) is associated with a terminal device[3] | True if file is associated with a terminal device, otherwise false |
-u |
Set-user-id (suid) flag set on file[4] | True if flag is set, otherwise false |
-w |
File has write permission (for the user running the test) | True if it has write access, otherwise false |
-x |
File has execute permission (for the user running the test) | True if it has execute access, otherwise false |
! |
NOT, reverses the tests above | True if condition absent, otherwise reverse of condition |
| Operator | C-style variant | Meaning | Usage | Usage C-Style |
|---|---|---|---|---|
-eq |
none | Is equal to | if [ "$a" -eq "$b" ] |
- |
-ne |
none | Is not equal to | if [ "$a" -ne "$b" ] |
- |
-gt |
> |
Is greater than | if [ "$a" -gt "$b" ] |
(("$a" > "$b")) |
-ge |
>= |
Is greater or equal to | if [ "$a" -ge "$b" ] |
(("$a" >= "$b")) |
-lt |
< |
Is less than | if [ "$a" -lt "$b" ] |
(("$a" < "$b")) |
-le |
<= |
Is less than or equal to | if [ "$a" -le "$b" ] |
(("$a" <= "$b")) |
| Operator | Meaning | Usage |
|---|---|---|
= |
Is equal to | if [ "$a" = "$b" ] |
== |
Is equal to[5] | if [ "$a" == "$b" ] |
!= |
Is not equal to | if [ "$a" != "$b" ] |
=~ |
Check string against extended regex | [[ $a =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]] |
< |
Is less than, in ASCII alphabetical order | if [ "$a" \< "$b" ] or if [[ "$a" < "$b" ]] |
> |
Is greater than, in ASCII alphabetical order | if [ "$a" \> "$b" ] or if [[ "$a" > "$b" ]] |
-z |
string is null(zero length) | if [ -z "$String" ] |
-n |
string is not null | if [ -n "$String" ] (always use quotes) |
| Operator | Name | Funtion |
|---|---|---|
+ |
Plus | 3 + 6 = 6 |
- |
Minus | 6 - 3 = 3 |
* |
Multiply | 3 * 3 = 9 |
/ |
Divide | 9 / 3 = 3 |
** |
Exponent | 3 ** 2 = 9 |
% |
Modulo | 24 % 4 = 4 |
Each arithmetic operator can be followed with an equals sign(=), which writes the output of the operation into the first variable. An example:
#!/bin/bash
a=2
$((a += 1))
echo $a
# returns: 3
| Operator | Name | Funtion |
|---|---|---|
&& |
AND | "true" && "false" = false |
|| |
OR | "true" || "false" = true |
! |
NOT | ! "true" = false |
Usage:
#!/bin/bash
if [ $condition1 ] && [ $condition2 ]; ...
if [[ $condition1 && $condition2 ]]; ...
if [ ! -f $FILENAME ]; ...
| Operator | Name | Funtion |
|---|---|---|
& |
AND | 1010 & 1100 = 1000 |
| |
OR | 1010 \| 1100 = 1110 |
^ |
XOR | 1010 ^ 1100 = 0110 |
~ |
NOT/Complement | ~1010 = 0101 |
<< |
Left Shift | 1100 << 1 = 1000 |
>> |
Right Shift | 1100 >> 1 = 0110 |
Bitwise operators are usually not used in if statements, but for number manipulation. An example:
#!/bin/bash
a=$((2#1010))
b=12
bitwiseAND=$(( a&b ))
echo Bitwise AND of a and b is $bitwiseAND
# returns: Bitwise AND of a and b is 8
Each bitwise operator can be followed with an equals sign(=), which writes the output of the operation into the first variable. An example:
#!/bin/bash
a=2
$((a <<= 1))
echo $a
# returns: 4
A function in bash can be defined in 2 ways:
function_name () {
<commands>
}
or
function function_name () {
<commands>
}
The () are there just for decoration, not to define arguments. Don't put anything between these. Arguments can be accessed using $1 etc. Arguments passed from the commandline can't be accessed directly from within a function. You should pass these when calling the function, or expose them in a global variable.
Calling functions is done by calling [result = ]function_name 'argument1' 'argument2'. Don't use parentheses for passing arguments.
(new in Bash 4.0)
| Operator | Funtion |
|---|---|
^^ |
To uppercase |
^ |
First (matched) character to uppercase |
,, |
To lowercase |
, |
First (matched) character to lowercase |
Usage:
var='Text'
echo "${var^^}"
> TEXT
This can be used with a pattern like this: ${var^pattern}. In which case only matched characters are transformed.
| Operator | Funtion |
|---|---|
U |
To uppercase (new in Bash 5.1) |
u |
First character to uppercase (new in Bash 5.1) |
L |
To lowercase (new in Bash 5.1) |
Q |
Quote/escape |
E |
With backslash escape sequences expanded as with the $'…' quoting mechanism |
P |
Expand as if it were a prompt string |
A |
As assignment statement |
K |
Display associative arrays as key-value pairs (new in Bash 5.1) |
a |
Show parameters' attributes flags parameters can have flags! |
Usage:
var='Text'
echo "${var@U}"
> TEXT
Iterate over all passed files
#!/bin/bash
for FILE1 in "$@"
do
wc $FILE1
done
Iterate over all lines in variable
while IFS= read -r line; do
echo "... $line ..."
done <<< "$list"
Loop until condition is true
while [ $i -lt 4 ]
do
echo hi &
i=$[$i+1]
done
Positional variables can be used within functions or scripts, enumerating arguments passed to them.
function positional_variables(){
echo "Positional variable 1: ${1}"
echo "Positional variable 2: ${2}"
}
$ positional_variables "one" "two"
Positional variable 1: one
Positional variable 2: two
Using ${@} or ${@} all positional arguments can be returned, as iterable or as expanded array. The difference is that ${@} expands the variables like this ${1} ${2}.. and ${@} uses ${1}c${2}.. where c is the first character set in IFS.
Sometimes you need the directory in which the script itself is located, for example if you need to copy/make files in that directory. There are a lot of ways to get this directory, however, not all methods still work when the script is called via a symlink. The following line should work in most cases:
script_path="$(readlink -f "${BASH_SOURCE[0]}")"
script_dir="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
A test to check this method can be found here.
With executing commands, I mean running a program/command from within a bash script. For example:
#!/bin/bash
touch "testfile"
When a command should not display any output to the commandline, it is possible to pipe both STDOUT and STRERR to /dev/null
echo "this won't be shown" >/dev/null 2>&1
Useful pieces of code
#! /bin/bash
set -Eeuxo pipefail
When using set -u, the processing of undefined commandline arguments might throw an error like this:
./script.sh: line 24: $1: unbound variable
To work around this error, you can use parameter substitution to replace an undefined parameter with an empty string like this:
[ -z "${1-}" ] || [ "$1" == "help" ] && help
The following will echo true when a file called test.* exists
compgen -G "test.*" && echo true
#!/bin/bash
read -p "Do you wish to be evil? (Y/n) " use_systemd
[ -z "$use_systemd" ] && use_systemd='yes' # if no input, assume yes
case ${use_systemd:0:1} in
y|Y|1 ) # Yes
echo "Try https://www.google.com";;
* ) # No (anything else)
echo "Better go to https://duckduckgo.com";;
esac
#!/bin/bash
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
#!/bin/bash
function help () {
echo "Usage: $0 [OPTIONS] <file>"
echo "Description of what this command does"
echo " -a what option a does"
echo " -b what option b does"
echo " -c what input c does"
echo " -h display this output"
exit 1
}
while getopts ':abc:h' opt ; do
case "$opt" in
a) a_var='true';;
b) b_var='true';;
c) c_var="${OPTARG}";;
h) help ;;
:)
echo "$0: Must supply an argument to -$OPTARG." >&2
exit 1
;;
?)
echo "Invalid option: -${OPTARG}."
exit 2
;;
esac
done
file=${@:$OPTIND:1}
output_file=${@:($OPTIND +1):1}
${@:$OPTIND:1} gets the first argument after the options parsed by getopts${@:($OPTIND +1):1} gets the second, ${@:($OPTIND +2):1} the third etc.This snippet demonstrates both how a lockfile can be used, as well as how to trap any errors, so the lockfile can be removed.
As long as the trap uses exit without an exit code, the exit code that was trapped will be returned.
#!/bin/bash
DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
LOCKFILE="$DIR/cronjob.lock"
set -e
# Get lockfile
counter=0
while [ -f "$LOCKFILE" ]; do
sleep 1
if [ "$counter" -ge 5 ]; then
echo "Could not get hold of lockfile, aborting."
exit 1
fi
counter=$((counter+1))
done
trap "{ echo Bye!; rm -f "$LOCKFILE"; exit; }" EXIT
echo $$ > "$LOCKFILE"
# Do something that can't be multithreaded
exit 0
#!/bin/bash
cat normalfile.txt | sed 's/$'"/`echo \\\r`/" > inferiorfile.txt
while IFS= read -r -d '' file; do echo "$file"; done < <(find . -name '*PATTERN*' -print0)
# And rename each file
while IFS= read -r -d '' file; do mv -v "$file" "$(echo "$file" | sed 's/.txt/.conf/')"; done < <(find . -name '*PATTERN*' -print0)
If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory, not necessarily to the group of the user who created the file. This may be useful for a directory shared by a workgroup. ↩︎
Commonly known as the sticky bit, the save-text-mode flag is a special type of file permission. If a file has this flag set, that file will be kept in cache memory, for quicker access. If set on a directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or directory listing. This restricts altering or deleting specific files in that directory to the owner of those files. ↩︎
This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal. ↩︎
A binary owned by root with set-user-id flag set runs with root privileges, even when an ordinary user invokes it. ↩︎
The == comparison operator behaves differently within a double-brackets test than within single brackets. See this documentation. ↩︎
Apparently && and || "short-circuit" while -a and -o do not. See the bottom of this page ↩︎ ↩︎