old-www/LDP/LG/issue01to08/shell.html

134 lines
8.9 KiB
HTML

<HTML>
<HEAD>
<TITLE>Shell Programming</TITLE>
</HEAD>
<BODY>
<H1><IMG ALIGN=MIDDLE SRC="./gx/homeboy_icon.gif">Linux Gazette
- Shell Programming</H1>
<H2>A Publication of the Linux HomeBoy WebPage Series</H2>
<H4>&quot;The Linux Gazette...<I>making Linux just a little more fun...!</I>&quot;
</H4>
<H5>Copyright (c) 1996 Geoff Taylor <I><A HREF="mailto:geoff@marburg.dnet.co.uk">geoff@marburg.dnet.co.uk</A></I><BR><BR>
The LINUX GAZETTE is a member of the LINUX DOCUMENTATION PROJECT.<BR>
For information regarding copying and distribution of this material see the
<A HREF="./copying.html">COPYING</A> document.<BR>
Linux Home Boy Pages logo created using <EM>David Koblas'</EM> excellent
program XPAINT 2.1.1 </H5>
<P><HR WIDTH=60% ALIGN=CENTER></P>
<H2>Shell Programming</H2>
<P>Hi there, and welcome to an occasional series on shells and shell programming. Just how occasional this column is depends on you! I'm always happy to learn new tips and tricks, and (since I don't intend to write everything that appears here) this is your opportunity to share your knowledge with others. Email me at <A HREF="mailto:geoff@marburg.dnet.co.uk">geoff@marburg.dnet.co.uk</A></P>
<P>I tend to use GNU's <B>bash</B> most of the time, because I think it's pretty nifty and I like some of it's features. But I'm not tied to it. I've used <B>sh</B>, <B>csh</B>, <B>ksh</B> and <B>perl</B> in the past, and they all have their advantages and disadvantages, so there'll be no Holy Wars here! What I'd like to collect and provide here are some ready-to-use scripts you can copy and paste into your environment. I'll try and cater for both beginners and more advanced users, but again this wll be entirely contribution-driven - if you think things aren't advanced enough, send me something!.</P>
<P><HR WIDTH=60% ALIGN=CENTER></P>
<H3><IMG ALIGN=BOTTOM SRC="../gx/text.gif">px</H3>
<P>We'll start with a simple script. One of the more common commands I enter is <B>ps -ax | fgrep -i <I>xxx</I></B>, to find out more information about the running program <I>xxx</I>. The <B>ps</B> command generates a detailed listing of all processes running on the system and then the output is piped into <B>fgrep</B> to strip out irrelevant information. After a while (being a lazy sort) I put the following into a file I called &quot;px&quot;</P>
<PRE>
#!/bin/bash
if [ $# = 1 ]
then
{
ps -ax | fgrep -i $1;
}
else
{
ps -ax;
}
fi;
</PRE>
<P>[Note - to run this script, just enter the above text into a file called &quot;px&quot; and make the file executable by <B>chmod +x px</B>. The command can now be run by typing &quot;./px&quot;, or just &quot;px&quot; if the current directory is listed in your $PATH.]</P>
<P>Let's take a closer look at what's in the script. The first line looks like a comment, because bash comments start with a &quot;#&quot; symbol. It is a comment as far as the script is concerned, but as well as that it tells the operating system that this script should be interpreted using the executable <B>/bin/bash</B>. The second line is a check on the number of arguments on the command line. The environment variable $# tells you how many arguments the script was passed, and the variables $1, $2, $3 etc hold the arguments themselves. If we are passed one argument, we execute the command:</P>
<DL>
<DD>ps -ax | fgrep -i $1;
</DL>
<P>$1 will be substituted with the parameter you entered on the command line, so if you typed <B>px bash</B>, the command executed will be <B>ps -ax | fgrep -i bash</B>. Easy, eh? If we enter only the command <B>px</B>, the comparison on the second line fails and we execute the other branch of the <B>if</B> statement. This simply executes the <B>ps -ax</B> command with no filtering of the output.</P>
<P><HR WIDTH=60% ALIGN=CENTER></P>
<H3><IMG ALIGN=BOTTOM SRC="../gx/text.gif">search</H3>
<P>Now, a slightly more complicated script. One of the things I always wanted in my younger days was a <B>grep</B> option to search subdirectories recursively. I usually only wanted to know which files contained the substring, so the output from <B>grep</B> was all irrelevant except for the filename. I'm a little older and wiser now, and I've seen this problem solved a couple of different ways (Un*x motto - there's always another way!). Here's one way a colleague of mine came up with:</P>
<PRE>
search ()
{
if [ $# = 1 ]
then
{
for i in `find . -path './dev' -prune -o -print 2&gt; /dev/null`
do
{
fgrep -i $1 $i &gt; /dev/null 2&gt;&amp;1;
if [ $? = 0 ]
then
{
echo $i
} fi;
} done;
}
else
{
echo &quot;Wrong number of arguments!&quot;
} fi;
}
</PRE>
<P>Let's take a look at it. For a start, it's a shell &quot;function&quot; instead of a script. Functions are basically shell scripts loaded into the shell's memory (usually via your .profile file when you log in), and are therefore available as commands without having to put the executable file on your $PATH. More importantly, they execute in your current shell, without starting a child or subshell process.</P>
<P>Again the first thing the function does is check that it has been given the right number of arguments. In this case we want just 1 argument, and we return an error message if we have too many or not enough.</P>
<P>The next line contains 2 important snippets - a <B>for</B> loop and a <B>find</B> command. We'll look at the <B>find</B> command first.</P>
<DL>
<DD>find . -path './dev' -prune -o -print 2&gt; /dev/null
</DL>
<P>This command is used to generate a list of all files in the current directory and any subdirectories, with the exception of any directory called <I>dev</I>. (We generally want to avoid looking in any <I>dev</I> directory because it is traditionally where device files and other special files are kept, so we &quot;prune&quot; that subtree from the search. Performing a search on special files can produce some interesting results, but it's definitely not recommended!) We print out any other filenames we come across, and we redirect any errors to /dev/null because we don't really want them and they would only confuse matters.</P>
<P>The for loop is fairly straightforward:</P>
<DL>
<DD>for i in `find . -path './dev' -prune -o -print 2&gt; /dev/null`
</DL>
<P>We execute the <B>find</B> statement by placing it in backquotes, which have a special meaning in bash. In effect the expression in the backquotes evaluates to the output of the command when it's executed, so if the find command printed out the name of three files as: &quot;file1 file2 file3&quot; the for loop would effectively be: <B>for i in &quot;file1 file2 file3&quot;B>.</P>
<P>The general look of a <B>for</B> loop is &quot;B>for <I>variable</I> in <I>textlist</I></B>&quot; where <I>variable</I> is the name of the reference variable we are going to use, and <I>textlist</I> is a list of one or more strings. Each time we go through the loop, <I>variable</I> is set to the next item in <I>textlist</I>, so (using the above example) the first time through the loop <I>$i</I> is set to <I>file1</I>, second time through <I>$i</I> is set to <I>file2</I>, and third and final time through <I>$i</I> is set to <I>file3</I>.</P>
<P>Inside the loop, we call fgrep with:</P>
<DL>
<DD>fgrep -i $1 $i &gt; /dev/null 2&gt;&1;
</DL>
<P>That is, we call <B>fgrep</B> for a case-insensitive search (the &quot;i&quot; option) using the pattern passed on the command line ($1) on the currently active filename ($i), and we ignore everything it prints out. The normal action for <B>fgrep</B> is to print out the line of text that matches the search pattern, but we're not interested in that here. All we want is the name of the file that contains the search pattern. We can tell if <B>fgrep</B> found anything by looking at the value it returns to the calling environment. This value is accessible like any other shell variable, and it is called &quot;$?&quot;. If $? is zero, we know that the search expression was found in the filename contained in $i. So, all that remains is to print this filename out, which we do with the straightforward <B>echo</B> command.</P>
<P><HR WIDTH=60% ALIGN=CENTER></P>
<P>As it stands there are a couple of problems with both the <B>px</B> script and the <B>search</B> function, but they're not too bad. I'd be really interested in hearing what you think of the functions, what problems you see with them, and how you could improve or rewrite them. Drop me a line at <A HREF="mailto:geoff@marburg.dnet.co.uk">geoff@marburg.dnet.co.uk</A> to let me know what you think, or to show me some simple yet wonderful scripts of your own.</P>
<A HREF="mailto:geoff@marburg.dnet.co.uk"><IMG ALIGN=BOTTOM
SRC="./gx/sendmail.gif"></A>
<A HREF="./gazette_toc.html"><IMG ALIGN=BOTTOM SRC="./gx/index.gif"></A>
<A HREF="./linux/linux.html"><IMG ALIGN=BOTTOM SRC="./gx/home.gif"></A>
<BR><BR>
<H3><A HREF="./lg_issue8.html">Back to Linux Gazette #8</A></H3>
</BODY>
</HTML>