BASH
Introduction
Today, when you meet someone, his or her appearance or the clothes that they wear, creates the first impression. And remember 'The first impression is always the last impression'. In Linux, you either use a GUI, or commands but no user, programmer or program ever talks to an operating system directly. There is a program that you use to communicate to an operating system and in the Linux Operating system, this program is called the shell.
Now the guys who wrote Unix realized that different people when they talk to an operating system, need different types of shell. Hence they didn't standardize on how a shell should talk to an operating system. All that they said was when the Linux Operating System loads on, it will load a program called shell and this shell would then load on another program which is being called. The point to be noted is that whenever you want to interact with an operating system, you interact through the shell.
The first shell was called 'sh' written by Stephen Bourne. Unfortunately this Bourne shell didn't have a lot of interaction. It was like programmers write commands, which the shell gives to the operating system, and the operating system executes them. Now there are a few interactive features needed from a shell like the Bourne shell didn't have the history mechanism for e.g. repeat the thirdlast command typed in. At times as a user or a programmer, we would like the operating system to complete the filename after writing a few characters because we don't want to sit and write the entire filename. Since the Bourne shell didn't give you these interactive features, Bill Joy from Sun - and the Java Team, invented another shell called the C-shell. C-shell comes as the default shell on Unixes that came in from University of California - the Berkeley Software Division. Most people feel that the C-shell has been a mistake. We don't want to get into an emotional argument here because whoever uses shell always gets into an emotional argument. The C-shell also was very buggy which is why most people used the Bourne shell to run shell programs (this book is all about how you can run shell programs) and they used C-shell for the fact that it gave interactivity, it gave features like history.....
Then there is a guy called David Korn who created the Korn shell, which was the best of the Bourne shell, and the C-shell. Unfortunately AT&T charged money for using the shell and had the licenses so our friends Richard Stallman and gang and the Free Software Foundation decided to write a better Bourne shell and they called it BASH - Bo(u)rn(e) Again SHell. :)
Now we assume you have logged into Linux and are at the prompt. This is because the first program you interact with is the Bourne Shell. What we would like you to do is write ls and press enter.
#ls
With ls, you will see a listing of your current directory, which we assume in this case, is blank. You won't see anything. We assume you know how to use a word processor so you say vi z or asedit z . You will see a blank screen. (We are here not to teach you how to use a word processor ) We write ls in this file. That's all and we quit out. Now we need to run this program called z. So you say z and press enter.
#z
bash: z: command not found
The output says the command is not found.
That is what it should be in normal circumstances, on some systems it may not. You get an error because z is in your current directory and by default, programs in your current directory are never looked at. So you say ./z, which means take z from the current directory, and you feel things will happen but nothing happens. It says execute permissions denied.
#./z
bash: ./z: Permission denied
We will tell you why it doesn't work and what has happened. By default whenever you use a word processor to write program, the programs are not executable. So, to make them executable you use the chmod command as follows
#chmod u+x z
u means you are the user, + means add, x means executable permissions. And now when you say ./z, it gives you a listing of the current directory. In our case since we have only one file - z , the output shows z.
#./z
z
Question : Why am I wasting my time on all this?
This chapter is going to teach you the basics of shell programming. We always believed that one shouldn't do anything in isolation, there should be a reason to learn shell programming. And we have our reasons too like:
1)When Linux starts on , it loads on many programs. It executes very large shell scripts. We want you to understand what these shell scripts are . This will help you understand what Linux does at boot up.
2)When you say startx to load X-Windows, startx is nothing but a shell script. What does it do? Many a times, it does do things as expected, so if anything does go wrong, you know what happens and why.
3)Whenever you install any program, you run a program called make. Now make looks at a file called Makefile that is generated because you have run this program called configure. Configure is nothing but a shell script.
4)When you start Apache or you shutdown Apache, you use a shell script.
So it is very important to understand these shell scripts.
If you want to call yourself a Linux Administrator or you want to do anything with Linux, then you'd better know shell-programming language inside out. So we decided to teach you shell programming with practical examples. In this book, you will find chapters that will focus on a certain issue and only after that we will teach you real life shell scripts. Hence you will learn Linux and at the same time, you will re-revise shell programming. This is more to know what happens for the commonly tasks that you do.
Let's get back again to our shell script
You may turn back and ask what is the point in having the ls command in z. ./z is three characters, ls - 2 characters, so why not say ls directly ? At times you may want do multiple things. You may want to run ls, then ps (tells you what programs are running) and then you may want to print date.
So you take these three commands , put them in a file called z and then run it. We get the output displayed one after the other.
z
ls
ps
date
#./z
z
z.bak
PID TTY TIME CMD
772 pts/2 00:00:00 bash
852 pts/2 00:00:00 bash
854 pts/2 00:00:00 ps
Sat Jul 8 15:43:23 IST 2000
Question : From my shell program ( you don't say program you say shell script), can I run another shell script?
So lets try it out.
z
ls
./a1
a1
ps
date
#./z
a1
z
z.bak
PID TTY TIME CMD
772 pts/2 00:00:00 bash
875 pts/2 00:00:00 bash
877 pts/2 00:00:00 bash
878 pts/2 00:00:00 ps
Sat Jul 8 15:47:22 IST 2000
In z, you first say ls and then ./a1. a1 contains ps and date (run chmod to make it an executable script). When you run z, it will first do ls and then call a1. A1 will execute ps and date and you will realize that there is no difference at all, the two outputs look the same. This means that from one shell script you can call as many shell scripts as you want.
Now we have a command called echo . echo displays whatever you give it.. Here we say echo aa hence it displays aa
z
echo aa
#./z
aa
In the next program, we have said aa=hi.
z
aa=hi
echo aa
So we have given aa a value. So when you say echo aa, we assume it will give us hi, but it yet gives you aa.
#./z
aa
That line that we wrote made no difference.
So now we make a small change. So instead of saying echo aa, we say echo $aa.
z
aa=hi
echo $aa
#./z
hi
Now when we say echo $aa, the echo will display hi. This is because aa was initialized to hi and whenever you use $ , $ means whatever is following it, don't use that , display what it stands for. In this script, aa stands for - hi. So we see hi.
The next script
z
aa=hi
echo $aa
aa=bye
echo $aa
#./z
hi
bye
In this script, we have set or initialized aa to hi, and then we change it to bye . The first echo displays hi as aa was hi at that point and the second echo displays bye, but remember both of them are the same variable.
This is how people write programs. The echo statement remains the same but something else changed in the script and now the same script gives you a different output. Aa has a value and this value varies. Hence we t call aa a variable and you only put $ in front of variables.
In the next example, you first initialize aa to 100 which is a number and then you initialize it to bye.
z
aa=100
echo $aa
aa=bye
echo $aa
#./z
100
bye
In the shell scripting language, 100 is not taken as a number. 100, hi, bye, they are all taken as words. Here you take a word, which you call a variable and you give it a value. This value is nothing but characters , letters of the alphabet and to display the value you put $ in front of it.
z
echo $aa
#./z
Here we have echo $aa. Does aa have a value ? no . So it displays nothing. If the variable doesn't have a value, it normally displays null, blank or nothing. Hence you will simply see a blank line.
z
aa=-l
ls $aa
#./z
total 12
-rwxr--r-- 1 root root 8 Jul 8 15:46 a1
-rwxr--r-- 1 root root 13 Jul 8 16:04 z
-rwxr--r-- 1 root root 33 Jul 8 16:00 z.bak
In this example, we take aa and make it equal to -l. Now when we say ls $aa, ls is not called with $aa. $aa means variable name following so it replaces it with the value which in our case is -l. ls now gets called with -l and ls with -l will display a long listing.
In the next program , you want ls to display all files
z
aa=-l -a
ls $aa
#./z
./z: -a: command not found
a1 z z.bak
Here we say aa=-l -a , -a option with ls means display all files. You will now realize that ls works but it gives you all the files on a single line; you also get an error on a.
This is what we need to understand. The shell takes only a line and what is a line ? Something that ends in an enter.
The shell starts reading that line and looks at the first space. For it, the space is a valid entity or a token or whatever techie word you want to use. So aa becomes -l aa=-l and then there is a space. So far so good. After initializing aa to -l, it comes to -a, so it tries to execute -a as a program or as a command.
Is -a a command? 'no'. Hence it gives an error and because the line gives an error, aa looses its value. Space is taken as a special character by shell but we don't want the space to stand as a special character. We want -l and -a together. So now to tell the shell to remove the special understanding that it has for the space either you put single quotes or a double quotes. When you look into the eyes of a beautiful woman, you forget the whole world. All that the single or the double does is make the shell forget or ignore the specialness of the character.
Why double quotes? - The shell has 12 special characters. The single quotes forgets/ignores all the special characters, the double says it will forget some. How do you decide which one to use .It depends on what special character you want the shell to ignore.
So now lets put the single and the double and it works as advertised.
z
aa='-l -a'
ls $aa
#./z
total 24
drwxr-xr-x 2 root root 4096 Jul 8 16:22 .
drwxr-xr-x 3 root root 4096 Jul 8 16:42 ..
-rwxr--r-- 1 root root 8 Jul 8 15:46 a1
-rwxr--r-- 1 root root 18 Jul 8 16:42 z
-rwxr--r-- 1 root root 16 Jul 8 16:22 z.bak
z
aa="-l -a"
ls $aa
#./z
total 24
drwxr-xr-x 2 root root 4096 Jul 8 16:22 .
drwxr-xr-x 3 root root 4096 Jul 8 16:42 ..
-rwxr--r-- 1 root root 8 Jul 8 15:46 a1
-rwxr--r-- 1 root root 18 Jul 8 16:42 z
-rwxr--r-- 1 root root 16 Jul 8 16:22 z.bak
Here we have gone a step further and we have said aa=ls-l .
z
aa="ls -l"
$aa
This is one more way of executing commands from a shell script. You just initialize the variable and then give the variable by itself on a new line. The $aa will get replaced by its value and since it is a command, it gets executed hence giving you a long listing.
#./z
total 16
-rwxr--r-- 1 root root 8 Jul 8 15:46 a1
-rwxr--r-- 1 root root 15 Jul 8 16:44 z
-rwxr--r-- 1 root root 18 Jul 8 16:42 z.bak
Here we have gone a step further and said ls -l in double quotes. But when we say ./z , it gives an error.
z
"ls -l"
#./z
./z: ls -l: command not found
This is because ls -l is taken as one word. That is what we have said - ls -l. Because we said ls -l, it tried to run a command ls -l. . But if you would have just said ls in double inverted commas, then everything will work as it should.
z
"ls"
#./z
a1 z z.bak
What do we learn from here?
The single quotes, the double quotes, the space .. are very important. Because the shell when it looks at your code , looks at the spaces first. To understand the space lets take the next example.
z
aa= "ls"
#./z
a1 z z.bak
When we say aa= and then when we have a space, it makes aa equal to nothing - initializing the variable and then bash executes ls. So aa becomes nothing , then you have the command ls which gives a small listing.
./z: aa: command not found
Here we say aa =ls, i.e. aa and then there is a space and =ls. Bash will try to execute aa as a program but there is no program with aa. Hence you see the error that says aa as command not found and also there will be no listing because whenever there is an error on a line, the entire line gets ignored.
To summarize, the shell picks one entire line , you can't have spaces before and after the equalto because you want to read the whole thing as one. But assuming you do make a mistake like we made , saying aa =, then here aa gets executed as a command. Since it is not a command, it gives an error and when there is an error, the whole line gets ignored.
z
aa=100
echo $aa
bb=$aa
echo $bb
#./z
100
100
Now we are making aa as 100 and we say echo $aa. This will display 100. The next statement is bb=$aa. The shell runs through your program twice, so the first time wherever it sees $aa, it will replace it with 100. And then it comes back again, makes bb=100 and wherever it sees $bb , the bash replaces it with 100..
We deliberately made it very difficult for you initially, so that you have a good understanding of the shell.
z
aa=100
echo $aa
bb=$aa + 200
echo $bb
#./z
100
./z: +: command not found
Now we say aa=100 and then we say $bb=$aa+200. Aa will become 100, that is the first round, the echo will display 100 on the screen. Then bash goes back again and initializes bb where he first sees $aa which becomes 100, then there is a space and then a +. Because there is a space, + becomes a command ; bash tries to execute the + as a command. Its like a kid, whenever it sees a sweet, it puts it in the mouth. Now that is dumb but that is life. It will come back and tell you command not found. bb will be null because that line didn't get executed.
z
aa=100
echo $aa
bb=$aa+ 200
echo $bb
#./z
./z: 200: command not found
We realize that we made a mistake so what do we do. We remove the space before the plus so bb now it becomes 100+(one word). Now it will see 200. But it sees space before 200 so it tries to execute 200 as a command. It will come back and tell you command 200 not found.
So now we make it $aa+200, so when you print $bb, it will give you 100+200.
z
aa=100
echo $aa
bb=$aa+200
echo $bb
#./z
100
100+200
It doesn't give you 300 - it gives 100+200. That means we now need to be careful of spaces.
Don't just keep putting spaces wherever you want because everything is seen as a command. So whenever you put a space, following the space is a command. Space, enters - are called delimiters.
The shell doesn't understand arithmetic's. There is a way you can make it understand arithmetic but by itself it can't add, subtract, multiply, divide. That means it didn't go to school. :-)
z
aa=100
echo $aa
echo '$aa'
echo "$aa"
#./z
100
$aa
100
Aa is 100. Echo $aa will give you 100. Then we have echo and $aa is in single quotes. $ means a variable, if it is within single quotes, it forgets what the $ means. Hence it will give you $aa.
But when you put $aa in double quotes, bash says it will forget others but it will not forget $. Hence $aa gets displayed with 100. Both single and double quotes forget the importance of the space but the $ is forgotten by single and not the double.
So here you are not supposed to put $aa in double because whether you put or you don't put , it doesn't matter. Unfortunately most guys who write scripts write it in double quotes. We can give you an entire chapter on single and double and most of you can spend an entire life but yet not understand the difference between single and double.
Now we say echo $PATH. If this variable path was not created, we will get a blank. Here the PATH is in all caps.
z
echo $PATH
#./z
/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin
Whenever you see something in all caps, it means we have not created, somebody else has created it by convention. Because PATH has a value, somebody has actually created it. PATH is called an environmental variable and aa is called a shell variable.
What is the difference?
Here we have aa=hi, we are calling a1 and then we say echo $aa. In a1 we have just got echo $aa. What will be the output? - nothing.
z
aa=hi
./a1
echo $aa
a1
echo $aa
#./z
hi
Why ?
Because variables like aa are shell variables. The variables that you create are not given to scripts called by you. That is a very simple rule. If you want to see them, you export them. Everyone wants to see the products you make, so you export them.
z
aa=hi
export aa
./a1
echo $aa
#./z
hi
hi
So if you want other scripts to see our variables, you export them. Some other scripts initialized PATH, we are allowed to use it because it was exported. So when you export a shell variable, you call it an environmental variable.
Do you realize that only fools let other people change their moods. So if I am upset right now, then you shouldn't get upset.
z
aa=hi
export aa
./a1
echo $aa
a1
echo $aa
aa=bye
echo $aa
#./z
hi
bye
hi
So we have created aa and we don't want others to change it. When a1 tries to change aa, it changes only in a1. Whatever changes you make are only valid in your script, they don't go out.
Now lets go one step further.
We now run ps. In our case when we run ps, ps by itself will tell you all the programs that are currently running . we will see bash because that is the shell we are currently working on. And you will also see ps because ps also has to say that it is running.
#ps
PID TTY TIME CMD
656 pts/0 00:00:00 bash
1558 pts/0 00:00:00 ps
So you run ps again and what you will realize is that bash has the same number but ps has a different number in the first column. - Process id or PID.
PID TTY TIME CMD
656 pts/0 00:00:00 bash
1561 pts/0 00:00:00 ps
Every program under Unix/Linux when it runs, is given a new number. So every time we run ps, it gives us a new number but if you notice the number for bash remains the same.
Now we write one line to our shell script and that is echo $$.
z
echo $$
#./z
1568
Anytime you see a $, it means it is a variable. And $$ is a free variable given to us which has the value 1568.
We have now got 3 kinds of variables given to us - 1) the shell variables that we create, 2) environmental variables which are exported variables created by others that we can use , 3)The $$ kind - variables given to us by the shell.
So $$ tells you what your process id is. ps at the prompt you will see one bash running.
Now in z you have ps. The first bash has to run ./z, so he will have to run another bash to execute your program and your earlier bash is yet waiting. So when ps is running there are two bashes. How do you know the difference?
z
ps
#./z
PID TTY TIME CMD
656 pts/0 00:00:00 bash
1594 pts/0 00:00:00 bash
1597 pts/0 00:00:00 ps
If you run ./z twice, you will realize that the first bash is the same number, the second bash number keeps changing as the ps number changes.
PID TTY TIME CMD
656 pts/0 00:00:00 bash
1602 pts/0 00:00:00 bash
1605 pts/0 00:00:00 ps
In the next script, when you say ps and echo $$, you will now realize that the echo $$ will give you the process id which is the second bash .
z
ps
echo $$
#./z
PID TTY TIME CMD
656 pts/0 00:00:00 bash
1630 pts/0 00:00:00 bash
1631 pts/0 00:00:00 ps
1630
Now lets go a step further
Here we have ps, echo $$, ./a1. And a1 will say ps with echo $$.
z
ps
echo $$
./a1
a1
ps
echo $$
#./z
PID TTY TIME CMD
640 pts/2 00:00:00 bash
689 pts/2 00:00:00 bash
690 pts/2 00:00:00 ps
689
PID TTY TIME CMD
640 pts/2 00:00:00 bash
689 pts/2 00:00:00 bash
691 pts/2 00:00:00 bash
692 pts/2 00:00:00 ps
691
Now the first bash will call the second bash. So the first ps will display two bashes, echo $$ will give you the pid of the second bash. When the second bash sees ./a, it will do what the first bash did, and hence we now have a third bash calling a1. The second bash will now wait for the third bash to quit out, so you will see three bashes and the echo $$ will obviously display the pid of the third bash. That means everytime you run a shell script you are actually asking another copy of bash to run that script. Therefore whatever changes made in any of the bash get forgotten. ie if the third bash makes some changes, then it is no longer alive in the second one.
Now if you don't want that to happen then you can say . ./a. This will make sure that the second bash doesn't start a third bash. You will see only two of them. That is why the echo $$ will give you the same in both.
z
ps
echo $$
. ./a1
#./z
PID TTY TIME CMD
640 pts/2 00:00:00 bash
783 pts/2 00:00:00 bash
784 pts/2 00:00:00 ps
783
PID TTY TIME CMD
640 pts/2 00:00:00 bash
783 pts/2 00:00:00 bash
785 pts/2 00:00:00 ps
783
If you don't like putting a dot ( . ) because your eyes are bad and you can't see, you say exec.
z
ps
echo $$
exec ./a1
#./z
PID TTY TIME CMD
640 pts/2 00:00:00 bash
796 pts/2 00:00:00 bash
797 pts/2 00:00:00 ps
796
PID TTY TIME CMD
640 pts/2 00:00:00 bash
796 pts/2 00:00:00 bash
798 pts/2 00:00:00 ps
796
Now this is one big problem that we have had with life and that is there are two thousand ways of doing exactly the same thing. That's how we get confused in life. We thought we would confuse you also. So when you see . and exec, you say they are the same things.
Now you can say . and exec for the earlier program. You say . ./a1 , if a1 changes any of the variables, the change will be reflected in the original.
z
aa=hi
. ./a1
echo $aa
a1
aa=bye
echo $aa
#./z
bye
bye
That's why whatever changes you make in the third bash remains only in the third bash. Now you know why they have given you a . or an exec.
In the next script we say ls;ps.
z
ls;ps
#./z
a1
a1.bak
z
z.bak
PID TTY TIME CMD
640 pts/2 00:00:00 bash
852 pts/2 00:00:00 bash
854 pts/2 00:00:00 ps
When you put a ; its like saying enter. Its like saying first execute ls and then execute ps.
Now lets introduce a new word - exit.
z
exit 7
#./z
#./z;echo $?
7
Now we say exit 7. When you run this program by saying ./z, nothing happens. But when you say ./z;echo $? , the $? will give you 7. Whenever programs quit out, there should be some way of knowing whether that program has quit out as a success or with failure. So to figure that out whether it was success or failure, programs return values to whoever calls it. In this case, bash has called this program and under Linux every time a program quits out to bash, a special variable like $$, in this case its $? stores the return value.
The convention is whenever programs return 0 it means success, any other value means a failure. The reasoning behind this convention is that a program can fail for 200 various reasons, every program can return a different value for the failure that took place. With exit, the program ends i.e. you don't write any more lines after exit. If you do write lines after exit, they don't get called.
z
exit 7
echo $?
#./z
#./z;echo $?
Here we have exit 7 and then we write echo $? . This isn't going to work because the echo will not be called.
z
./a1
echo $?
a1
exit 12
#./z
12
#./z;echo $?
12
0
In z, we are now calling ./a1 and a1 exits with 12. So when you say ./z and then echo $? , the ./z will call a1, a1 will exit with 12 the echo $? will display 12. z also has quit out and z doesn't have an exit statement. so the exit value will be that of the last statement that was executed. In our case it is echo $? which was executed successfully. So the $? after ./z at the prompt will be that of echo i.e. 0. If you don't believe us, go and put exit 14, it will give you 14. So you will see 12 and 14.
Now wouldn't it be nice if we could say that we want to execute one program and if this program executed successfully only then execute another one. Isn't it dumb to execute another program if the first one has failed? A feature as such is supported in the shell programming language.
There are so many scripting languages and there is no visible difference between a scripting and programming language. C is a programming language, bash shell, perl , php are scripting languages but they have all been written in C. All that we want to tell you is 'Please don't use C'. C is an unintelligent language but you can in C write a perl, write a shell that is intelligent. We are saying don't use C directly, it takes too long to do the same job in C than in the scripting languages. In C, there is no way where you can say run a program and if it results in a success, then run another program. In the shell you can. This happens many a times when you are executing large programs which says that if something is successful then do something more because if the earlier program ends in a failure, the second one will also end in a failure. Now how do you do that.
z
./a1 && ./a2
a1
echo a1
exit 0
a2
echo a2
#chmod u+x a2
#./z
a1
a2
You have ./a1 and then two ampersands - the && means if the earlier guy - a1 on execution returned success then execute the next one. So when we say ./a1 it echoes a1 and then there is exit 0 which means it is a success. Hence it will execute a2. A2 has echo a2 that displays a2 on screen.
Now you go back to a1 and there you say exit 1 or any other number. It will not execute a2 because the first one failed.
z
./a1 && ./a2
a1
echo a1
exit 1
a2
echo a2
#chmod u+x a2
#./z
a1
Now lets look at the reverse logic.
That is we want to execute the second program only if the guy before ended in a failure. For that you use the || (or) sign .
./a1 || ./a2
a1
echo a1
exit 0
a2
echo a2
#./z
a1
Isn't this very dumb because you saying only if there is a failure execute the file? A1 has exit 0 so a2 doesn't get called. The minute you make it exit 1, a2 gets called.
./a1 || ./a2
a1
echo a1
exit 1
a2
echo a2
#./z
a1
a2
Isn't that very dumb - even I thought so.. Then , we searched the entire Internet and read every shell-programming tutorial. Please not that this is not our original work, it is the collective wisdom of anybody and everybody who put anything on shell script on the net.
Two reasons why you need it the or ||. If the first program ends in a failure then you would want to display a failure message. You can do that with the or . More important is that every program normally quits out and if it is failure, at times programs may have created 4 temporary files, which may have not been deleted. These temporary files have to be deleted. If it succeeds then the program will do it but in case of a failure then you have to do it. Isn't this a useful reason for something like this?
The shell guys are there to please everybody. They allow you to use the && and the || together. So if the program exits in a success then the first && gets called, if it is failure then the || gets called.
z
./a1 && echo success || echo failure
a1
echo a1
exit 0
#./z
a1
success
---------------
z
./a1 && echo success || echo failure
a1
echo a1
exit 1
#./z
a1
failure
Now we are putting ls in square brackets.
z
[ls]
#./z
./z: [ls]: command not found
You will now get an error because bash takes the square brackets and ls together. So now we put a space before and after ls. It will not give you any error but ls will not give a listing either. So, obviously in the square brackets you are not supposed to run programs
z
[ ls ]
#./z
#
These square brackets are another word for test. It returns either a true or false.
In this script, we say aa=hi, . You are supposed to give condition, you are supposed to ask questions. So we are asking a question - is $aa = hi. The answer is true and because the left is true it will run ls.
z
aa=hi
[ $aa = hi ] && ls
#./z
a1 a1.bak a2 z z.bak
Then we make aa as bye and check is it equal to hi. Since the answer is no, we will not see any listing.
z
aa=bye
[ $aa = hi ] && ls
#./z
Now we initialize aa to bye , and then we check whether $aa is not equal to hi. bye is not equal to hi, and hence bash will now run ls.
z
aa=bye
[ $aa != hi ] && ls
#./z
a1 a1.bak a2 z z.bak
This is how you can make your programs very intelligent. If certain conditions are being met, then we say run ls otherwise don't.
This is only the start 'tip of the iceberg'. The square brackets are special and they stand for test
z
aa=bye
test $aa != hi && ls
#./z
a1 a1.bak a2 z z.bak
Now we use -x in the square brackets.
z
[ -f ./a1 ] && ls
#./z
a1 a1.bak a2 z z.bak
--------------------
z
[ -x ./a1 ] && ls
#./z
a1 a1.bak a2 z z.bak
--------------------
z
[ -f ./a100 ] && ls
#./z
-------------------------
z
[ -d ./a1 ] && ls
#./z
--------------------------
z
[ -d /bbook1 ] && ls
#./z
a1 a1.bak a2 z z.bak
-------------------------
These program works exactly in the same way. Now you will realize that the -f means does the file exist. So if a1 exist, ls gets called , a100 doesn't exist so ls doesn't get called. -x means is it executable. -d means is it a directory.
Where will you use a -f , -x, -d ?
At times you may want to run a shell script and you don't want any errors to be displayed. So use this to check if the file is there and then run it. If the file doesn't exist, then don't run it. At times you may want to check whether the file exist , and then you may want to make it executable, if it is not executable so that you can't run it. For this purpose you use the -x option. At times you may want to know whether certain directories are present, then you may want to do certain things otherwise not.
Now we come to one more -z - this stands for zero length. So if $aa has a length of 0, write now when you create a variable, its length is not 0, if you don't create it, its length is 0. That is why when you make aa=hi, its length is not 0, its false so ls doesn't get called and in the second case it gets called.
z
aa=hi
[ -z $aa ] && ls
#./z
#
---------------------
z
[ -z $aa ] && ls
#./z
a1 a1.bak a2 z z.bak
Now you use -n that is the reverse of -z. -n means the variable should exist.
z
aa=hi
[ -n $aa ] && ls
#./z
a1 a1.bak a2 z z.bak
Now we ask ourselves as to why are we doing all this if we can't call a shell script with parameters. Don't we say ls -l. The -l goes to the ls command. That's how ls can give you the output as it does. So now you have a shell script $1 , $2.
z
echo $1
echo $2
#./z
We say ./z and we get two blanks. Remember when the shell variables are not created, they get a blank space. But if you say ./z aa bb, $1 becomes aa, $2 becomes bb.
z
echo $1
echo $2
#./z aa bb
aa
bb
$1, $2 are called positional parameters. You can go upto 9. You can go beyond that also but right now we will assume we can go upto 9.
z
echo $0
#./z
./z
The next program echoes $0, $0 stores the name of the program.
Now we have $# which will tell you how many words or how many of the positional parameters have been set. Set is another word for initialized. So when you say ./z you will get 0. But if you say ./z aa bb , you will get 2 because you've got two parameters.
z
echo $#
#./z
0
#./z aa bb
2
In the next script, we are printing $1 , $2 .
z
echo $0
echo $1
echo $2
echo $#
shift
echo $0
echo $1
echo $2
echo $#
#./z
./z
aa
bb
3
./z
bb
cc
2
Then you say ./z aa bb cc. $1 will become aa, $2 becomes bb, $3 will be cc. So $# will give you 3. Then there is a command called shift. When you say shift, $1 gets lost. The previous value of $2 now becomes $1. The $2 value becomes that of $3 and it keeps going on. So when you want to shift all your parameters to the left, you use shift. It is like rerunning the command , that is why $# reduces by 1 and $0 remains as is.
One reason you would use this is when you want to remove parameters from the list when you are through with them, another is when you want more than 9 parameters, and then this is what you do. There are other ways but this is one of them.
z
echo $@
echo $*
#./z aa bb cc
aa bb cc
aa bb cc
Whenever you say $* or you say $@, both will give you all the parameters one after the other. So both of them will display for you the same thing, aa bb cc. So whenever you want all the parameters you either use $* or $@.
Now you will keep wondering why they gave you two because there is one small subtle difference . We promise we will do it in the last chapter of the book because it is very subtle.
Whenever you want to give all parameters to a function, a command, you will use $* or $@.
Just as a re-revision, we say echo followed by hi .. (10 spaces ) and then bye. Then we put the same thing in double quotes.
z
echo hi bye
echo "hi bye"
#./z
hi bye
hi bye
All that we are saying is that in the first case the shell first starts. It reads the word hi , then it sees 10 spaces , it then laughs " Aren't these guys dumb enough to give 10 spaces?", and it doesn't like the spaces so it removes the spaces. Then it actually calls echo and gives echo two things hi and bye. In other words $1 will be hi and $2 will be bye in the code of echo if echo was a shell built in or a script. If you put the same thing in double quotes then you are making $2 as nothing and $1 as hi with 10 spaces and bye. Echo will display all the command line arguments and hence in the first case echo doesn't give you the spaces, in the second one it does. We are not putting this in single quotes because single doesn't recognize any special characters (Remember in double quotes , special characters like $ have meaning but in single quotes , every character looses its meaning)
z
aa=hi
echo $aa
echo ${aa}
aabb=bye
echo $aabb
echo ${aa}bb
bb="$aa (aa)"
echo $bb
#./z
hi
hi
bye
hibb
hi (aa)
In this script aa is given the value of hi and then we say echo $aa. In the next line we follow the true syntax of echo hence we've said echo {$aa} - $aa is in curly braces. Won't it look very odd if we said echo and then the $aa in curly braces? You will wonder why they give it to you this way. This is only for flexibility purpose. They were trying to be reasonable and thought that if you just had echo $aa, you will have a problem. Like here we have another variable aabb initialized to bye and then echo $aabb. Here the shell doesn't look at $aa is a variable because aa and aabb are both variables in the same script. It takes the longest one , echo $aabb will display bye. but now if you say echo ${aa}bb, then aa gets executed first and bb becomes a normal bb, so you will see hibb. Round brackets don't have any meaning. The next one will give the value stored in aa as hi followed by aa in round brackets.
Earlier when we did the square brackets , the square brackets were the ones that gave the program some intelligence. That means the square brackets said that if some condition was met then do something otherwise don't. That was a shorter form of what programming languages call as an if statement. To be called a scripting language, to be called a programming language, you need to have an if statement. If statement decides whether code should be executed or not. If statements are well explained in the code following.
z
if ./a1 ; then
echo true
else
echo false
fi
a1
exit 0
#./z
true
Here we use if and then we run as usual our ubiquitous a1. A1 has one line - exit 0. 0 remember is true so the if statement becomes true , the then is part of the syntax, the semicolon is part of the syntax . From the then to the else gets executed only when if executes the command and returns a true. You will see echo true. Take the same a1 and you give exit 1, you will see false - exit 1 gets executed resulting in false. Remember between the then and the else, and the else and the fi (fi - end of it all), you could have put one million lines of code.
Sometimes you may not want the semicolon, it reminds you of the C programming language, in such a case you put the 'then' on a separate line. If you want to put the then on a separate line as some people want to do, add the semi colon otherwise you don't.
z
if ./a1
then
echo true
else
echo false
fi
a1
exit 0
#./z
true
This is why we get upset with scripting languages because you have two thousand ways of doing the same thing. What difference does it make. Simple have a rule, remove the semicolon and put the then on a separate line, we will all smile and say 'thankyou very much Sir'.
Now a little while ago we had -f in a square brackets which meant that if a file exist (in this case there should be a file called fastboot from the root) ; if you have file fastboot then echo true otherwise it will echo false.
z
if [ -f /fastboot ]
then
echo true
else
echo false
fi
#./z
false
What we are saying is that in the if statement and what we did earlier in the square bracket is the same thing. That was a short form, it let you execute only one thing , it could have two three or more if wanted. This is the most generic form of that so the same rules that applied there, apply here also. That's why in that sense it is a repetition
z
if [ ! -f /fastboot ]
then
echo true
else
echo false
fi
#./z
true
If you want to now reverse the condition meaning that if there is no file called fastboot, then do something , you simply put a ! sign. The ! sign makes false true and true false.
Now we say here -n keytable ( this is a repetition ), it says -n that means if the variable keytable exist and has been given a value, and in the next example we put it in double quotes, you will realize that in both case we will get different answers. Now you will ask why do we need it .
z
if [ -n $KEYTABLE ]
then
echo true
else
echo false
fi
#./z
true
z
if [ -n "$KEYTABLE" ]
then
echo true
else
echo false
fi
#./z
false
Let's take the next example
z
if [ -n $KEYTABLE -o -d /usr/lib/kbd/keymaps ]
then
echo true
else
echo false
fi
#./z
./z: [: too many arguments
false
The next example says -n keytable and then there is a -o . -o is the or . At times when you write code, you want your if statement to have more complicated conditions. You may say that if any one of the two things happen, you want to execute some code and not otherwise. Or you may say that if both happen then do something otherwise don't. E.g. I want to invite somebody for a party, either he is wearing good clothes or he has lots of money. If one of the conditions is true, he should be invited, in some of the conditions, both should be true. The first case is called an or, in the second case it is an and.
Here we are saying either the variable keytable should exist or there should be a subdirectory called keymaps. -o separates the two conditions, unfortunately you get an error. Because $keytable here doesn't exist. So now bash gets all confused and it says -n $keytable - doesn't exist and it immediately looks at the -o . So the condition now starts with -o and hence it gives an error.
So now to remove the error, you put the variable in double quotes.
z
if [ -n "$KEYTABLE" -o -d /usr/lib/kbd/keymaps ]
then
echo true
else
echo false
fi
#./z
true
If $keytable doesn't exist, that means if the variable is unset, then the double will at least give you a blank instead of nothing. These are the little eccentricities of the shell that you should understand. From now on we will always try and put the keytable in double quotes. We will always try to put variables in square brackets and in double quotes because if they don't exist then we have a problem. And you only have a problem if you have got && or || following. In single you don't have a problem at all.
The second case is when you want both conditions to be simultaneously satisfied. You want keytable to be a variable to be set and at the same time there should be a directory called keymap. If both are true, only then will the condition be true.
z
KEYTABLE=hi
if [ -n "$KEYTABLE" -a -d /usr/lib/kbd/keymaps ]
then
echo true
else
echo false
fi
#./z
true
If you remove the line keytable=hi, one of the condition will be false it will return false.
if [ -n "$KEYTABLE" -a -d /usr/lib/kbd/keymaps ]
then
echo true
else
echo false
fi
#./z
false
Now the big problem that we had with the shell earlier is that it didn't understand arithmetic. So here we say aa is 100 and then we say $aa -gt 40. That means if it is greater than 40. So whenever you have to make numerical comparisons, you have to use -gt. -eq means equal, lt - less than .....
z
aa=100
if [ $aa -gt 40 ]
then
echo true
else
echo false
fi
#./z
true
So that is how you can do arithmetic or you can do as we did earlier X$aa - So this actually becomes Xhi.
aa=hi
if [ X$aa = Xhi ]
then
echo true
else
echo false
fi
Is Xhi equalto Xhi ? true so the statement echo true gets executed. remember when you do string comparisons, you say =, !=. If you are looking at numbers you say -gt -eq. This just shows that for bash, strings are more important, it naturally understands strings and not numbers.
We are now running a program called a1 that exits with a 0. Because we are exiting with 0, $? will be 0. The question is : Is this 0 a number or a string? Technically it is a number that looks like a string. So when we say $rc = 0, we are making a string comparison. It is an actual literal comparison - rc is 0, the shell being smart gives you true.
z
./a1
rc=$?
if [ $rc = "0" ]
then
echo true
elif [ $rc = 1 ] ; then
echo false
else
echo no
fi
a1
exit 0
#./z
true
a1
exit 1
#./z
false
a1
exit 4
#./z
no
Now when a1 returns 1, once again bash is smart enough to realize that rc is 1, this is also 1, and both of them are actually strings so we don't have a problem. if statement here is if..then..else fi. Elif is a short form of else if condition. So by doing this you can give more and more and more checks.
a1
exit 01
The only problem that we have is when we say exit 01, the exit says I will remove all leading 0, the 01 becomes 1 and we don't have a problem.
z
aa=01
if [ $aa = 1 ]
then
echo true
fi
#./z
But if you now take aa and make it 01, then when you say $aa=1, it is an error because aa is 01 and the right hand side is 1. Both are taken to be strings. So if you take $aa=01, then everything works.
z
aa=01
if [ $aa = 01 ]
then
echo true
fi
#./z
true
You have to be very careful about all this. A little while ago we showed you the if..then..elif..else..fi. We haven't really explained elif because normally if you have too many elifs it means you have too many possibilities. So whenever you have too many possibilities you have another option, you use the case statement. In the case statement , you say case and then you give the name of the variable.
z
UTC=yes
case $UTC in
yes)
echo hi
;;
no)
echo bye
;;
esac
#./z
hi
Here we have UTC as a shell variable, $UTC is initialized to yes and then there is a word in. After in is a list of possibilities. So if $UTC is a yes then bash execute statements following yes. How do we know $UTC gets over ? you see two semicolons , they indicate the end of the option. That means case checks for a pattern. So if $UTC is yes, bash executes everything upto the two semicolons. If not try the next one. In this case the second possibility is a no, but since $UTC is yes it quits out after executing the statements matching this pattern. The case need not be this simple, you can make the case statement as complicated as you want . It checks for the pattern of the variable before the close the bracket which can be multiple separated by an or.
z
UTC=YES
case $UTC in
yes | YES)
echo hi
;;
no)
echo bye
;;
*)
echo problem
esac
#./z
hi
Here we check whether UTC is a 'yes' or a 'YES', if so then you echo hi. As in an if statement you had a final else where nothing met the condition, the else got called, in the case statement there is an *. A * matches 0 to infinity of anything. Because * matches everything, echo problem will be displayed. It is the catchall. It is like food at home; after everyone finishes, you clean up the plates by eating whatever is left. :}
Remember the first pattern the case matches, everything stops. So if you are going to put the *) at the very beginning, all conditions will match everything hence the other patterns will never get called.
When somebody puts the *) right at the beginning, you know he doesn't know to write a case statement. The two semicolons are very important, they don't have to be one line, and they are the ones that separate the pattern.
Now we say aa=date , then echo $aa.
z
aa=date
echo $aa
#./z
date
echo displays date but we thought date was a command!!. In real life, what we would really like to do is execute a command and store the output of the command in a variable. To do so, that we have another set - the back quotes.
z
aa=`date`
echo $aa
#./z
Tue Jan 11 13:35:47 IST 2000
So if you put something in back quotes it gets executed as a command and the output of the command gets stored in aa. If you run the date command by itself and then run this program, you will realize aa as a variable has the same content as what the date command gives. That means whenever you want the output of a command to become the value of the variable, remember the same variable can be used in an if statement, the case statement or anywhere, the backquotes can be used. That's why the backquotes are extremely crucial , they are the major features of the shell.
z
bb=hi
aa="Today $bb is `date` "
echo $aa
#./z
Today hi is Tue Jan 11 13:37:55 IST 2000
Here we are creating a variable bb and giving it the value hi. Aa has the value in double quotes, double quotes recognizes the $ sign, it takes the $bb and replaces it with hi, then he sees the back quotes, it executes date. So hence when you echo $aa it gives you 'Today hi is ................'.
The major difference between single and double, is that double recognizes the $, says they are special so it does something. Single doesn't recognize any of the special characters so it doesn't do anything. It is called quoting characters. In the next script, we run abc and the shell comes back and tells you that it doesn't know abc.
z
abc
#./z
./z: abc: command not found
Then in the next script, we say abc() , open the brace, close the brace and then say ls ls -l..
z
abc()
{
ls
ls -a
}
abc
#./z
a1 a1.bak a2 z z.bak
. .. a1 a1.bak a2 z z.bak
Now when we run abc, it first executes ls and then ls -a. Now whenever you have a word that ends in an open and close bracket, it is called a function. A function does something . What does it do ?
It executes whatever is in open and close bracket. when we run abc. Here abc stands for ls, ls -a. If you ever want to repeat code, normally you do something, you want to repeat it somewhere else, functions are ideal for that because they let you reuse your code. So whenever you are doing something over and over again, you put it into a function.
Remember the shell is not very intelligent. It starts executing from the beginning, sees an abc, gives you an error. It assumes abc is a command, then it comes below and sees abc as a function but it can't go back, The second abc will work because by then it knows abc is a function .
abc
abc()
{
ls
ls -a
}
abc
#./z
./z: abc: command not found
a1 a1.bak a2 z z.bak
. .. a1 a1.bak a2 z z.bak
What we are trying to say is that bash doesn't read everything in one go and understand that abc is a function. As it keeps reading, it keeps executing.
A function requires parameters. With parameters, you can make the program behave differently.
z
abc()
{
string=$1
echo $string
shift
echo $*
}
abc "how are you"
#./z
how are you
Earlier we had used $1, $2, $3 to display parameters given to the program, here we see how it works with functions. String here becomes $1 and then we are echoing $string. This shows 'how are you' on the screen. Then we are shifting. The shift will make reinitialize $1 to $2, and then $2 to $3. Shift will shift all the parameters to the left by 1 so echo $* will give you whatever is left. Since there is no $2, it will give you blank.
Now when we remove the double, we are actually passing 3 values, $1 - how, $2 - are, $3 - you. So when you say string=$1, and then we say echo $string, you see how . Then you shift it, $1 becomes $2, and $2 becomes $3. So when you say echo $*, it will display are you on the screen.
z
abc()
{
string=$1
echo $string
shift
echo $*
}
abc how are you
#./z
how
are you
The next example is a more practical example we are saying action how are you and then we are passing it -l a a1.
z
action()
{
string=$1
echo $string
shift
ls $*
}
action "how are you" -l z a a1
#./z
how are you
ls: a: No such file or directory
-rwxr--r-- 1 root root 7 Jul 10 17:30 a1
-rwxr--r-- 1 root root 81 Jul 11 13:55 z
First the echo will display how are you because of $1 and then when you shift it. In one sense we are knocking of how are you , $* will become everything now and these will be parameters passed to ls. So in other words we are calling a function, how are you is the label to be displayed and the rest of it are parameters to a command. Since there is no file called a, ls gives an error on that followed by a long listing for a1 and z
We can go one step further, remove the ls and instead put the ls as part of command. So now you notice we are actually saying action how are you and then ls and everything gets executed as a command.
z
action()
{
string=$1
echo $string
shift
$*
}
action "how are you" ls -l z a a1
#./z
how are you
ls: a: No such file or directory
-rwxr--r-- 1 root root 7 Jul 10 17:30 a1
-rwxr--r-- 1 root root 81 Jul 11 13:55 z
This will be very clear when we do the startup scripts with you, the startup script says rc.sysinit. Then there is a function called action, where we would like a string to be logged and the rest of it to be executed as a command. The above code demonstrates how you can have a function that does the two.
Before we forget action has a $1 $2 $3. That $1 $2 $3 is only valid in a function. Outside the function, the $1, $2, $3 have their original value which is what the shell gave it when you ran it.
z
action()
{
string=$1
echo $string
shift
$*
}
action "how are you" ls -l z a a1
echo $1 $2
#./z
how are you
ls: a: No such file or directory
-rwxr--r-- 1 root root 7 Jul 10 17:30 a1
-rwxr--r-- 1 root root 92 Jul 11 14:04 z
one two
Whenever you want something to wait, you say sleep.
z
ls
sleep 10
#./z
a1 a1.bak a2 z z.bak
You give a number that is in seconds. You are running ls, you want to see the output, then when we say sleep 10, it will wait for 10 seconds and then get out.
So far we've done so many things but we haven't really learnt how to take input from the user, so you want the user to type in something . You say read, read will wait.
z
read aa
echo $aa
#./z
hi bye
Whatever you type in goes into aa. When you type in hi bye and press enter, aa will contain hi bye. So we say echo $aa, the value of $aa is hi bye. Maybe you would like to key in 20 things so now we have 2 variables, aa and bb.
z
read aa bb
echo $aa
echo $bb
#./z
how are you
how
are you
Then we say how are you and you are typing in three things, so when you display aa, it will be how , the are and you gets displayed in bb because the space is considered to be separator and the enter means the read is over. Bash being extremely smart will take every word till the delimiter, which is a space, and store it in the first variable. the last variable gets everything till the read is over i.e. the enter key. That's why people use the shell and not 'C'.
Now we are saying echo hi, this will display hi for you. Then we say hi dd.
z
echo hi
echo hi dd
#./z
hi
#cat dd
hi
Now you will realize that he has created a file called dd and when you cat dd it gives you hi.
You may think that echo behaved differently. So you say ls dd.
z
ls zz
#./z
#cat zz
a1
a1.bak
a2
dd
z
z.bak
zz
Nothing happens but ls has already worked. Now when you say cat dd, you will realize that it is the output of ls.
Now isn't there something funny going on?
Whenever you run a shell script, 3 files get created for you. You call these 3 files for want of a better name, standard input - stdin, standard output stdout, standard error stderr. Your standard input is your keyboard and standard output is the screen and the standard error is the error, which is again your screen. Ls doesn't display anything on the screen but displays to standard output. The same thing with echo It doesn't display on the screen, your echo displays to standard output. By default, the standard output is your screen. Whenever you say you are saying that your standard output will now be the file called dd. So the shell now creates a file called dd and it will redirect the standard output to dd. Hence the output of echo and ls goes to wherever the stdout is. In the case of echo it is the file called dd. The standard input, which is your file called keyboard, can also be changed .
z
read aa bb
echo $aa
echo $bb
zz
hi
bye
no
#./z
hi
In z, we read into aa and bb. Then we echo $aa and then $bb . Create a file zz with three words on three different lines and then at the shell prompt give the command ./z < zz. You will realize that aa gets hi but unfortunately hi , bye , no are on 3 separate lines. Hence the bye and no will not be seen as the read stops when it sees an enter. But if you put it on one line then hi goes into aa and bye and no go into bb.
z
read aa bb
echo $aa
echo $bb
zz
hi bye no
#./z
hi
bye no
This now means that the standard input, which was the keyboard earlier, assumes whatever you keyed in is stored in a file. Whatever are the contents of this file are given to read. Remember read and echo don't care and are not aware that the standard input and standard output have been redirected. This is the flexibility that bash provides
Lets see where we can use this redirection.
When we say ls zz, in one sense it is like saying 1 zz. If you don't say 1, the default is 1.
z
ls 1zz
#./z
#cat zz
a1
a1.bak
a2
dd
z
z.bak
zz
zz.bak
If you said pqrs, it will give you an error and that is file not found.
z
ls pqrs 2zz
zz
ls: pqrs: No such file or directory
0 - standard input, 1 standard output, 2 standard error. If you say 2, it means you are redirecting stderr into a file called zz. Cat zz will give you the error message. Now lets say ls -l pqrs z.
z
ls -l pqrs z
#./z
ls: pqrs: No such file or directory
-rwxr--r-- 1 root root 15 Jul 11 14:34 z
Here we have an ls which works , z is a file that exists, it will display ls -l z but pqrs doesn't exist. Bash will write the contents of ls -l on standard output - the terminal and the error messages will be on standard error which is also the terminal. Hence both get displayed one after the other. But we can now do a very smart thing
z
ls -l pqrs z 1pp 2qq
#./z
#cat pp
-rwxr--r-- 1 root root 24 Jul 11 14:35 z
#cat qq
ls: pqrs: No such file or directory
When we say 1pp we are redirecting standard output into pp, 2qq - here we are redirecting standard error to qq. So you say cat pp and then cat qq and you will see the output and the error messages.
The next one
z
ls -l pqrs z 1pp 2&1
#./z
#cat pp
ls: pqrs: No such file or directory
-rwxr--r-- 1 root root 24 Jul 11 14:35 z
Now we are doing something different, we are saying 1pp, so you are redirecting standard output to a file called pp. Then we have 2&1 , remember after 2 earlier you had given a file name, but here you say &1, When we say & it means the fileno or file handle. Standard output was 1, if you say 2&1 we are saying redirect 2 to standard output. But unfortunately the standard output is a file called pp, so here the 2 gets redirected to pp, So both the standard output and the standard error get redirected to pp.
Now lets reverse the thing.
z
ls -l pqrs z 2&1 1pp
#./z
ls: pqrs: No such file or directory
#cat pp
-rwxr--r-- 1 root root 25 Jul 11 14:41 z
When you say 2&1, we are saying standard error goes to &1 - because & is a file handle - not the name of the file. 1 is standard output, which right now is the terminal, which means we are redirecting standard error to the terminal. Then you follow up by saying 1pp, 1 is standard output which is now redirected to pp. Here remember bash doesn't say that standard output is pp, so standard error should be redirected to pp also. After 2&1, this is forgotten, standard error is redirected to standard output, which is the terminal. It's all over after standard error is redirected to standard output. And then standard output goes to a file called pp. Its like saying I have 100 RS, I will give 10% of 100 RS, so I will give you 10 RS. Now just because I have 5000 RS, then I am not going to give you 10% of 5000 - 50 , it is not that, you will get only 10 RS.
z
ls -l pqrs z 2/dev/null
#./z
-rwxr--r-- 1 root root 25 Jul 11 14:41 z
You can say 2/dev/null, here whatever you write to /dev/null gets ignored. It goes into the big bucket and anything that goes into the big bucket is like the black book, nobody knows where it is. So whenever you don't want standard output or standard error to be displayed on your screen, terminal, file, you redirect it to /dev/null.
z
ls -l pqrs z 2/dev/null 2&1
#./z
ls: pqrs: No such file or directory
-rwxr--r-- 1 root root 32 Jul 11 14:47 z
You first redirect stderr to /dev/null, you change your mind, and you redirect it to std output, the last one is what it sees. You can keep saying 2 as many times you want, only the last one is the active one.
Ls gives a list of files , wc counts the number or characters, words lines in a file. So assuming I want to find out the number of bytes in a file, ls doesn't give a straight option of counting. So wouldn't it be a great idea if I could take the output of ls and give it to wc. Otherwise I will have to take ls, redirect it to zz and then either you say wc zz or wc < zz.
z
ls zz
wc zz
wc <zz
#./z
11 11 43 zz
11 11 43
The only difference between the two is that if you say wc zz, it knows that zz is the name of a file, when you say <zz, it doesn't give you the file name. Instead of doing this - redirecting it to file, why don't we say ls and give the output directly to wc.
z
ls | wc
#./z
11 11 43
The or sign is the pipe sign which takes the standard output of the program on the left and gives it as an input to the program on the right. The ls may take 6 hours to execute , wc will wait because it can work only after it receives some input. Linux handles this piping, we don't have to worry about it. So the same thing that we showed you earlier can be handled by pipelining. These programs are called filters, and the main thing under Unix and Linux is building blocks. The programmers believer in one thing and that is 'Lets write small programs that do one thing but do it well, and lets take that output and give it to another program'.
We are getting tired of . Now you have file but of 0 bytes, that is you want a file but you want to delete all the contents. This is because the files have permission and attributes. Just say zz, the contents of the file get removed but the file yet is there.
z
zz
#ls -l zz
-rw-r--r-- 1 root root 43 Jul 11 14:49 zz
#./z
#ls -l zz
-rw-r--r-- 1 root root 0 Jul 11 14:54 zz
Now you are doing ls twice with zz. The second one will overwrite the first. But when you say two - , you are saying concatenate the second to the first. Add to the end of the first one.
z
ls zz
ls zz
ls yy
ls yy
#./z
#cat zz
a1
a1.bak
a2
dd
pp
qq
z
z.bak
zz
zz.bak
#cat yy
a1
a1.bak
a2
dd
pp
qq
yy
z
z.bak
zz
zz.bak
a1
a1.bak
a2
dd
pp
qq
yy
z
z.bak
zz
zz.bak
We now say cat a.h. cat normally says either give me the name of the file , I will read that file and give it to the standard output, or I will write standard input to standard output. Here it will redirect the content to a.h and in normal cases the standard input is the keyboard. But here if you have 2<, you are redirecting standard input to terminal from your program , here we are saying sss, you can give any set of characters. From those characters to the sss, everything goes to a.h. a.h becomes the standard output. Its called the 'here document' everything happens here.
z
cat a.h << sss
hi
bye
sss
#./z
#cat a.h
hi
bye
You can give curly brackets and club in many commands. It becomes one command so when you say zz, the output of both these two commands are stored in zz.
z
{
ls
date
} zz
#./z
#cat zz
a.h
a1
a1.bak
a2
dd
pp
qq
yy
z
z.bak
zz
zz.bak
Tue Jul 11 15:01:13 IST 2000
Let's go one step further.
We say i ,then we use three words, hi bye no ...So what happens here is that i first become hi, then becomes bye and then in. The do done will get executed thrice. And because i is a variable, we display it using $.
z
for i in hi bye no
do
echo $i
done
#./z
hi
bye
no
Remember whenever you see a list - hi bye no is a list - you can say $aa and put hi bye no in double inverted commas - this is for the space. So the for is what we call a repetition. When we want to repeat something over and over again you put it in a for.
z
aa="hi bye no"
for i in $aa
do
echo $i
done
#./z
hi
bye
no
----------------------------------------