Ever wanted to automate a task on your *nix machine? Or are you fed up executing some set of commands again and again? If yes, then shell scripting can help you out there. In this tutorial, introduce yourself to shell scripting, along with some hands-on examples to practice.
What is a shell?
The shell is a user program or it is an environment provided for user interaction. It is a command language interpreter, that executes commands read from the standard input device such as a keyboard or from a file.
Several shells are available for Linux including:
- BASH ( Bourne-Again SHell ) - Most common shell in Linux. It's Open Source.
- CSH (C SHell) - The C shell's syntax and usage are very similar to the C programming language.
- KSH (Korn SHell) - Created by David Korn at AT & T Bell Labs. The Korn Shell also was the base for the POSIX Shell standard specifications.
- TCSH - It is an enhanced but completely compatible version of the Berkeley UNIX C shell (CSH).
A shell is simply accessible using a terminal. If you are familiar with *nix, then you might have an experience of using it. Now, if you want to know what all shells are available in your system, then use the following command.
$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
/bin/zsh
/usr/bin/zsh
What is a shell script?
It is a programming language of shells, where you may write a set of commands to execute for a particular shell. And similar to programming, shell scripts support that sequential, conditional and iteration flow along with functions that help to make your programs more modular.
Shell Program: It is simply the file containing shell commands. You may directly run this file instead of individually executing each command.
Please note that each shell does the same job, but each understands different command syntax and provides different built-in functions. In this tutorial, we'll discuss shell scripting w.r.t bash. Because "bash" is used as a default shell in most *nix systems.
Use cases of shell scripting
The following tasks could be done easily with shell scripting.
- Automating a series of tasks:
Like loading script files according to the availability of shells found in your system. Or loading OS-specific settings. (here loading means to source/run a file) - Setting cron/anacron jobs:
Like every week, auto removing packages that are no longer usable. - Executing a set of commands.
Like installing no. of packages, without repeatedly giving your confirmations (y).
Commands you write in a script can also be executed directly on a shell, and commands you write in the shell can also be written in the script. Therefore, you may do everything with scripting, that you may think of doing with a shell.
Writing your first script
To create a shell program you need to go through the following steps:
1. Create a file for the shell program.
$ touch newfile.sh
Here, we have given the extension .sh or .bash (conventional for bash script), though it is not necessary, but quite useful to distinguish between scripts of different shells.
2. Put a shebang (#!) on the first line of the file
1 |
|
Shebang(#!) is a special comment, that informs, which shell to be selected while executing this file. It is actually the path to the shell required to run this script.
3. Add some commands to the file
echo "Script is working fine!"
4. Save the file.
5. Make the file executable and run the program.
To run a script file, at least it must have the permission of "readability" so that we can explicitly execute it using a shell.
$ bash newfile.sh
Script is working fine!
And if the script file is "executable", then we may directly run the file as shown below.
$ chmod +x newfile.sh
$ ./newfile.sh
Script is working fine!
Hurray! You got your first shell script running. Let's go ahead and explore more programming elements of scripting.
Comments
Writing comments is really useful in any programming language. Because the code is read much more times than it is written. Also, comments make the code easy to comprehend, thus, it is advisable to use comments to explain complex parts of the code.
In shell scripting, you may give comments with hash (#) symbol.
# File: newfile.sh
# Purpose: To learn and practice shell script
# Author: Your name
# These are comments in a shell script.
Shell Parameters
Values you pass to a shell program from a terminal while executing the script, are called program parameters. For example.
$ ./newfile.sh Hello World
Here, "Hello" and "World" are 2 parameters that are passed to newfile.sh. You may pass any number of parameters to a shell script. But, the question arises, how are we gonna use them in a program?
\$0 to \$9
- \$0: It holds the complete path to the script file from the current working directory.
- \$1-\$9: These hold shell parameters. For example: \$2 is second parameter.
- \$#: It holds the total number of shell parameters (i.e exclude \$0).
To access these parameters we may use dollar (\$) symbol followed by the number of parameter. For example, if your file is.
1 2 3 4 |
|
Output:
$ ./newfile.sh Hello World
Running script: ./newfile.sh
First parameter is: Hello
Second parameter is: World
Along with these you also have other predefined variables.
\$* and \$@
- \$*: It has all the parameters passed as a single word, where each element is space separated.
- \$@: It has all the parameters passed but it holds each parameter as a separate word.
\$* and \$@ seems confusing from the definition, therefore, let's take a look at the example below.
1 2 3 4 5 6 7 |
|
The output is.
$ ./newfile.sh 1 "2 3" "4 5"
Total parameters: 3
With $*
1 2 3 4 5
See, here total parameters are 3, but you are actually getting 1 string that includes all parameters.
Note: While using \$*, if the parameter is "2 3", then it is treated as "2" and "3". This means the shell interprets the space symbol. But, this is not the case with \$@.
If you use "\$@" instead of "\$*" you'll get the following.
$ ./newfile.sh 1 "2 3" "4 5"
Total parameters: 3
With $@
1
2 3
4 5
Here, 3 separate parameters can be easily seen.
\$\$ and \$?
-
\$\$: It shows the process ID. Following command shows the process ID of the current terminal.
$ echo $$ 2123
-
\$?: It holds exit status of the last command. For example.
$ clear $ echo $? 0
Here, 0 indicates that last command (clear) was successful. In case of failure, \$? returns any non-zero value.
To return exit status 0 or 1 from your shell script, simply use the exit command.1 2 3
#!/bin/bash echo "Successful" exit 0
Shift Command to access more than 9 parameters
You may easily go from \$1 to \$9. But how would you use the 10th parameter?
Accessing 10th parameter is not possible with "\$10" because the shell will interpret only the first digit, that is "1", and output of this statement would be the first parameter followed by a "0".
To solve this issue we have shift command. This command shifts all the parameter to left by one. As a result, it also decreases the number of total parameters (\$#). For example.
1 2 3 4 5 6 7 |
|
The output of this command.
$ ./newfile.sh a b c d e f
Total parameters: 6
Parameters: b c d e f
Total parameters: 5
Parameters: c d e f
Total parameters: 4
Parameters: d e f
Total parameters: 3
Parameters: e f
We can see, that parameters are shifting to left and getting reduced by one. So, if you want to access 10th parameter then you need to left shift once and access \$9. However, this command is not much recommended due to the side effect of losing earlier parameters.
Variables and Expressions
Expressions are valid combination of variables and operators.
Variables: These are shell parameters or any other initialized variable in a shell program like.
1 2 3 |
|
Note: No space around "=" (assignment) operator.
Operators: These are special symbols to operate on variables. For example.
String operations
-z string
True: if the length of a string
is 0.
-n string
True: if the length of a string
is not 0.
string1 = string2
True: if the two strings are identical.
string != string2
True: if the two strings are NOT identical.
string
True: if the string
is not NULL.
Integer operations
int1 -eq int2
True: if the first int is equal to the second.
int1 -ne int2
True: if the first int is not equal to the second.
int1 -gt int2
True: if the first int is greater than the second.
int1 -ge int2
True: if the first int is greater than or equal to the second.
int1 -lt int2
True: if the first int is less than the second.
int1 -le int2
True: if the first int is less than or equal to the second.
File operations
-r file
True: if the file
exists and is readable.
-w file
True: if the file
exists and is writable.
-x file
True: if the file
exists and is executable.
-f file
True: if the file
exists and is a regular file.
-d file
True: if the file
exists and is the directory.
Logical operations
!
reverse the result of expression
-a
AND operator
-o
OR operator
Conditional Flow
When program control flows sequentially from start to end of a program, it is known as the sequential flow of programming. But, in case of conditional flow, the program is executed on the basis of conditional statements. If the result of such statements is true, one part of the code is executed, and if the result is false, another part of the code is executed, not both. This is called "Mutual Exclusion".
Shell script provides 2 conditional statements 1) if-else
and 2) case
. Let's have a look at their syntax and usage.
If-else statement
Usage: To execute a set of commands, on the basis of evaluation of an expression.
Syntax:
if command1 # if block started
then
command-list1
elif command2 # optional
then
command-list2
else # optional: to accept rest of the conditions.
command-list3
fi # end of if-else block
Here, elif and else parts are optional. This implies if command1 returns success (0), then execute command-list1. Otherwise, if command2 returns success (0), then execute command-list2, and if no condition turns out to be successful, then execute command-list3.
Example:
1 2 3 4 5 6 7 8 9 10 |
|
Here, "ls | grep \^z" searches files/folders in the current directory that starts with "z". And "> /dev/null" sends the output of the file in a null device (typically used for disposing of unwanted output streams of a process).
The output of the above program is shown below. (As I don't have any file that starts with "z")
Second case.
Test Condition
Usage: It is mostly used in association with if-else to evaluate expressions instead of commands.
Syntax: test expression
or [ expression ]
Example:
if [ expression ]
then
command-list1
fi
Let's put all this in a short example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Output:
$ ./newfile.sh
Sorry, you entered fewer parameters.
$ ./newfile.sh 1
Hi! Nice to meet you!
$ ./newfile.sh 2
/home/thegeekyway
$ ./newfile.sh 3
Sorry, you entered the wrong parameter.
Case statement
Usage: Shell script also supports case conditional statement. It is used to evaluate a particular variable with some set of values.
Syntax:
case value in
pattern1) command-list1;;
pattern2) command-list2;;
*) command-list2;; # this is optional: to accept rest of the conditions
esac
Example: Let's make the above command using case statements.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The output is same as above.
Repeated action commands (Loops)
To write a set of commands, which repeat themselves until a condition is satisfied, we have loops in programming languages. Bash supports 3 types of loops:
- For loop
- While loop
- Until loop
For Loop
Usage: When iterating any variable over an iterator like "\$@", we use "for-loop".
Iterators are objects, having multiple values within them like lists in python. Therefore, using a for-loop we may access their individual values one by one.
Syntax:
for var in iterator
do
command-list
done
Example:
1 2 3 4 5 |
|
Output:
$ ./newfile.sh The Geeky Way
The
Geeky
Way
While Loop
Usage: To repeat a set of commands, till an expression remains true.
Syntax:
while [ expression ]
do
command-list
done
Example:
1 2 3 4 5 6 7 |
|
Output:
$ ./newfile.sh
0
1
2
3
Until Loop
Usage: It is reverse of while loop, it repeats a set of commands, untill an expression becomes true.
Syntax:
until [ expression ]
do
command-list
done
Example:
1 2 3 4 5 6 7 |
|
Output:
$ ./newfile.sh
0
1
2
Read input at runtime
Read Command: To read input from the user at runtime of shell script.
Syntax: read var1 var2
This command will hold (pause) the script to take input from the terminal. Once you give appropriate values, the shell script would run ahead.
Example:
1 2 3 4 5 6 |
|
Output:
$ ./newfile.sh
The Geeky Way
The
Geeky
Way
Functions
Usage: These are set of commands, which can be called any number of times in a script. Thus, it gives the feature of re-usability of code and makes the code modular.
Syntax:
function_name()
{
command-list
return value # optional
}
Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Note that, no test expression in the last block of "if-else", because calling function is a command in itself. And we put expressions in the test, not commands.
Output:
$ ./newfile.sh
Are you sure, that you want to continue (y)?: y
Continue process!!
Conclusion
Knowledge of shell scripting is a required norm, not only for Linux administrators but for a general Linux user as well.
Being a Linux user, learning shell script is simply a boon. This sets you apart from those who just use Linux, then those who know how to use Linux!
To know more about shell and scripting, you may visit here.
If you have any query or idea to improve this post, please feel free to write in the comments section below.
Thanks for reading!