Python for Network Engineers

Bash scripting 101

by: George El., January 2019, Reading time: 6 minutes

In this post I describe basic features of the bash shell

Variables

Variables by convention use uppercase characters and are case sensitive. There are builtin variables like HOME, USER, PWD and user defined variables. To print a variable you precede it with $ or put it in ${}

$ VAR1=1
$ echo "$VAR1"
1
$ VAR2="Hello"
$ echo "$VAR2"
Hello
$ echo $USER
geo555
$>echo ${USER}
geo555

you may have noticed that I enclose variables in quotes when printing them. This is not mandatory but it is a good practice to follow, double quotes avoid variable expansion

arithmetic operations

When you want to perform arithmetic operations you can either user the (( )) notation or use the expr command

# echo "$((31+21))"
52
# echo $(expr 31 * 21)
651

increase variable by 1

var=$((var+1))
((var=var+1))
((var+=1))
((var++))

Or you can use let:

let "var=var+1"
let "var+=1"
let "var++"

Floating point arithmetic is not supported by default in bash, so you have to have an external program like python, awk, or bc

$>echo  | awk '{print 1 / 2}'
0.5
$>echo '1 / 2' | bc -l
.50000000000000000000
$>echo 'scale=2;1 / 2' | bc -l
.50
bc -l <<< 'scale=2; 1/2'
.50
$>VAR3=`python -c "print(1.0/2.0)"`
$>echo $VAR3
0.5

if you don’t use quotes you have to escape * with \

$>echo "2 * 4" | bc -l
8
$>echo 2 * 4 | bc -l
(standard_in) 1: syntax error
(standard_in) 1: illegal character: R
(standard_in) 1: illegal character: R
$>echo 2 \* 4 | bc -l
8

store outcome to variable using command substitution

to assign it to a variable you have to use command substitution. There are two ways. Either use backticks `` or $()

$>VAR2=`echo "1 / 2" | bc -l`
$>echo $VAR2
.50000000000000000000
$>VAR2=$(echo "1 / 2" | bc -l)
$>echo $VAR2
.50000000000000000000
$>echo 'scale=2;1 / 2' | bc -l
.50

this is not limited to numbers, you can also do

FILES=`ls`

comparing numbers

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal

some examples

$>[ 1 -eq 1 ]
$>echo $?
0
$>[ 15 -gt 1 ]
$>echo $?
0
$>[ -5 -gt 1 ]
$>echo $?
1

the $? is the exit code. 0 means true or success, 1 means false. you can also use (( )) for integer comparisons

$>(( 1 == 1 ))
$>echo $?
0
$>(( 1 > 5))
$>echo $?
1
$>(( 10 > 5))
$>echo $?
0
$>(( 10 != 5))
$>echo $?
0
$>(( 10 >= 5))
$>echo $?
0

of course you can use variables instead of real numbers.

[ "$a" -eq "$b" ]

you can combine multiple comaparisons using && and ||

(( "$num1"<1 )) && (( $num2 == 1 || $num2 == 2 ))

String comparison

- string1 = string2
- string1 == string2
    Use the = operator with the test [ command.
    Use the == operator with the [[ command for pattern matching.
- string1 != string2 
- string1 =~ regex
- string1 > string2
- string1 < string2
- -z string : True if the string length is zero.
- -n string : True if the string length is non-zero.

if statements

num1=20
if [ "$num1" -gt 10 ]
then
  echo "num1 is greater than 10"
elif [ "$num1" -gt 0 ]
then 
  echo "$num1 is greater than 0"
else
  echo "$num1 is not greater than 0"
fi

the output is

20 is greater than 10

For loop

$>for num in {1..5}
> do
>  echo "$num"
> done

We used brace expansion here, which will look later. The output is

1
2
3
4
5

you can also use c cyntax loop

$>END=5
$>for ((i=1;i<=END;i++)); do
>     echo $i
> done

the output is

1
2
3
4
5

another way is to use seq

$>for i in $(seq 1 $END); do echo $i; done
1
2
3
4
5

Until loop

$>until [ $NUM -gt 5 ]
> do
>   echo $NUM
>   ((NUM++))
> done

in one line is

until [ $NUM -gt 5 ]; do    echo $NUM;   ((NUM++)); done

output

0
1
2
3
4
5

While loop

NUM=0
$>while [ $NUM -le 5 ]
> do
>   echo $NUM
>   ((NUM++))
> done

or in one line

$>NUM=0
$>while [ $NUM -le 5 ]; do   echo $NUM;   ((NUM++)); done
0
1
2
3
4
5

CASE Stamement

general syntax

case  $variable-name  in
    pattern1|pattern2|pattern3)       
    command1
        ...
        ....
        commandN
        ;;
    pattern4|pattern5|pattern6)
    command1
        ...
        ....
        commandN
        ;;            
    pattern7|pattern8|patternN)       
    command1
        ...
        ....
        commandN
        ;;
    *)              
esac 

case is usually used in menus

$>INPUT=y
$>case $INPUT in
>   y|Y)
>   echo "you pressed yes"
>   ;;
>   n|N|No)
>   echo "you pressed No"
>   ;;
>   exit)
>   echo "you pressed exit"
>   ;;
> esac
you pressed yes

simple menu with case and while

while true
do
    clear
    echo "enter 1 to show date"
    echo "enter 2 to show uptime"
    echo "enter q to exit"
    read num
    case "$num" in
        1)
        date
        ;;
        2)
        uptime
        ;;
        q)
        exit 0
        ;;
    esac
    echo -n "enter to continue"
    read input
done

we haven’t seen read before, but it just stores a values from stdin to variable num. We put the case statement inside a while loop in order to appear the menu back to the user, after the execution of a command. the last read is just for the user to see the outcome of the command and serves no other purpose.

Script arguments

When you write a bash script it is most likely that you want to pass it arguments. Arguments are stores in special variables:

  • $0 is the name of script
  • $1 is the first argument
  • $2 second argument
  • $N is the nth argument
  • $@ is all arguments
  • $# is number of arguments

Lets write a script to loop through all arguments and print them

#!/bin/bash

for arg in "$@"
do
  echo "$arg"
done
echo "number of arguments is $#"

the outcome with some arguments is

$>./loopargument.sh 1 2 3 4 5
1
2
3
4
5
number of arguments is 5
$>./loopargument.sh hi hello there
hi
hello
there
number of arguments is 3

Loop through files

Lets say I have the following files and I want to create a backup of each file

file1.txt
file2.txt
file3.txt
file4.txt
file5.txt
#!/bin/bash

for file in *.txt
do
  cp "$file" "$file".bak
done

after the execution of the script

file1.txt
file1.txt.bak
file2.txt
file2.txt.bak
file3.txt
file3.txt.bak
file4.txt
file4.txt.bak
file5.txt
file5.txt.bak

you could also do the same using xargs, or find command. See my other posts on how to do this.

file conditions

you can use the following tests in your scripts

[ -e FILE ] 	Exists
[ -r FILE ] 	is Readable
[ -h FILE ] 	is Symlink
[ -d FILE ] 	is Directory
[ -w FILE ] 	is Writable
[ -s FILE ] 	Size is > 0 bytes
[ -f FILE ] 	is File
[ -x FILE ] 	is Executable

We will use the a for loop to loop through all files in the current directory and print if it is a file or directory

#!/bin/bash

for file in *
do
  if [ -f "$file" ]; then
    echo "$file is file"
  elif [ -d "$file" ]; then
    echo "$file is directory"
  fi
done

the output is

dira is directory
dirb is directory
dirc is directory
file10.txt is file
file10.txt.bak is file
file1.txt is file
file1.txt.bak is file
file2.txt is file
file2.txt.bak is file
file3.txt is file
file3.txt.bak is file
file4.txt is file
file4.txt.bak is file
file5.txt is file
file5.txt.bak is file
comments powered by Disqus