Resources that help you to master GNU/Linux -- all the way from Australia ! |
Linux Supporters Group |
Home | Blog | Interest | Tutorials |
A script is a text file containing a sequence of shell commands that you would otherwise type at the terminal. The script can be executed at any time or amended for future use.
Scripting in GNU/Linux is often done using the bash syntax.
Here is a good overview of bash with links to other tutorials. There is also a long and complex bash manual for reference by non-beginners, and in which information can be found .... (If you dare, have a quick look at bash version 2.05a).
For these tutorials, you'll need to pick up the basic syntax of the Bourne-Again shell. I am not going to teach you any of that; we shall just be using it. You should read through any of the following: the excellent bash tutorial by Mendel Cooper which explains all these bash details, the very thorough linux tutorial by Bob Rankin that covers any topic you want to choose about Linux, browse this quick linux tutorial by Jiri Vogel which covers a lot of stuff usefully and quickly, and also see the Hamish Whittle bash tutorial. You'll absorb quite a bit by just doing the exercises in those references.
Top down design is systematic and fast.
You then go through that process again: you define everything you can and if you can implement it do it; else break it down again into pieces etc until all done in bash or until prove it cannot be done. That's when you might have to turn to the C language.
Two things you need to know before you plunge in here:
So hold on tight...
This might be it for our example script:
That completes the specification of the script. We can now move on to the subsystem design.
This level normally answers the question "How are we going to do it?"
There are four separate things here to do:
and we would want to do this with one script. We could create a separate script for each action but that is unnecessary and it makes more things to remember.
Right now you need to know a little about shell positional parameters.
Well, yes, it looks as if we can; we can name a script log and use it to execute the four parts of the task above, and we can distinguish each of the four tasks using the 'case' statement.
If we want our script to look bash-like then whenever we want to log a normal comment we could type something like
log |
Whenever we want to just view the comments we could type something like
log -v |
Whenever we want to edit them all we could type something like
log -e |
Whenever we want to print them all we could type something like
log -p |
Cooper's tutorial shows us that this structure should work OK with the case statement but [see note].
To summarize our commands:
command you type | action requested | script executed | positional parameter 1 |
log | log a comment | log | (null) |
log -v | view all comments | log | -v |
log -e | edit all comments | log | -e |
log -p | print all comments | log | -p |
We shall consider anything else following 'log' to be a mistake, treat it as a request for help, and just display a reminder of the correct usage.
Here is the case statement so far in all its glory:
case $1 in "") [log comment] ;; -v) [view comments] ;; -e) [edit comments] ;; -p) [print comments] ;; *) [display usage] ;; esac |
Notice in this:
We still have to fill in the details that are between [] in the script above.
Now, here is the great secret to top-down design: get this working at this level before continuing, and then you always know that the last change you made is the cause of any problems, and you never have to return to debug this level again. In other words -- this should work already as far as it can. So lets get this working.
If you want to try out this script as you read this you shall need to get a command terminal onto your desktop.
Also make use of your tabbed browsing in Firefox for referring to myriad notes and manuals.
Before testing this script, lets tidy it up in a formal sort of way; i.e., let's develop some good habits.
So the script now looks like:
#!/bin/bash case $1 in "") echo "[log comment]" ;; -v|v) echo "[view comments]" ;; -e|e) echo "[edit comments]" ;; -p|p) echo "[print comments]" ;; *) echo "[display usage]" ;; esac exit 0 |
It is ready to save and be tested.
We can save all this in a file named 'log' by typing exactly this [your typing is shown in bold] after the prompt on your terminal, starting with 'cat 1>log' and ending with 'Ctrl-d'. Remember to press the ENTER key after every line:
user@host~$ cat 1>log #!/bin/bash case $1 in "") echo "[log comment]" ;; -v) echo "[view comments]" ;; -e) echo "[edit comments]" ;; -p) echo "[print comments]" ;; *) echo "[display usage]" ;; esac exit 0 Ctrl-d |
When you see the prompt return after the Ctrl-d, check that the file has been created and contains what you typed in, by using the cat command by typing this:
user@host~$ cat file |
which should produce this on the screen:
#!/bin/bash case $1 in "") echo "[log comment]" ;; -v) echo "[view comments]" ;; -e) echo "[edit comments]" ;; -p) echo "[print comments]" ;; *) echo "[display usage]" ;; esac exit 0 |
If it does not look like that, then use your favourite editor (e.g.., the nano editor) invoking it like this:
user@host~$ nano log |
and edit the commands and fix it up until it is correct. Remember that some spaces here and there do not usually matter; they just make it more readable.
You can cut and paste the text, too, onto your terminal using the mouse, if you do not want to type it all now.
Now try to run the command like this, and note the response:
user@host~$ log bash: log: command not found |
This means that bash does not know where the script is. So we need to tell bash where this script is. We can use the alias command; type this after the prompt:
user@host~$ alias log='$HOME/log'>>$HOME/.bashrc |
which establishes that when we type log, bash runs the program $HOME/log. By adding this alias to the file .bashrc in your $HOME directory (>>$HOME/.bashrc) we ensure that it is established every time bash restarts.
Now try to run it again, and note the reponse:
user@host~$ log bash: ./log: Permission denied |
This means that the script is not executable (this is the default when a file is created); change this using the chmod command:
user@host~$ chmod +x log |
Now at last you can type these four commands to check each part of your script, and note the responses:
user@host~$ log [log comment] user@host~$ log -v [view comments] user@host~$ log -e [edit comments] user@host~$ log -p [print comments] user@host~$ log ? [display usage] user@host~$ |
If any of the above do not work, use your favourite editor (e.g.., nano) like this:
user@host~$ nano log |
and edit the log command and fix it up until the above all work.
Going well; the top level works and shall never have to be debugged again. We can now concentrate on getting the four subscripts to work.
There are four things that we have not implemented that we now have to analyse by the same top down methods.
Let's do them in order.
Can we do this now in bash? Not yet; there is no single command to do this. So we need further analysis. To log a comment (by typing 'log') we might need to do a number of things.
Can we do these in bash as they stand?
Yes, we can use the variable assignment statement =, and supposing that we shall put them all in a file named 'logged.comments' in our home directory, we define the variable logfile using the command (try this yourself):
user@host~$ logfile=$HOME/logged.comments |
Yes, we can use the echo command (here is the echo manual) to put out a prompt for a comment, like this (type this yourself):
user@host~$ echo "log text: < " user@host~$ log text: < |
Yes, the read command builtin to bash does that; in fact, it does more: using the '-p' option we can specify our prompt string, too, and get what we type into the variable 'comment' (try this):
user@host~$ read -p "log text: < " comment log text: < this is a test comment user@host~$ echo $comment this is a test comment |
This shall need a bit of work, but yes, we have the date program (here is the date manual) for that. Try it; it is in the /bin directory:
user@host~$ /bin/date Sat Jul 26 15:11:35 CST 2008 |
or perhaps you prefer the date in another form, using its '-R' option:
user@host~$ /bin/date -R Sat, 26 Jul 15:11:35 CST 2008 |
That is better. But get rid of the comma that crept in after 'Sat'. Pipe (|) the date string into the translate program with its delete (-d) option zapping the comma (','). Try it now (use the up arrow to get the last command back):
user@host~$ /bin/date -R|tr -d ',' Sat 26 Jul 15:11:35 CST 2008 |
We might be happy with the date part (Sat 26 Jul) but not all that time stuff, so finally let's use the awk program (here is the awk manual) to select only those first three fields ("Sat" "26" "Jul") by piping that text into the awk program [use the up arrow and then add the pipe]:
user@host~$ /bin/date -R|tr -d ','|awk '{print $1,$2,$3}' Sat 26 Jul |
Got the date stamp where we want it!
Now store that datestamp string in a variable named 'day' by a process known as command substitution. Your command should look like this:
user@host~$ $(/bin/date -R|tr -d ','|awk '{print $1,$2,$3}') |
and assign that result to a variable named 'day', and then check that it is there correctly:
user@host~$ day=$(/bin/date -R|tr -d ','|awk '{print $1,$2,$3}') user@host~$ echo $day Sat 26 Jul |
Getting somewhere now!!
Yes, we can do that with the append (>>) operator (here is the append manual). We first concatenate the datestamp text (which is in the variable 'day') and the comment text (which is in the variable 'comment') like this:
user@host~$ echo "$date: $comment" |
and when we have done that we can append it to the end of the logfile like this:
user@host~$ echo "$date: $comment">>$logfile |
Try it.
The comment should have gone on as the last line of the comment file. We can use the tail program (here is the tail manual) to look at that last line (with the '-1' option):
user@host~$ tail -1 $logfile |
Just print out the name of the log file, like this:
user@host~$ echo "logfile is $logfile" logfile is /home/user/logged.comments |
We can view the comments in the file using the excellent less paging program (here is the less manual).
user@host~$ less $logfile |
Just invoke your favourite editor. I hesitate to introduce you to vi so better just use the simpler nano editor:
user@host~$ nano $logfile |
Send the file containing the comments to the default printer, nicely formatted using the a2ps program to convert from ASCII to postscript (here is the a2ps manual), and then check the status of the queue using the lpq program (here is the lpq manual).
user@host~$ a2ps $logfile [/home/user/logged.comments (plain): 2 pages on 1 sheet] request id is hp_laserjet_1200-39 (1 file(s)) [Total: 2 pages on 1 sheet] sent to the default printer user@host~$ lpq hp_laserjet_1200 is ready and printing |
In bash, you may separate commands with a semicolon, thus putting these commands on one line for neatness, like this:
user@host~$ a2ps $logfile; lpq |
OK, so we have developed the elements of the script by trial and error, (by design and test, I mean!) and it is in the form you want it.
Here is a record of the complete annotated script development.
And here is the final log script ready for use.
Have fun!