old-www/LDP/LG/issue54/okopnik.html

534 lines
28 KiB
HTML

<!--startcut ==============================================-->
<!-- *** BEGIN HTML header *** -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML><HEAD>
<title>Introduction to Shell Scripting LG #54</title>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
ALINK="#FF0000">
<!-- *** END HTML header *** -->
<CENTER>
<A HREF="http://www.linuxgazette.com/">
<H1><IMG ALT="LINUX GAZETTE" SRC="../gx/lglogo.jpg"
WIDTH="600" HEIGHT="124" border="0"></H1></A>
<!-- *** BEGIN navbar *** -->
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="nielsen.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue54/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="pramode.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
<!-- *** END navbar *** -->
<P>
</CENTER>
<!--endcut ============================================================-->
<H4 ALIGN="center">
"Linux Gazette...<I>making Linux just a little more fun!</I>"
</H4>
<P> <HR> <P>
<!--===================================================================-->
<center>
<H1><font color="maroon">Introduction to Shell Scripting</font></H1>
<H4>By <a href="mailto:ben-fuzzybear@yahoo.com">Ben Okopnik</a></H4>
</center>
<P> <HR> <P>
<!-- END header -->
<BLOCKQUOTE>
Never write it in 'C' if you can do it in 'awk';
<br>Never do it in 'awk' if 'sed' can handle it;
<br>Never use 'sed' when 'tr' can do the job;
<br>Never invoke 'tr' when 'cat' is sufficient;
<br>Avoid using 'cat' whenever possible.
<br><CITE>--Taylor's Laws of Programming</CITE>
</BLOCKQUOTE>
<p>Last month, we looked at loops and conditional execution. This time
around, we'll look at a few of the simpler "external" tools (i.e., GNU
utilities) that are commonly used in shell scripts.
<p>Something to keep in mind as you read this article: the tools available
to you as a script writer, as you might have guessed from
the above quote, are arranged in a rough sort of a "power hierarchy".
It's important to remember this - if you find yourself continually
being frustrated by the limitations of a specific tool, it may not
have enough "juice" to do the job.
<p>Some time ago, while writing a script that processed Clipper database
files, I found myself pushed up against the wall by the limitations of
arrays in "bash"; after a day and a half of fighting it, I swore a bitter
oath, glued a "screw it" label over the original attempt, and
rewrote it in "awk".
<p> It took 15 minutes.
<p> I didn't tell anyone at the time; even my good friends would
have taken a "clue-by-4" to my head to make sure that the lesson stuck...
<p><b>Don't</b> be stubborn about changing tools when the original one
proves under-powered.
<H2>cat</H2>
<p>Strange as it may seem, 'cat' - which you've probably used on innumerable
occasions - can do a number of useful things beyond simple
catenation. As an example, 'cat -v file.txt' will print the contents
of "file.txt" to the screen - and will also show you all the non-text
characters that might normally be invisible (this excludes the standard
textfile characters such as `end-of-line' and `tab'), in "'^'
notation". This can be very useful when you've got something that is
supposed to be a text file, but various utilities keep failing to
process it and give errors like "This is a binary file!". This capability
can also come in handy when converting files from one type
to another (see the section on 'tr'). If you decide you'd like to see
<u>all</u>
the characters in the file, the `-A' switch will fill the bill -
`$' signs will show the end-of-lines (the buck stops here?), and `^I'
will show the tabs.
<p>'-n' is another useful option. This one will number all the lines (you
can use `-b' to number only the non-blank lines) of a file - <u>very</u>
useful when you want to create a `line selector', i.e., whenever you
want to have a "handle" for a specific line which you would then pass
to another utility, say, 'sed' (which is very happy with line numbers).
<p>'cat' can also serve as a "mini-editor", if you need to insert more
than a line or two into a file during the execution of your script. In
most cases, the built-in 'read' function of 'bash' will take care of that
sort of thing - but it is designed as more of a "question/reply" mechanism;
'cat' is a bit more useful for file input.
<p>Last, but not least, 'cat' is very useful for displaying formatted text,
e.g., the error messages at the beginning of a shell script.
<br>Here are two script "snippets" for comparison:
<p>
<hr WIDTH="100%"><font face="Courier New,Courier">&nbsp; ...</font>
<br><font face="Courier New,Courier">&nbsp; echo "'guess' - a shell script
that reads your mind"</font>
<br><font face="Courier New,Courier">&nbsp; echo "and runs the program
you're thinking about."</font>
<br><font face="Courier New,Courier">&nbsp; echo</font>
<br><font face="Courier New,Courier">&nbsp; echo "Syntax:"</font>
<br><font face="Courier New,Courier">&nbsp; echo</font>
<br><font face="Courier New,Courier">&nbsp; echo "guess [-fnrs]"</font>
<br><font face="Courier New,Courier">&nbsp; echo</font>
<br><font face="Courier New,Courier">&nbsp; echo "-f&nbsp;&nbsp;&nbsp;
Force mode: if no mental activity is detected,"</font>
<br><font face="Courier New,Courier">&nbsp; echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
take a Scientific Wild-Ass Guess (SWAG) and execute."</font>
<br><font face="Courier New,Courier">&nbsp; echo "-n&nbsp;&nbsp;&nbsp;
Read your neighbor's mind; commonly used to retrieve"</font>
<br><font face="Courier New,Courier">&nbsp; echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
the URLs of really good porno sites."</font>
<br><font face="Courier New,Courier">&nbsp; echo "-r&nbsp;&nbsp;&nbsp;
Reboot brain via TCP (Telepathic Control Protocol) - for</font>
<br><font face="Courier New,Courier">&nbsp; echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
those times when you're drawing a complete blank."</font>
<br><font face="Courier New,Courier">&nbsp; echo "-s&nbsp;&nbsp;&nbsp;
Read the supervisor's mind; implies the '-f' option."</font>
<br><font face="Courier New,Courier">&nbsp; echo</font>
<br><font face="Courier New,Courier">&nbsp; exit</font>
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br>
<hr WIDTH="100%">
<p>
<hr WIDTH="100%">
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br><font face="Courier New,Courier">&nbsp; cat &lt;&lt; !</font>
<br><font face="Courier New,Courier">&nbsp; 'guess' - a shell script that
reads your mind</font>
<br><font face="Courier New,Courier">&nbsp; and runs the program you're
thinking about.</font>
<p><font face="Courier New,Courier">&nbsp; Syntax:</font>
<p><font face="Courier New,Courier">&nbsp; guess [-fnrs]</font>
<p><font face="Courier New,Courier">&nbsp; -f&nbsp;&nbsp;&nbsp; Force mode:
if no mental activity is detected,</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
take a Scientific Wild-Ass Guess (SWAG) and execute.</font>
<br><font face="Courier New,Courier">&nbsp; -n&nbsp;&nbsp;&nbsp; Read your
neighbor's mind; commonly used to retrieve</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
the URLs of really good porno sites.</font>
<br><font face="Courier New,Courier">&nbsp; -r&nbsp;&nbsp;&nbsp; Reboot
brain via TCP (Telepathic Control Protocol) - for</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
those times when you're drawing a complete blank.</font>
<br><font face="Courier New,Courier">&nbsp; -s&nbsp;&nbsp;&nbsp; Read the
supervisor's mind; implies the '-f' option.</font>
<p><font face="Courier New,Courier">&nbsp; !</font>
<br><font face="Courier New,Courier">&nbsp; exit</font>
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br>
<hr WIDTH="100%">
<br>Note that everything between the two exclamation points will be printed
to 'stdout' (the screen) as formatted; the only requirement
for "closing" the printable text is that the "!" must be on a line
by itself, which allows the delimiter to be used as part of the text.
Delimiters other than "!" may also be used.
<p>I tend to think of 'cat' as an "initial processor" for text that will
be further worked on with other tools. That's not to say that it's unimportant
- in some cases, it's almost irreplaceable. Indeed, your 'cat' can do tricks
that are not only entertaining but useful... and you don't even need a
litter box.
<H2>tr</H2>
<p>When it comes to "one character at a time" processing, this utility,
despite it's oddities in certain respects (e.g., characters specified
by their ASCII value have to be entered in <b>octal</b>), is
one of the most useful ones in our toolbox. Here's a script using it that
replaces those "DOS-text-to-Unix" conversion utilities:
<p>
<hr WIDTH="100%">
<br><font face="Courier New,Courier">&nbsp; #!/bin/bash</font>
<br><font face="Courier New,Courier">&nbsp; [ -z $1 ] &amp;&amp; { echo
"d2u - converts DOS text to Unix."; echo \</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
"Syntax: d2u &lt;file&gt;"; exit }</font>
<p><font face="Courier New,Courier">&nbsp; cat $1|tr -d '\015'</font>
<br>
<hr WIDTH="100%">
<p>&lt;grin&gt; I guess I'd better take time to explain; I can already hear
the screams of rage from all those folks who just learned about 'if'
constructs in last month's column.
<p><i>"What happened to that nice `if' statement you said we needed at
the beginning of the script? and what's that `&amp;&amp;' thing?"</i>
<p>Believe it or not, it's all still there - at least the mechanism that
makes the "right stuff" happen. Now, though, instead of using
the structure of the statement and fitting our commands into the "slots"
in the syntax, we use the <i>return value</i> of the commands,
and make the logic do the work.&nbsp; Let's take a look at this very
important concept.
henever you use a command, it returns a code on exit - typically 0&nbsp;
for success, and 1 for failure (exceptions are things like the
'length' function, which returns a value). Some programs return a variety
of numbers for specific types of exits, which is why you'd
normally want to test for zero versus non-zero, rather than testing
for `1' specifically. You can implement the same mechanism in your scripts
(this is a good coding policy): if your script generates a variety of messages
on different exit conditions, use 'exit n' as the
last statement, where `n' is the code to be returned. The plain 'exit'
statement returns 0. These codes, by the way, are invisible - they're internal
"flags"; there's nothing printed on the screen, so don't bother looking.
<p>To test for them, 'bash' provides a simple mechanism - the reserved
words `&amp;&amp;' (logical AND) and `||' (logical OR). In the script above,
the statement basically says "if $1 has a length of zero, then the following
statements (<font face="Courier New,Courier">echo... echo...&nbsp; exit</font>)
should be executed". If you're not familiar with binary logic, this may
be confusing, so here's a quick rundown that will suffice for our purposes:
in an '<font face="Courier New,Courier">A &amp;&amp; B</font>' statement,
if 'A' is true, then 'B' will also be true (i.e., if 'B' is a command,
it will be executed). In an
<br>'<font face="Courier New,Courier">A || B</font>' statement, if 'A'
is <u>false</u>, then 'B' will be true (i.e., executed). The converse of
either statement is obvious (a.k.a. "is left as an exercise for the student".)
&lt;grin&gt;
<p>As a comparison, here are two script fragments that do much the same
thing:
<p>
<hr WIDTH="100%">
<br><font face="Courier New,Courier">&nbsp; if [ -z $1 ] then</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
echo "Enter a parameter."</font>
<br><font face="Courier New,Courier">&nbsp; else</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; echo
"Parameter entered."</font>
<br><font face="Courier New,Courier">&nbsp; fi</font>
<br>
<hr WIDTH="100%">
<p>
<hr WIDTH="100%"><font face="Courier New,Courier">&nbsp;[ -z $1 ] &amp;&amp;
echo "Enter a parameter." || "Parameter entered."</font>
<br>
<hr WIDTH="100%">
<p>You have to be a bit cautious about using the second version for anything
more complex than "echo" statements: if you use a command in the part after
the `&amp;&amp;' which returns a failure code, <u>both</u> it and the statements
after `||' will be executed! This in itself can be useful, if that's what
you need - but you have to be aware of how the mechanism works.
<br>&nbsp;
<p>Back to the original "d2u" script - note the use of the `\' character
at the end of the second line: this `escape' character "cancels" the `end-of-line'
character, making the following line a continuation of the current one.
This is a neat trick for enhancing readability in scripts with long lines,
allowing you to visually break them while maintaining program continuity.
I put it between the "echo"&nbsp; statement and the text string for a reason:
<i>whitespace</i>
(here, spaces and tabs) makes no difference to a command string but stands
out like a beacon in text, creating ugly formatting problems. Make
sure your line breaks happen in reasonable places in the command
string - i.e., not in the middle of text or quoted syntax.
<p>The "active" part of the script, "cat $1|tr -d '\015'", pipes the original
text into 'tr', which deletes DOS's "CR/Carriage Return"
character (0x0D), shown here in octal (\015). That's the bit... err,
_byte_ that makes DOS text different from Unix text - we use just the "LF/Newline"
character (0x0A), while DOS uses both (CR/LF). This is why Unix text looks
like<font face="Courier New,Courier"></font>
<p><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
This is line one*This is line two*This is line three*</font>
<p>&nbsp; in DOS, and DOS text like
<p><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
This is line one^M</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
This is line two^M</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
This is line three^M</font><font face="Courier New,Courier"></font>
<p>&nbsp; in Unix.
<p>"A word to the wise" applicable to any budding shell-script writer:
close study of the "tr" man page will pay off handsomely. This is a
tool that you will find yourself using again and again.
<H2>head/tail</H2>
<p>A very useful pair of tools, with mostly identical syntax. By default
they print, respectively, the first/last 10 lines of a given file; the
number and the units are easily changed via syntax. Here's a snippet that
shows how to read a specific line in a file, using its line number as a
"handle" (you may recall this from the discussion on "cat"):
<p>
<hr WIDTH="100%"><font face="Courier New,Courier">&nbsp; ...</font>
<br><font face="Courier New,Courier">&nbsp; handle=5</font>
<br><font face="Courier New,Courier">&nbsp; line="$(head -$handle $1|tail
-1)"</font>
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br>
<hr WIDTH="100%">
<br>Having defined `$handle' as `5', we use "<font face="Courier New,Courier">head
-$handle</font>" to read a file specified on the command line and print
all lines from 1 to 5; we then use "<font face="Courier New,Courier">tail
-1</font>" to read only the last line of that. This can, of course, be
done with more powerful tools like "sed"... but we
won't get to that for a bit - and Taylor's law, above, is often a sensible
guideline.
<p>These programs can also be used to "identify" very large files without
the necessity of reading the whole thing; if you know that
one of a number of very large databases contains a unique field name
that identifies it as the one you want, you can do something like
this:
<p>
<hr WIDTH="100%">
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br><font face="Courier New,Courier">&nbsp; for fname in *dbf</font>
<br><font face="Courier New,Courier">&nbsp; do</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; head
-c10k $fname|grep -is "cost_in_sheckels_per_cubit" &amp;&amp; echo $fname</font>
<br><font face="Courier New,Courier">&nbsp; done</font>
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br>
<hr WIDTH="100%">(Yes, I realize we haven't covered 'grep' yet. I trust
those readers that aren't familiar with it will use their "man" pages wisely...
or
hold their water until we get to that part. :)
<p>So - the above case is simple enough; we take the first 10k bytes (you'd
adjust it to whatever size chunk is necessary to capture all the field
names) off the top of each database by using 'head', then use 'grep' to
look for the string. If it's found, we print the name of the file. Those
of you who have to deal with large numbers of multi-gigabyte databases
can <u>really</u> appreciate this capability.
<p>'tail' is interesting in its own way; one of the syntax differences
is the '+' switch, which answers the question of "how do I read everything
<u>after</u>
the first X characters/lines?" Believe it or not, that can be a very important
question - and a very difficult one to
answer in any other way... (<i>Also sprach</i> The Voice of Bitter
Experience.)
<H2>cut/paste</H2>
<p>In my experience, 'cut' comes in for a lot more usage than 'paste' -
it's very good at dealing with fields in formatted data, allowing you to
separate out the info you need. As an example, let's say that you have
a directory where you need to get a list of all the files that are 100k
or more in size, once a week (logfiles over a size limit, perhaps). You
can set up a "cron" job to e-mail you:
<p>
<hr WIDTH="100%">
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br><font face="Courier New,Courier">&nbsp; ls -r --sort=size $dir|tr -s
' '|cut -d ' ' -f 5,9|grep \</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -E
^'[1-9]{6,} '|mail joe@thefarm.com -s "Logfile info"</font>
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br>
<hr WIDTH="100%">
<p>'ls -r --sort=size $dir' gives us a listing of `$dir' sorted by size
in `reverse' order (smallest to largest). We pipe that through "tr -s '
'" to collapse all repeated spaces to a single space, then use "cut" with
space as a delimiter (now that the spaces are singular, we can actually
use them to separate the fields) to return fields 5 and 9 (size and filename).
We then use 'grep' to look at the very beginning of the line (where the
size is listed) and print every line that starts with a digit between 1
and 9, repeats that match 5 times, and is followed by a space.&nbsp; The
lines that match are piped into 'mail' and sent off to the recipient.
<p>'paste' can be useful at times. The simplest way of describing it that
I can think of is a "vertical 'cat'" - it merges files line by line,
<br>instead of "head to tail". As an example, I had a long list of songs
followed by the names of the groups that performed them, and I wanted the
song names to be in quotes. The songs were separated from the names by
tabs. Here was the solution:
<p>
<hr WIDTH="100%"><font face="Courier New,Courier">&nbsp; #!/bin/bash</font>
<br><font face="Courier New,Courier">&nbsp; # Single-use file; no error
checking</font>
<p><font face="Courier New,Courier">&nbsp; cut -f 1 $1 &gt; groups&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
# 'Tab' is the default separator</font>
<br><font face="Courier New,Courier">&nbsp; cut -f 2- $1 &gt; songs</font>
<br><font face="Courier New,Courier">&nbsp; for n in $(seq $(grep -c $
songs))</font>
<br><font face="Courier New,Courier">&nbsp; do</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; echo
'"'&gt&gt;quotes</font>
<br><font face="Courier New,Courier">&nbsp; done</font>
<p><font face="Courier New,Courier">&nbsp; paste -d "" quotes songs quotes
&gt; list1</font>
<br><font face="Courier New,Courier">&nbsp; paste list1 groups &gt; list</font>
<p><font face="Courier New,Courier">&nbsp; rm quotes songs groups list1</font>
<br>
<hr WIDTH="100%">
So - I split the file in two, with the first fields going into "groups"
and all the rest into "songs". Then, I created a file called "quotes" that
contained the same number of double quotation marks as there were lines
in the "songs" file by using 'grep' to count `end-of-line' characters in
"songs" (the `$' character stands for `EOL' in regular expressions). The
next part was up to 'paste' - the standard delimiter for it is `tab', which
I replaced with an empty string (I wanted the quotes right next to the
song names). Then, I pasted the "groups" file into the result with the
default 'tab' as the separator - and it was done, all except for cleaning
up the temporary
<br>files.
<H2>grep</H2>
<p>The "Vise-Grips" of Unix. :) This utility, as well as its more
specialized&nbsp; relatives 'fgrep' and 'egrep', is used primarily for
searching files for matching text strings, using the 'regexp' (Regular
Expression) mechanism. (There are actually two of these, the 'basic' and
the 'extended', either one of which can be used; the 'basic' is the default
for 'grep'.)
<p><i>"Let's see now; I know the quote that I want is in of these 400+
text files in this directory - something about "Who hath desired the Sea".
What was it, again?..."</i>
<p><font face="Courier New,Courier">&nbsp; Odin:~$ grep -iA 12 "who hath
desired the sea" *</font>
<p><font face="Courier New,Courier">&nbsp; Poems.txt-Who hath desired the
Sea? - the sight of salt water unbounded -</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-The heave and the
halt and the hurl and the crash of the comber</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-&nbsp;&nbsp;&nbsp;
wind-hounded?</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-The sleek-barrelled
swell before storm, grey, foamless, enormous,</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-&nbsp;&nbsp;&nbsp;
and growing -</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt:Stark calm on the
lap of the Line or the crazy-eyed hurricane</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-&nbsp;&nbsp;&nbsp;
blowing -</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-His Sea in no showing
the same - his Sea and the same 'neath each</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-&nbsp;&nbsp;&nbsp;
showing:</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
His Sea as she slackens or thrills?</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-So and no otherwise
- so and no otherwise - hillmen desire their</font>
<br><font face="Courier New,Courier">&nbsp; Poems.txt-&nbsp;&nbsp;&nbsp;
Hills!</font>
<p><font face="Courier New,Courier">&nbsp; Odin:~$</font>
<br>&nbsp;
<p><i>"Yep, that was the one; so, it's in `Poems.txt'..."</i>
<p>'grep' has a wide variety of switches (the "-A &lt;n&gt;" switch that I
used above determines the number of lines of context after the matched
line that will be printed; the "-i" switch means "ignore case") that allow
precise searches within a single file or a group of files, as well as specifying
the type of output when a match is found (or conversely, when no match
is found). I've used 'grep' in several of the "example" scripts so far,
and use it, on the average, about a dozen times a day, command line and
script usage together: the search for the above Kipling quote (including
my muttered comments) happened just a few minutes before I sharpened my
cursor and scribbled this paragraph.
<p>You can also use it to search binary files too, with 'strings' (a utility
that prints only the text strings found in binary files) as a useful
companion: an occasionally useful "last-ditch" procedure for those
programs where the author has hidden the help/syntax info behind some obscure
switch, and 'man', 'info', and the '/usr/doc/' directory come up empty.
<p>Often, there is a requirement for performing some task the same number
of times as there are lines in a given file, e.g., reading in each line
of a configuration file and parsing it. 'grep' helps us here, too:
<p>
<hr WIDTH="100%">
<br><font face="Courier New,Courier">&nbsp; ...</font>
<br><font face="Courier New,Courier">&nbsp; for n in $(grep -n $ ~/.scheduler)</font>
<br><font face="Courier New,Courier">&nbsp; do</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LINE=$(head
-$n ~/.scheduler|tail -1)</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DATE=$(echo
"$LINE"|cut -d ' ' -f 1)</font>
<p><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</font>
<br><font face="Courier New,Courier">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</font>
<p><font face="Courier New,Courier">&nbsp; done</font>
<br>
<hr WIDTH="100%">
<p>This is a snippet from a scheduling program I wrote some time ago; whenever
I log in, it reminds me of appointments, etc. for that day.
'grep', in this instance, numbers the lines (this is used in further
processing - not shown) and polls every line for the "end-of-line"
metacharacter ('$') which matches every line in the file. The result
is then parsed into the date and text variables, and the script executes
an "alarm and display" routine if the appointment date matches today's
date.
<H2>Wrapping it up</H2>
<p>In order to produce good shell scripts, you need to be very familiar
with how all of these tools work - at the very least, have a good idea
<br>what a given tool can and cannot do (you can always look up the exact
syntax via 'man'). There are many other, more complex tools that are available
to us - but these six programs will get you started and keep you going
for a long time, as well as giving you a broad field of possibilities for
script experimentation of your own.
<p> Until next month -
<p> Happy Linuxing!
<p>
<H2>"Script quote" of the month</H2>
<BLOCKQUOTE>
I used to program my IBM PC
to make hideous noises to wake me up. I
also made the conscious decision
to hard-code the alarm time into the
program, so as to make it more
difficult for me to reset it. After I
realised that I was routinely
getting up, editing the source file,
recompiling the program and
rerunning it for 15 minutes extra sleep,
before going back to bed, I
gave up and made the alarm time a
command-line option.
<BR><CITE>--B.M. Buck</CITE>
</BLOCKQUOTE>
<H2>References</H2>
<UL>
<LI> The "man" pages for 'bash',
'builtins', 'cat', 'head', 'tail', 'cut', 'paste', 'grep', 'strings'
<LI> <A HREF="../issue53/okopnik.html">"Introduction to Shell Scripting
- The Basics"</A> by Ben Okopnik, LG #53</font>
<LI> <A HREF="../issue54/okopnik.html">"Introduction to Shell Scripting"</A>
by Ben Okopnik, LG #54</font>
<LI> <A HREF="../issue55/okopnik.html">"Introduction to Shell Scripting"</A>
by Ben Okopnik, LG #55</font>
</UL>
<!-- *** BEGIN copyright *** -->
<P> <hr> <!-- P -->
<H5 ALIGN=center>
Copyright &copy; 2000, Ben Okopnik<BR>
Published in Issue 54 of <i>Linux Gazette</i>, June 2000</H5>
<!-- *** END copyright *** -->
<!--startcut ==========================================================-->
<HR><P>
<CENTER>
<!-- *** BEGIN navbar *** -->
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="nielsen.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue54/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="pramode.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
<!-- *** END navbar *** -->
</CENTER>
</BODY></HTML>
<!--endcut ============================================================-->