In this tutorial you will learn the basic syntax of Bash. This is also useful for other UNIX shells.

In the following examples we can use $ at the start to distinguish a command from its output.

Table of Contents

Script structure

  • It’s recommended to put in the first line the “shebang” (#!) followed by the location of the Shell interpreter you want to use.
    #!/bin/bash
    
    • In some systems, bash may be in other path, you can use this shebang for the system to find the right path: #!/usr/bin/env bash.
  • Script files need to have execute permissions (and read permissions as well).
    chmod +x script.sh
    

exit

exit terminate the script. You can use it without parameters (same as exit 0 or not adding exit) or you can add the exit status as a parameter.

  • exit 0: means the program has terminated sucessfully.
  • exit $?: terminate with the exit status of the last command.
  • exit <any positive number in the range [1..255]>: program has terminated with an error (exit status <number>). There are some reserved exit codes (https://tldp.org/LDP/abs/html/exitcodes.html) but you can define your own exit codes.

Variables

x=6
item="some text"
  • Do not add spaces between =.

Reference a variable using $ and its name (or ${varname}). You can type the variable inside double quotes (to prevent word splitting), but if you use single quotes, command will take the variable name as text.

$ item="some text"
$ echo $item
some text
$ echo ${item}
some text
$ echo "$item"
some text
$ echo '$item'
$item

You can echo the output of a command (or do whatever you want with it) by using `COMMAND` or $(COMMAND).

$ echo date
date
$ echo `date`
vie 01 oct 2021 18:45:13 CEST
$ echo $(date)
vie 01 oct 2021 18:45:15 CEST

If you want to output a text with several lines, you can use echo -e, printf or use quotes.

y="One Line\nSecond Line"
printf $y
$ echo "hello
bye"

In some cases, you can also preserve newlines by referencing the variable like this: "${var}":

y=`cat file.csv`
echo "${y}"
echo "$(ls -l)"

Unset (delete) a variable with unset.

unset y

Working with text

  • ${var:start:character_number}: select a substring of ‘var’ of ‘character_number’, starting from index ‘start’.
    $ a="text";echo ${a:0:2}
    te
    
    $ a="text";echo ${a:1}
    ext
    
    $ a="text";echo ${a::1}
    t
    
    $ a="pass";echo ${a:(-1)}
    s
    
  • ${var/pattern/replace}: search ‘pattern’ in ‘var’ and replace with ‘replace’.
    # Replace first ocurrence
    $ a="text";echo ${a/t/r}
    rext
    
    # Replace all ocurrences
    $ a="text";echo ${a//t/r}
    rexr
    
    $ a="text";echo ${a/t}
    ext
    
  • Modifying the case:
    $ a="Hello World";echo ${a,,}
    hello world
    
    $ a="Hello World";echo ${a^^}
    HELLO WORLD
    

Arrays

x=(3 5 9)
y=(some_word, "some sentence")

echo ${x[0]}

x[0]=6

echo ${x[0]}

$ ./script.sh
3
6
  • You can wrap array references with double quotes
    echo "${x[0]}"
    
  • Print all items in an array.
    echo ${x[@]}
    
  • Print the number of items in an array.
    echo ${#x[@]}
    
  • Print array indexes
    echo ${!x[@]}
    

Associative arrays

Arrays which indexes are strings. Always use declare -A to declare an associative array.

declare -A arr
arr[test]=value
arr["some index"]="some value"
echo ${arr[test]}

You can also initialize an associative array in one line:

declare -A arr2=([i1]=v1 [i2]=v2)

Operators

Arithmetic

+ - * /
  • %: modulus (remainder of a division).
  • **: power operator.
  • x++: increase x by one.

Use $ and double parenthesis to do calculations:

$ echo $((5 + 8))
13
$ calc="2*3";echo $(($calc))
6
# time shell has been runing
$ echo "$(($SECONDS / 60)) minutes"
87 minutes

Because using $ with variables in arithmetic operations is optional, you can also do:

echo "$((SECONDS / 60)) minutes"

or

calc="2*3";echo $((calc))

Bolean

  • Strings:
    =
    ==
    !=
    -z #test if a string is null
    -n #test if a string is not null
    
  • Files:
    -e #test if a file exists
    -f #test if a file is a regular file
    -d #test if a file is a directory
    -L #test if a file is a symbolic link
    
  • Numbers (if you use [ ] to enclose the condition):
    • -eq: equal to.
    • -gt: greater than.
    • -ge: greater than or equal to.
    • -lt: less than.
    • -le: less than or equal to.
    • -ne: not equal to.

Conditionals

if, elif, else

Basic structure:

if [ EXPRESSION ]
then COMMAND
else COMMAND
fi
  • EXPRESSION is usually enclosed in brackets [ ], but you can use a command (the first bracket [ is a shortcut for the test command).
    if [ $x -eq 6 ]
    
    if test $x -eq 6
    
  • If you add then in the same line as if, you must use a semicolon.
    if [ $x -eq 6 ];then
    
if [ $x -eq 6 ]
then echo "x is equal to 6"
else echo "x is not equal to 6"
fi

# or
if [ $x -eq 6 ]
then
echo "x is equal to 6"
else
echo "x is not equal to 6"
fi

# or
if (($x == 6))
then
echo "x is equal to 6
else
echo "x is not equal to 6"
fi
if [ $y = "house" ]
then
echo "y is equal to house"
elif [ $y = "office" ]
then
echo "y is equal to office"
else
echo "y is not equal to house or office"
fi

You can also use this structure for simple conditionals:

[ $x -eq 6 ] && echo "x is equal to 6" || echo "x is not equal to 6"
$ [ -e lectura-agua.csv ] && echo "OK" || echo "FALSE"
OK

Use double brackets ([[ ]]) when you need to use the AND (&&) and OR (||) operators inside the condition.

if [[ $x -gt 6 && $x -lt 9 ]]
then
echo "x is greater than 6 but less than 9
fi

Note that you need to add spaces between the operator in all cases.

Curly braces: {}

You can use {} to build an array or a range, to expanse a parameter or to group command outputs.

$ echo {1..5}
1 2 3 4 5
$ touch ABC_{1..20}.txt
$ ls
ABC_10.txt  ABC_12.txt  ABC_14.txt  ABC_16.txt  ABC_18.txt
ABC_1.txt   ABC_2.txt  ABC_4.txt  ABC_6.txt  ABC_8.txt
ABC_11.txt  ABC_13.txt  ABC_15.txt  ABC_17.txt  ABC_19.txt
ABC_20.txt  ABC_3.txt  ABC_5.txt  ABC_7.txt  ABC_9.txt
$ mkdir 202{0..3}-{01..12}
$ ls
2020-01  2020-05  2020-09  2021-01  2021-05  2021-09  2022-01  2022-05  2022-09  2023-01  2023-05  2023-09
2020-02  2020-06  2020-10  2021-02  2021-06  2021-10  2022-02  2022-06  2022-10  2023-02  2023-06  2023-10
2020-03  2020-07  2020-11  2021-03  2021-07  2021-11  2022-03  2022-07  2022-11  2023-03  2023-07  2023-11
2020-04  2020-08  2020-12  2021-04  2021-08  2021-12  2022-04  2022-08  2022-12  2023-04  2023-08  2023-12
# expands to 'mv image.jpeg image.jpg'
$ mv image.{jpeg,jpg}
$ echo {0..6..2}
0 2 4 6

Loops

for

for i in $some_array
do
echo $i
done
for i in {1..30}
do
[ if $i = 15 ]
then break
# this will stop the loop, use 'continue' instead of 'break' to jump to the next loop item
fi
echo $i
done
for i in *.pdf
do
echo $i
done

while

i=5
while [ $i -gt 0 ]
do
echo $i
i=$((i - 1))
done
awk '{print $1, $3}' somefile | while read n1 n2
do echo "N1 is equal to $n1 and N2 is equal to $n2"
done

case

case $x in
3) echo "x is equal to 3";;
6) echo "x is equal to 6";;
*) echo "x is not equal to 3 or 6";;
esac
case $1 in
"") echo "No parameters"
exit 1;;
some_parameter) echo $1;;
*) echo "Parameter is not 'some parameter'
exit 1;;
esac

select

PS3="Select the item: "

select item in "i1" "i2" "i3"
do echo "You've selected $item"
break
done

Functions

Definition

Function definition must precede invocation.

function_name(){
command1
command2
}
# In one line, remember to add a semicolon after each command
function_name() { command1; command2; }

Invocation

function_name

Function arguments

You can pass arguments to functions.

fun(){
  if [ -n $1 ]
  then
    echo $1
  fi
}

fun arg1

To pass an script argument to a function, you must invocate the function with that argument.

# ./script.sh arg1

fun(){
  if [ -z $1 ]
  then
    echo "No argument passed."
  else
    echo $1
  fi
}

fun $1

Bash parameters

  • -n: test the syntax of a script without having to execute it.
    bash -n ./script.sh
    
  • -x: turn on debugging.

Script parameters

./script.sh arg1 arg2

  • You can access these arguments using $ and 1 for the first argument, 2 for the second, etc.
    #!/bin/bash
    if [ -n $1 ]
    then
    first_argument=$1
    echo $first_argument
    fi
    
    $ ./script.sh my_argument
    my_argument
    

Some characters have special meanings (referring to parameters):

  • $#: number of parameters.
  • $@ or $*: a list with the parameters.
  • $0: displays the script path (relative or absolute).

There are several ways to loop between the parameters:

while [ $# -gt 0 ];do
  case $1 in
    -e) echo "Found 'e' parameter";;
    -config) echo "Found 'config' parameter"
    CONFVALUE=$2
    shift #Delete first argument and move the second to first place, third to second place, etc.
    if [ $CONFVALUE -gt 10 ];then
      echo "Error: Config value must be less than 10"
      exit 1
    fi
    ;;
  esac
  shift
done
for arg in $@; do
  case $arg in
    -a) echo "Found 'a' parameter";;
    -b) echo "Found 'b' parameter;;
    *) echo "Unknown parameter;exit 1;;
  esac
done

Pipes / redirections

  • command1 | command2: transform the output from command1 into the input of command2.
    cat /etc/passwd | grep root
    
    • You can specify where to add the output of the first command by using - in the second command.
  • command > file: create (or overwrite) a file with the output from command
    find . -maxdepth 1 > files.txt
    
  • command >> file: append the output from command into file (in a new line). It does not overwrite the file if it exists but it will create it if it does not exist.
  • command < file: use the content of file as input to command.
  • command 2>file: redirect standard error from command to file.
    # Redirect stdout and stderr to same location
    ls /somefolder > file.txt 2>&1
    # this is the same as:
    ls /somefolder &> file.txt
    
    for i in {0..20}; do ls test_$i.txt 1>/dev/null; done
    
    • Show only the files that do not exist in that range (1 refers to standard output, 2 to standard error).
  • command << EOF: this is called a ‘here document’ and allows to pass multiline text to a command. EOF could be any word you can use to define the end of the text.
    $ cat > multiline.txt << EOF
    > one
    > two
    > three
    > EOF
    
  • command <<< text: you can use ‘here strings’ to feed a command with some text.
    awk '{print $2}' <<< "Some text"
    

Wildcards

  • *: matches any number of characters.
    ls *.png
    
  • ?: matches a single character.
    ls 202201??.png
    
  • []: matches any one of the enclosed characters.
    ls 202[23]_report.pdf
    
  • {}: matches a range.
    ls file_{1..20}.txt
    
Test with this online terminal:

If you have any suggestion, feel free to contact me via social media or email.