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
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 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:
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:
This would consider World an argument. Here, we need to add quotes:
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
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
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
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
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:
- $0 The name of the command
- $1 The first parameter
- $2 The second parameter
- $3 The third parameter etc etc etc
- $# Refers to the number of arguments entered (count)
- $* Returns all parameters in a single string
- $@ Returns all parameters in a sequence of strings
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
Sahmir
2010-04-27 06:08:46
Thanks for the list on the test operators!