MorkaLork Development

Interesting stuff I've picked up over the years...

Shell scripting

2010-01-31 15:28:21 | 561 views | linux shell script scripting

Index:






What is a shell script?


A shell script is a script designed to run in the shell. It is often a collection of commands that you use often enough which you want to shorten down to one command.
One very simple example is to shorten down a piped command into just a few letters, let's say ps aux | less. We can shorten this down to a command we'll call lpsaux like this:


#!/bin/bash
ps aux | less


If you were to save this in a text file and name it lpsaux and set the permission to executable you could just run the command ./lpsaux in your terminal and it would do it for you. This is however just an example, we'll make better ones as the article goes (and explain the peculiar #!/bin/bash header.


Note: Before moving ahead, I implore you to read the article about environment variables as we will use them in this tutorial.

Back to top



Shell script rules


There are of course rules when you write a shell script, and it's probably best if we look through the rules of writing a shell script before we really start.

The interpreter


To begin with, you have to explicitly declare in what way/environment the script is to be run. This tutorial will work mainly with the Bourne Again Shell (bash) which is why we'll always start by declaring that our scripts should be run in bash:

#!/bin/bash

Other ways of running the script is in zsh, sh or a text editor (like vi). If you want to know where the bash interpreter is located (it might not be in /bin/bash) you can run the command which bash. On my laptop this would output the following:


maffelu@maffelu-laptop:~$ which bash
/bin/bash


Normally comments are written with a pound sign (#) as a prefix. However, the first line is an exception and is not read by the interpretor as a comment.
If you however want to comment something in the rest of the script, use the # sign ahead of the comment, like this:


#!/bin/bash

#This is a comment
#This is another comment, sneaky...



String handling


Variables can be used in shell scripting, and are declared as follows:


a=Hello


a now has the value Hello in it. Notice that we do not use blank space when we declare. This is because blank space causes the interpreter to view the first word as a command, ie:


a = Hello


This would cause the interpreter to view 'a' as a command try to run it. Alway skip the white space when declaring variables.

When you declare string values you might be used to adding quotes around them. In shell scripting this is only required if there is white space in the value:


a=Hello World


This would consider World an argument. Here, we need to add quotes:


a="Hello World"


A little example using both custom and environment variables:


#!/bin/bash

message="Hello World!"

echo "$USER says $message"


As we can see above, which we remember from the environment variables article, we have to use the dollar sign in front of a variable.

We can also see that even though we output a string (echo followed by a quoted value), the variables are outputted. This is because in double quotation variables are expanded into their content meaning that "$USER says $message" turns into "maffelu says Hello World" (in this cause, since my USER variable holds maffelu). This is, however, not the cause with single quotes (if you have programmed PHP this comes as no surprise). This can be good if you want to output a variable name with its content:


#!/bin/bash

echo -n '$PATH contains ' # -n = no break line
echo $PATH


Of course, you can always escape the dollar sign (like "\$PATH") within quotes if you want that, but using single quotes you know exactly what you are doing.


Variables in string issues


Another potential problem when having variables inside of a string is if you want the contents of the variable to be outputted directly with the following word. This will work if the following word is a minus sign (-), but other wise you will have some trouble:


#!/bin/bash

action_type="Knife"

echo "Say hello to $action_type-man"
echo "Say hello to $action_typeman"

#Output:
#Say hello to Knife-man
#Say hello to


On line 5 we get the correct output as the - sign cannot be used in a variable name and thus the interpreter accepts that this is the end of the variable $action_type. However, in the next line, line 6, the interpreter sees $action_typeman as one variable, which has not contents. The solution to this is to encapsulate the variable with braces, { and }:


#!/bin/bash

action_type="Knife"

echo "Say hello to $action_type-man"
echo "Say hello to ${action_type}man"

#Output:
#Say hello to Knife-man
#Say hello to


This makes sure that the interpreted identifies what is the variable name and what is considered string data and lets it differentiate between the two.


Output without quotes


If you want to use variables to store the commands you can just output the variable directly:


#!/bin/bash

com="ls"
arg="-l"

$com $arg


The above script would run the command 'ls -l' in your terminal


Global and local variables


Variables can exist both 'globally' and 'locally' in a script meaning that if you declare a local variable inside a function it will only exist inside that function and will not have any effect on variables outside that function:


#!/bin/bash

myVar="Hello World!"

function changeVar {
local myVar="Goodbye World!"
echo $myVar
}

echo $myVar
changeVar
echo $myVar

#Output:
#Hello World!
#Goodbye World!
#Hello World!


Back to top



How to create a script and execute it


Allright, we've gone through some theory, now let's create a script that we can execute.
First, open a text editor and enter the following:


#!/bin/bash

echo "This computer belongs to $USER"
echo "The home directory is $HOME"
echo "Thank you for visiting!"


Now save this file as myScript.sh (the .sh extension doesn't really matter but is good to have as an indicator). After you've saved it set the execute permission to it (chmod x myScript.sh).
To run it enter the following command and you'll get an output similar to this:


maffelu@maffelu-laptop:~/shellScript$ sh myScript.sh
This computer belongs to maffelu
The home directory is /home/maffelu
Thank you for visiting!


This is how you write a simple script. If you want more, keep on reading.

Back to top



Using conditions


If you want to check a value in a script or perhaps make sure a certain attribute is set it is useful to have an if-then-fi statement in the script. The syntax for such a condition is:

if condition
else
statement
fi

or

if condition
statement
elif condition
statement
else
statement
fi

You can use this to evaluate data like this:


#!/bin/bash

X=1
Y=2

if [ $X -gt $Y ]; then
echo "$X is larger than $Y"
else
echo "$Y is larger than $X"
fi

#Output:
#2 is larger than 1


Things to notice here. We use the 'test' command which can be used in two ways:


test $X -gt $Y;
#or
[ $X -gt $Y ];


To things to notice here: the operators we use are quite different from what you might be used to seeing and when we use the brackets we must have spaces between the brackets and the statement!.
This tutorial will use the bracket way, if you want to know more run the command 'man test' in your terminal.

The operators are:

Operator Description Operands
(expr) Check if an expression is true 1
! expr Check if an expression is false 1
expr1 -a expr2 Check if both expressions are true 2
expr1 -o expr2 Check if either expression1 or expression 2 are true 2
-n STRING Check if the length of the string is nonzero (more than 0) 1
-z STRING Check if the length of the string is zero (0) 1
STR1 = STR2 Check if strings are equal 2
STR1 != STR2 Check if strings are not equal 2
INT1 -eq INT2 Check if integers are equal 2
INT1 -ge INT2 Check if INTEGER1 is greater than or equal to INTEGER2 2
INT1 -gt INT2 Check if INTEGER1 is greater than INTEGER2 2
INT1 -le INT2 Check if INTEGER1 is less than or equal to INTEGER2 2
INT1 -lt INT2 Check if INTEGER1 is less than INTEGER2 2
INT1 -ne INT2 Check if INTEGER1 is not equal to INTEGER2 2
FILE1 -ef FILE2 Check if FILE1 and FILE2 have the same device and inode numbers 2
FILE1 -nt FILE2 Check if FILE1 is newer than FILE2 (check on modification date) 2
FILE1 -ot FILE2 Check if FILE1 is older than FILE2 2
-b FILE Check if FILE exists and is block special 1
-c FILE Check if FILE exists and is character special 1
-d FILE Check if FILE exists and is a directory 1
-e FILE Check if FILE exists 1
-f FILE Check if FILE exists and is a regular file 1
-g FILE Check if FILE exists and is set-group-ID 1
-G FILE Check if FILE exists and is owned by the effective group ID 1
-h FILE Check if FILE exists and is a symbolic link (same as -L) 1
-k FILE Check if FILE exists and has its sticky bit set 1
-L FILE Check if FILE exists and is a symbolic link (same as -h) 1
-O FILE Check if FILE exists and is owned by the effective user ID 1
-P FILE Check if a FILE exists and is a named pipe 1
-r FILE Check if FILE exists and read permission is granted 1
-s FILE Check if FILE exists and has a size greater than zero (0) 1
-S FILE Check if a FILE exists and is a socket 1
-t FD True if FD is opened on a terminal. If FD is omitted, it defaults to 1 (standard output) 1
-u FILE Check if FILE exists and its set-user-ID bit is set 1
-w FILE Check if FILE exists and write permission is granted 1
-x FILE Check if a FILE exists and execute (or search) permission is granted 1


Let's make a script that creates a backup of a directory, but before so, checks if the directory exists:


#!/bin/bash

dir=$HOME/shellScript/foo

if [ -d $dir ]; then
tar -czf secretBackup.tar.gz $dir
else
echo "Directory $dir was not found, no backup was made"
fi


If there is a directory called foo in your homedirectory /shellScript then you will now have a tar file called secretBackup.tar.gz.

Back to top



Looping


As is standard, bash scripts support looping. There are two kinds of loops:




For loop



#!/bin/bash

for X in val1 val2 val3 do
echo $X
done

#Output:
#val1
#val2
#val3


Now, we can use this to loop through files in one or several directories to check files. What if we have a couple of html files that we want to go through. We want to find all image references, so we use a loop:

#!/bin/bash

for X in html/*.htm
do
grep 'img' $X
done

#Possible output:
#<img src="somePic.png" />
#<img src="someOtherPic.png" />


While loop



#!/bin/bash

X=0
while [ $X -le 5 ]; do
echo "Number: $X"
X=$((X 1)) #Remember that arithmetics has to be done in double parentheses
done

#Output
#Number: 1
#Number: 2
#Number: 3
#Number: 4
#Number: 5


Back to top



Handling arguments


If your shell script is getting a bit complex it might be time to let the user customize it a bit when using it. Letting the user enter arguments might be a good idea.
There are a couple of ways to handle parameters:



Let's try it out. We create a script which takes arguments and then we output them:


#!/bin/bash

echo "Command name: $0"
echo "Number of arguments: $#"
echo "All arguments in one string: $*"
echo ""

echo "List of arguments:"
for X in $@
do
echo $X
done


maffelu@maffelu-laptop:~/shellScript$ sh testScript.sh foo bar
Command name: testScript.sh
Number of arguments: 2
All arguments in one string: foo bar

List of arguments:
foo
bar


Back to top


Article comments

Feel free to comment this article using a facebook profile.

I'm using facebook accounts for identification since even akismet couldn't handle all the spam I receive every day.