Test[edit | edit source]

test is a bash built-in function to add conditionals in your script. [ is an alias of the test command and are analogous.

Use the test operators listed below with test or a single bracket test condition like the following example.

$ test -e /etc/hosts && echo "File exists"
File exists

## Note that the white space around [ and ] and also the operator are required.
$ [ -e /etc/hosts ] && echo "File exists"
File exists

$ if [ -e /etc/hosts ] ; then echo "File exists" ; fi
File exists
Operator Description
-e file exists
-a file exists (deprecated)

This is identical in effect to -e

-f file is a regular file (not a directory or device file)
-s file is not zero size
-d file is a directory
-b file is a block device. Eg. /dev/sda.
-c file is a character device. Eg. /dev/tty0.
-p file is a pipe. Eg. /dev/fd/0.
-h file is a symbolic link
-L file is a symbolic link
-S file is a socket
-t file (descriptor) is associated with a terminal device

This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal.

-r file has read permission (for the user running the test)
-w file has write permission (for the user running the test)
-x file has execute permission (for the user running the test)
-g file or directory has set-group-id (sgid) flag set

If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory.

-u file has set-user-id (suid) flag set
-k file or directory has sticky bit set
  • If set on a file, 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.
-O you are owner of file
-G group-id of file same as yours
-N file modified since it was last read
f1 -nt f2 file f1 is newer than f2
f1 -ot f2 file f1 is older than f2
f1 -ef f2 files f1 and f2 are hard links to the same file
! "not" -- reverses the sense of the tests above (returns true if condition absent).

Integer[edit | edit source]

Integer tests, with test or inside single brackets.


$ test 2 -eq 2 && echo equal

Operator Description
-eq is equal to
-ne is not equal to
-gt is greater than
-ge is greater than or equal to
-lt is less than
-le is less than or equal to

Integer tests can aso be done inside double parentheses. Eg.

$ ((2==2)) && echo equal

Operator Description
== is equal
< is less than
<= is less than or equal to
> is greater than
>= is greater than or equal to

Strings[edit | edit source]

Operator Description
-z string is null, that is, has zero length
-n string is not null.
= is equal to
## Does not work in zsh. Works only in sh and bash.
$ if [ "$a" = "$b" ] ; then
  echo "Equal"
== is equal to
## Does not work in zsh. Works only in sh and bash.
$ if [ "$a" == "$b" ] ; then
  echo "Equal"

This operator inside a double bracket [[ ]] construct does pattern matching.

!= is not equal to
$ if [ "$a" != "$b" ] ; then
  echo "Not equal"

This operator inside a double bracket [[ ]] construct does pattern matching.

=~ Match using regex
$ if [ zone36-ta =~ .*ta$ ] ; then
  echo "matches"

$ if [ zone36-ta =~ *ta ] ; then
  echo "matches"

Useful Functions / Utilities[edit | edit source]

Here are some common functions I use in my scripts.

Prompt[edit | edit source]

You can prompt the user for an yes or no answer using this function. An optional default values are accepted as the second argument.

Example Usage:

if prompt "Is this okay?" Y ; then
    echo "It's Okay!"
 if ! prompt "Is this okay?" N ; then
    echo "It's not okay!"

Source: <phorkie>https://phorkie.leo.home.steamr.com/13/embed</phorkie>

max[edit | edit source]

Find the maximum of two numbers:

max (){
        echo $((A>B ? A : B))

die[edit | edit source]

Similar to the die command in PHP which prints out a message before stopping execution.


function die {
	echo $@
Caveat when using in a subshell
This will not work inside a subshell as it will just quit the subshell rather than the script. To get around this, you may need to kill the parent PID instead.

Indent[edit | edit source]

Indents text piped to the function.

# Supports up to 10 tabs
function Indent (){
	sed "s/^/${Tabs:0:$((2*$1))}/g"

echo "test" | Indent 1
echo "test" | Indent 2

Extracting / Parsing Filenames[edit | edit source]

Use the basename to extract filenames from a path and and dirname to extract the path of a filename.


$ dirname /etc/hosts
$ basename /etc/hosts

To extract only the extension or the filename without the extension, use bash's string matching or basename:

Filename=$(basename /etc/resolv.conf)
Name=`basename $Filename .conf`

Skipping N Lines[edit | edit source]

Use tail to skip the first N lines:

## -n +N outputs starting on line N.
## Eg. To skip the first 2 lines, we start on line 3:
# tail -n+3

Note that the two are equivalent, because printing from line 0 will also print starting on line 1.

# tail -n+0  
# tail -n+1

File Modification[edit | edit source]

Use stat to find when a file was access/modified. You an obtain the timestamp as a Unix timestamp by using the -c %Y parameter.

A Unix timestamp can be converted into a human readable timestamp using awk:

$ echo 1355514777 | awk '{ print strftime("%c", $1) }'
Fri 14 Dec 2012 12:52:57 PM MST

Get Unix Timestamp[edit | edit source]

$ date +"%s"

Convert Unix Timestamp to Readable Timestamp[edit | edit source]

Inversely, you can pass in the timestamp to date and print a formatted timestamp.

$ date +"%c" -d @15996137957
Thu 30 Jul 2020 01:39:17 PM MDT

Get a specific column[edit | edit source]

You can use either awk with delimiters specified with -F.

$ echo one two three four five | awk '{print $3}'

$ echo one,two,three,four,five | awk -F, '{print $3}'

Or cut by a specific delimiter and select a specific column. This method cannot handle columns separated by more than one space. If you have neat columns, you'll need to pipe through tr -s ' ' first which collapses (or squeezes) the given repeating characters down.

$ echo one two three four five | cut -d ' ' -f3
$ echo "one    two    three    four     five" | tr -s ' ' | cut -d ' ' -f3

$ echo one,two,three,four,five | cut -d ',' -f3

Or use the bash built-in function read which can take in a list of variables. The last variable contains all remaining columns as well. Unwanted columns can be named repeatedly with _ (like in golang) and ignored.

## Remember, to get only the 3rd column, we need to read the 4th column out.
$ echo one two three four five | read _ _ third; echo $third
three four five
$ echo one two three four five | read _ _ third _ ; echo $third

## Delimiters specified with IFS
$ echo one,two,three,four,five | IFS=',' read _ _ third _ ; echo $third

In terms of speed, be aware that using awk or tr+cut requires a sub-process which may slow down performance. If you are going to do a while read line statement and then parsing out columns within the loop, it might benefit you to parse the columns out by replacing while read line with while read column1 column2 .... For example:

## Super slow! Invokes awk 2 times per line
$ w | tail -n +3 | while read i ; do
  Username=$(echo "$i" | awk '{print $1}')
  Source=$(echo "$i" | awk '{print $3}')
  echo $Username $Source

## Instead do something like this!
$ w | tail -n +3 | while read Username _ Source _ ; do
  echo $Username $Source

Preserve header while sorting without using a temporary file[edit | edit source]

You can sort an output on a specific column while preserving the header and without needing a temporary file by using a shim function that reads and prints the first line before passing the rest of the output down the pipe. Define this function:

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"

Then use this shim body function with the desired sort function. For example:

$ top -b -n 1 | head -n 15 | tail -n +7 \
  | body sort -k 5 -rn

Credit to https://unix.stackexchange.com/questions/11856/sort-but-keep-header-line-at-the-top for this neat trick.

Command Substitution[edit | edit source]

Command substitution allows output from a command to be substituted in place of the command itself in a script. Commands can be enclosed within backticks `command` or the POSIX compatible form $(command).

## Eg.
echo "Today is " $(date)
echo "Today is " `date`

Do not confuse $(command) with $((expression)) which is used for arithmetic expansion.

Nested Substitution[edit | edit source]

If you are trying to do something similar to:

Something=`basename `ls /home/*.txt``

You will get an error since the expansion is ambiguous. Instead, use the $(command) substitution instead:

Something=$(basename $(ls /home/*.txt))

Arithmetic Operations[edit | edit source]

Do integer arithmetic operations on variables within double brackets. For example:

ValuePlusOne=$((Value + 1))
ValueTimes12=$((Value * 12))

The operators that are supported are:

+ - / * % 

Bitwise operators also work:

^ (XOR)   | (OR)   & (AND)   << (Shift left)   >> (Shift right)

Control Blocks[edit | edit source]

Switch[edit | edit source]

read -p "What do you want to do?: " Option

case "$Option" in
		die "Complete!"
		die "Stopping installation."

Snippets[edit | edit source]

Get First IP Address[edit | edit source]

# ip a | /bin/grep inet | grep -v inet6 | tail -n+2 | head -n 1 | awk '{print $2}' | cut -d'/' -f1

Run Script within Script Directory[edit | edit source]

For scripts that need to be running on the same directory as the script location, add this line to the top of the script.

cd "$(dirname "$0")"

See Also[edit | edit source]