615 lines
31 KiB
HTML
615 lines
31 KiB
HTML
<!--startcut ==============================================-->
|
|
<!-- *** BEGIN HTML header *** -->
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
|
|
<HTML><HEAD>
|
|
<title>Introduction to Shell Scripting LG #53</title>
|
|
</HEAD>
|
|
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
|
|
ALINK="#FF0000">
|
|
<!-- *** END HTML header *** -->
|
|
|
|
<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 *** -->
|
|
<A HREF="lamb.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
|
|
<IMG ALT=""
|
|
SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom" >
|
|
<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="../faq/index.html"><IMG ALT="[ FAQ ]"
|
|
SRC="./../gx/navbar/faq.jpg"WIDTH="62" 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/issue53/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
|
|
<IMG ALT=""
|
|
SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom" >
|
|
<A HREF="sipos.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
|
|
<!-- *** END navbar *** -->
|
|
<P>
|
|
<!-- A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue53/okopnik.html">
|
|
<FONT SIZE="+2"><EM>Talkback:</EM> Discuss this article with peers</FONT></A -->
|
|
|
|
<!--endcut ============================================================-->
|
|
|
|
<H4>
|
|
"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 -->
|
|
|
|
|
|
|
|
|
|
Last month, we took a look at some basics of creating a shell script,
|
|
as well as a few of the underlying mechanisms that make it all work. This
|
|
time around, we'll see how loops and conditional execution let us direct
|
|
program flow in scripts, as well as looking at a few good shell-writing
|
|
practices.
|
|
<br>
|
|
<p>CONVENTIONS
|
|
<p>The only thing to note in this article are ellipses (...) - I use them
|
|
to indicate that the code shown is only a fragment, and not an entire script
|
|
all by itself. If it helps, think of each ellipse as one or more lines
|
|
of code that is not actually written out.
|
|
<br>
|
|
<p>LOOPS AND CONDITIONAL EXECUTION
|
|
<p>"FOR;DO;DONE"
|
|
<p>Often, scripts are written to automate some repetitive task; as a random
|
|
example, if you have to repeatedly edit a series of files in a specific
|
|
directory, you might have a script that looks like this:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">for n in ~/weekly/*.txt</font>
|
|
<br><font face="Courier New,Courier">do</font>
|
|
<br><font face="Courier New,Courier"> ae $n</font>
|
|
<br><font face="Courier New,Courier">done</font>
|
|
<p><font face="Courier New,Courier">echo "Done."</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>or like this:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">for n in ~/weekly/*.txt; do ae $n;
|
|
done; echo "Done."</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>The code in both does exactly the same thing - but the first version
|
|
is much more readable, especially if you're building large scripts with
|
|
several levels. As good general practice in writing code, you should indent
|
|
each level (the commands inside the loops); it makes troubleshooting and
|
|
following your code much easier.
|
|
<p>The above control structure is called a 'for' loop - it tests for items
|
|
remaining in a list (i.e., 'are there any more files, beyond the ones we
|
|
have already read, that fit the "~/weekly/*.txt" template?'). If the test
|
|
result is true, it assigns the name of the current item in the list to
|
|
the loop variable ("n" in this case) and executes the loop body (the part
|
|
between "do" and "done"), then checks again. Whenever the list runs out,
|
|
'for' stops looping and passes control to the line following the 'done'
|
|
keyword - in our example, the "echo" statement.
|
|
<p>A little trick I'd like to mention here. If you want to make the "for"
|
|
loop 'spin' a certain number of times, the shell syntax can be somewhat
|
|
tiresome:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">for i in 1 2 3 4 5 6 7 8 9 10 11 12
|
|
13 14 15</font>
|
|
<br><font face="Courier New,Courier">do</font>
|
|
<br><font face="Courier New,Courier"> echo $i</font>
|
|
<br><font face="Courier New,Courier">done</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>What a pain! If you wanted it to iterate, say, 250 times, you'd have
|
|
to type all of that out! Fortunately, there's a 'shortcut' - the "seq"
|
|
command, which prints a sequence of numbers from 1 to the given maximum,
|
|
e.g.,
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">for i in $(seq 15)</font>
|
|
<br><font face="Courier New,Courier">do</font>
|
|
<br><font face="Courier New,Courier"> echo $i</font>
|
|
<br><font face="Courier New,Courier">done</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>This is functionally the same as the previous script. "seq" is part
|
|
of the GNU "shellutils" package and is probably already installed on your
|
|
system. There's also the option of doing this sort of iteration by using
|
|
a "while" loop, but it's a bit more tricky.
|
|
<br>
|
|
<p>"WHILE;DO;DONE"
|
|
<p>Often, we need a control mechanism that acts based on a specified condition
|
|
rather than iterating through a list. The 'while' loop fills this requirement:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">pppd call provider &</font>
|
|
<p><font face="Courier New,Courier">while [ -n "$(ping -c 1 192.168.0.1|grep
|
|
100%)" ]</font>
|
|
<br><font face="Courier New,Courier">do</font>
|
|
<br><font face="Courier New,Courier"> echo "Connecting..."</font>
|
|
<br><font face="Courier New,Courier">done</font>
|
|
<p><font face="Courier New,Courier">echo "Connection established."</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>The general flow of this script is: we invoke "pppd", the PPP paenguin...
|
|
I mean, daemon :), then keep looping until an actual connection is established
|
|
(if you want to use this script, replace 192.168.0.1 with your ISPs IP
|
|
address). Here are the details:
|
|
<p>1) The "ping -c 1 xxx.xxx.xxx.xxx" command sends a single ping to the
|
|
supplied IP address; note that it has to be an IP address and not a URL
|
|
- "ping" will fail immediately due to lack of DNS otherwise. If there's
|
|
no response within 10 seconds, it will print something like
|
|
<br>
|
|
<p><font face="Courier New,Courier">PING xxx.xxx.xxx.xxx (xxx.xxx.xxx.xxx):
|
|
56 data bytes</font>
|
|
<br><font face="Courier New,Courier">ping: sendto: Network is unreachable</font>
|
|
<br><font face="Courier New,Courier">ping: wrote xxx.xxx.xxx.xxx 64 chars,
|
|
ret=-1</font>
|
|
<p><font face="Courier New,Courier">--- xxx.xxx.xxx.xxx ping statistics
|
|
---</font>
|
|
<br><font face="Courier New,Courier">1 packets transmitted, 0 packets received,
|
|
100% packet loss</font>
|
|
<br>
|
|
<p>2) The only line we're interested in is the one that gives us the packet
|
|
loss percentage; with a single packet, it can only be 0% (i.e., a
|
|
successful ping) or 100%. By piping the output of "ping" through the "grep
|
|
100%" command, we narrow it down to that line, if the loss is indeed 100%;
|
|
a 0% loss will not produce any output. Note that the "100%" string isn't
|
|
anything special: we could have used "ret=-1", "unreachable", or anything
|
|
else that's unique to a failure response.
|
|
<p>3) The square brackets that contain the statement are a synonym for
|
|
the 'test' command, which returns '0' or '1' (true or false) based on the
|
|
evaluation of whatever's inside the brackets. The '-n' operator returns
|
|
'true' if the length of a given string is greater than 0. Since the string
|
|
is assumed to be contiguous (no spaces), and the line we're checking for
|
|
is not, we need to surround the output in double quotes - this is a technique
|
|
that you will use again and again in script writing. Do note that the square
|
|
brackets require spaces around them - i.e., <font face="Courier New,Courier">[-n
|
|
$STRING]</font> won't work; <font face="Courier New,Courier">[ -n $STRING
|
|
]</font> is correct. For more info on the operators used with 'test',
|
|
type "help test"; a number of very useful ones are available.
|
|
<p>4) As long as the above test returns "true" (i.e., as long as the "ping"
|
|
fails), the 'while' loop will continue to execute - by printing the
|
|
"Connecting..." string every ten seconds. As soon as a single ping is successful
|
|
(i.e., the test returns "false"), the 'while' loop will break and pass
|
|
control to the statement after "done".
|
|
<br>
|
|
<p>"UNTIL;DO;DONE"
|
|
<p>The 'until' loop is the reverse of the 'while' - it continues to loop
|
|
as long as the test is false, and fails when it becomes true. I've never
|
|
had the occasion to use it; the 'while' loop and the flexibility of the
|
|
available tests have sufficed for everything I've needed so far.
|
|
<br>
|
|
<p>"IF;THEN;[ELSE];FI"
|
|
<p>There are many times when we just need to check for the existence of
|
|
a condition and branch the execution based on the result. For those times,
|
|
we have the 'if' statement:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">...</font>
|
|
<p><font face="Courier New,Courier">if [ $BOSS="jerk" ]</font>
|
|
<br><font face="Courier New,Courier">then</font>
|
|
<br><font face="Courier New,Courier"> echo 'Take this
|
|
job and shove it!'</font>
|
|
<br><font face="Courier New,Courier">else</font>
|
|
<br><font face="Courier New,Courier"> echo 'Stick around;
|
|
the money is good.'</font>
|
|
<br><font face="Courier New,Courier">fi</font>
|
|
<p><font face="Courier New,Courier">...</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p><grin> I guess it's not quite that easy... but the logic makes sense.
|
|
Anyway, if a variable called BOSS has been defined as "jerk" (C programmers
|
|
take note: '=' and '==' are equivalent in a test statement - no assignment
|
|
occurs), then the first 'echo' statement will be executed. In all other
|
|
cases, the second 'echo' statement will run (if $BOSS="idiot", you'll still
|
|
be working there. Sorry about that. :). Note that the 'else' statement
|
|
is optional, as in this script fragment:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">...</font>
|
|
<p><font face="Courier New,Courier">if [ -n $ERROR ]</font>
|
|
<br><font face="Courier New,Courier">then</font>
|
|
<br><font face="Courier New,Courier"> echo 'Detected
|
|
an error; exiting.'</font>
|
|
<br><font face="Courier New,Courier"> exit</font>
|
|
<br><font face="Courier New,Courier">fi</font>
|
|
<p><font face="Courier New,Courier">...</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>This routine will obviously exit if the ERROR variable is anything other
|
|
than empty - but it will not affect the program flow otherwise.
|
|
<br>
|
|
<p>"CASE;IN;;ESAC"
|
|
<p>The remaining tool that we can use for conditional branching is basically
|
|
a multiple 'if' statement, based on the evaluation of a test. If, for example,
|
|
we know that the only possible outputs from an imaginary program called
|
|
'intel_cpu_test' are 4, 8, 16, 32, or 64, then we can write the following:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">case $(intel_cpu_test) in</font>
|
|
<br><font face="Courier New,Courier"> 4) echo "You're
|
|
running Linux on a calculator??";;</font>
|
|
<br><font face="Courier New,Courier"> 8) echo "That 8088
|
|
is past retirement age...";;</font>
|
|
<br><font face="Courier New,Courier"> 16) echo "A 286
|
|
kinda guy, are you?";;</font>
|
|
<br><font face="Courier New,Courier"> 32) echo "One of
|
|
them new-fangled gadgets!";;</font>
|
|
<br><font face="Courier New,Courier"> 64) echo "Oooh...
|
|
serious CPU envy!";;</font>
|
|
<br><font face="Courier New,Courier"> *) echo "What
|
|
the heck are you running, anyway?";;</font>
|
|
<br><font face="Courier New,Courier">esac</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>(Before all you folks flood me with mail about running Linux on a 286
|
|
or an 8088... you can't run it on a calculator either. :)
|
|
<p>Obviously, the "*" at the end is a catch-all: if someone at
|
|
the Intel Secret Lab runs this on their new CPU (code name "UltraSuperHyperWhizBang"),
|
|
we want the script to come back with a controlled response rather than
|
|
a failure. Note the double semicolons - they 'close' each of the "pattern/command"
|
|
sets and are (for some reason) a common error in "case/esac" constructs.
|
|
Pay extra attention to yours!
|
|
<br>
|
|
<p>BREAK and CONTINUE
|
|
<p>These statements interrupt the program flow in specific ways. The
|
|
"break", once executed, immediately exits the enclosing loop; the
|
|
"continue" statement skips the current loop iteration. This is useful in a
|
|
number of situations, particularly in long loops where the existence of a
|
|
given condition makes all further tests unnecessary. Here's a long (but
|
|
hopefully understandable) pseudo-example:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">...</font>
|
|
<p><font face="Courier New,Courier">while [ hosting_party ]</font>
|
|
<br><font face="Courier New,Courier">do</font>
|
|
<br><font face="Courier New,Courier"> case $FOOD_STATUS</font>
|
|
<br><font face="Courier New,Courier"> in</font>
|
|
<br><font face="Courier New,Courier">
|
|
potato_chips_gone) replace_potato_chips;;</font>
|
|
<br><font face="Courier New,Courier">
|
|
peanuts_finished) refill_peanut_bowl;;</font>
|
|
<br><font face="Courier New,Courier">
|
|
pretzels_gone) open_new_pretzel_bag;;</font>
|
|
<br><font face="Courier New,Courier">
|
|
...</font>
|
|
<br><font face="Courier New,Courier">
|
|
...</font>
|
|
<br><font face="Courier New,Courier"> esac</font>
|
|
<br>
|
|
<p><font face="Courier New,Courier"> if [ police_on_scene
|
|
]</font>
|
|
<br><font face="Courier New,Courier"> then</font>
|
|
<br><font face="Courier New,Courier">
|
|
talk_to_nice_officers</font>
|
|
<br><font face="Courier New,Courier">
|
|
continue</font>
|
|
<br><font face="Courier New,Courier"> fi</font>
|
|
<p><font face="Courier New,Courier"> case $LIQUOR_STATUS</font>
|
|
<br><font face="Courier New,Courier"> in</font>
|
|
<br><font face="Courier New,Courier">
|
|
vodka_gone) open_new_vodka_bottle;;</font>
|
|
<br><font face="Courier New,Courier">
|
|
rum_gone) open_new_rum_bottle;;</font>
|
|
<br><font face="Courier New,Courier">
|
|
...</font>
|
|
<br><font face="Courier New,Courier">
|
|
...</font>
|
|
<br><font face="Courier New,Courier"> esac</font>
|
|
<p><font face="Courier New,Courier"> case $ANALYZE_GUEST_BEHAVIOR</font>
|
|
<br><font face="Courier New,Courier"> in</font>
|
|
<br><font face="Courier New,Courier">
|
|
lampshade_on_head) echo "He's been drinking";;</font>
|
|
<br><font face="Courier New,Courier">
|
|
talking_to_plants) echo "She's been smoking";;</font>
|
|
<br><font face="Courier New,Courier">
|
|
talking_to_martians) echo "They're doing LSD";;</font>
|
|
<br><font face="Courier New,Courier">
|
|
levitating_objects) echo "Who spiked my lemonade??";;</font>
|
|
<p><font face="Courier New,Courier"> ...</font>
|
|
<p><font face="Courier New,Courier"> ...</font>
|
|
<p><font face="Courier New,Courier"> ...</font>
|
|
<p><font face="Courier New,Courier"> esac</font><font face="Courier New,Courier"></font>
|
|
<p><font face="Courier New,Courier">done</font>
|
|
<p><font face="Courier New,Courier">echo "Dude... what day is it?"</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>A couple of key points: note that in checking the status of various
|
|
party supplies, you might be better off writing multiple "if" statements
|
|
- both potato chips <b>and</b> pretzels may run out at the same time (i.e.,
|
|
they are not mutually exclusive). The way it is now, the chips have
|
|
top priority; if two items do run out simultaneously, it will take two
|
|
loops to replace them.
|
|
<p>We can keep checking the food status while trying to convince the cops
|
|
that we're actually holding a stamp-collectors' meeting (in fact, maintaining
|
|
the doughnut supply is a crucial factor at this point), but we'll skip
|
|
right past the liquor status - as it was, we got Joe down off the chandelier
|
|
just in time...
|
|
<p>The "continue" statement skips the last part of the "while" loop as
|
|
long as the "police_on_scene" function returns 'true'; essentially, the
|
|
loop body is truncated at that point. Note that even though it is actually
|
|
inside the "if" construct, it affects the loop that surrounds it:
|
|
both "continue" and "break" apply only to loops, i.e., "for", "while",
|
|
and "until" constructs.
|
|
<br>
|
|
<br>
|
|
<p>BACK TO THE FUTURE
|
|
<p>Here is the script we created last month:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<br><font face="Courier New,Courier"># "bkup" - copies specified files
|
|
to the user's ~/Backup</font>
|
|
<br><font face="Courier New,Courier"># directory after checking for name
|
|
conflicts.</font>
|
|
<p><font face="Courier New,Courier">a=$(date +%T-%d_%m_%Y)</font>
|
|
<br><font face="Courier New,Courier">cp -i $1 ~/Backup/$1.$a</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>Interestingly enough, shortly after finishing last month's article,
|
|
I was cranking out a bit of C code on a machine that didn't have 'rcs'
|
|
(the GNU Revision Control System) installed - and this script came in very
|
|
handy as a 'micro-rcs'; I used it to take "snapshots" of the project status.
|
|
Simple, generalized scripts of this sort become very useful at odd times...
|
|
<br>
|
|
<p>ERROR CHECKING
|
|
<p>The above is a workable script - for you, or anyone who cares to read
|
|
and understand it. Let's face it, though: what we want from a program or
|
|
a script is to type the name and have it work, right? That, or tell us
|
|
exactly why it didn't work. In this case, though, what we get is a somewhat
|
|
cryptic message:
|
|
<p><font face="Courier New,Courier">cp: missing destination file Try `cp
|
|
--help' for more information.</font>
|
|
<p>For everyone else, and for ourselves down the road when we forget exactly
|
|
how to use this tremendously complex script with innumerable options :),
|
|
we need to put in error checking - specifically, syntax/usage information.
|
|
Let's see how what we've just learned might apply:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">if [ -z $1 ]</font>
|
|
<br><font face="Courier New,Courier">then</font>
|
|
<br><font face="Courier New,Courier"> clear</font>
|
|
<br><font face="Courier New,Courier"> echo "'bkup' -
|
|
copies the specified file to the user's"</font>
|
|
<br><font face="Courier New,Courier"> echo "~/Backup
|
|
directory after checking for name conflicts."</font>
|
|
<br><font face="Courier New,Courier"> echo</font>
|
|
<br><font face="Courier New,Courier"> echo "Usage: bkup
|
|
filename"</font>
|
|
<br><font face="Courier New,Courier"> echo</font>
|
|
<br><font face="Courier New,Courier"> exit</font>
|
|
<br><font face="Courier New,Courier">fi</font>
|
|
<p><font face="Courier New,Courier">a=$(date +%T-%d_%m_%Y)</font>
|
|
<br><font face="Courier New,Courier">cp -i $1 ~/Backup/$1.$a</font>
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<p>The '-z' operator of 'test' returns '0' (true) for a zero-length string;
|
|
what we're testing for is 'bkup' being run without a filename. The very
|
|
beginning is, in my opinion, the best place to put help/usage information
|
|
in a script - if you forget what the options are, just run the script without
|
|
any, and you'll get an instant 'refresher course' in using it. You don't
|
|
even have to put in the original comments, now - note that we've basically
|
|
incorporated our earlier comments into the usage info. It's still a good
|
|
idea to put in comments at any non-obvious or tricky places in the script
|
|
- that brilliant trick you've managed to pull off may cause you to cuss
|
|
and scratch your head next year, if you don't...
|
|
<p>Before we wrap up playing with this script, let's give it a few more
|
|
capabilities. What if you wanted to be able to send different types of
|
|
files into different directories? Let's give that a shot, using what we've
|
|
learned:
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<p><font face="Courier New,Courier">if [ -z $1 ]</font>
|
|
<br><font face="Courier New,Courier">then</font>
|
|
<br><font face="Courier New,Courier"> clear</font>
|
|
<br><font face="Courier New,Courier"> echo "'bkup' -
|
|
copies the specified file to the user's ~/Backup"</font>
|
|
<br><font face="Courier New,Courier"> echo "directory
|
|
tree after checking for name conflicts."</font>
|
|
<br><font face="Courier New,Courier"> echo</font>
|
|
<br><font face="Courier New,Courier"> echo "Usage: bkup
|
|
filename [bkup_dir]"</font>
|
|
<br><font face="Courier New,Courier"> echo</font>
|
|
<br><font face="Courier New,Courier"> echo "bkup_dir
|
|
Optional subdirectory in '~/Backup' where the file"</font>
|
|
<br><font face="Courier New,Courier"> echo " will be
|
|
stored."</font>
|
|
<br><font face="Courier New,Courier"> echo</font>
|
|
<br><font face="Courier New,Courier"> exit</font>
|
|
<br><font face="Courier New,Courier">fi</font>
|
|
<p><font face="Courier New,Courier">if [ -n $2 ]</font>
|
|
<br><font face="Courier New,Courier">then</font>
|
|
<br><font face="Courier New,Courier"> if [ -d ~/Backup/$2
|
|
]</font>
|
|
<br><font face="Courier New,Courier"> then</font>
|
|
<br><font face="Courier New,Courier">
|
|
subdir=$2/</font>
|
|
<br><font face="Courier New,Courier"> else</font>
|
|
<br><font face="Courier New,Courier">
|
|
mkdir -p ~/Backup/$2</font>
|
|
<br><font face="Courier New,Courier">
|
|
subdir=$2/</font>
|
|
<br><font face="Courier New,Courier"> fi</font>
|
|
<br><font face="Courier New,Courier">fi</font>
|
|
<p><font face="Courier New,Courier">a=$(date +%T-%d_%m_%Y)</font>
|
|
<br><font face="Courier New,Courier">cp -i $1 ~/Backup/$subdir$1.$a</font>
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<p>Here is the summary of changes:
|
|
<p>1) The comment section of the help now reads "...directory tree" rather
|
|
than just "directory", indicating the change we've made.
|
|
<p>2) The "Usage:" line has been expanded to show the optional (as shown
|
|
by the square brackets) argument; we've also added an explanation
|
|
of how to use that argument, since it might not be obvious to someone else.
|
|
<p>3) An added "if" construct that checks to see if $2 (a second argument
|
|
to 'bkup') exists; if so, it checks for a directory with the given name
|
|
under "~/Backup", and creates one if it does not exist (the "-d" tests
|
|
if the file exists and is a directory).
|
|
<p>4) The 'cp' command now has a 'subdir' variable tucked in between "Backup/"
|
|
and "$1".
|
|
<p>Now, you can type things like
|
|
<p><font face="Courier New,Courier">bkup my_new_program.c c</font>
|
|
<br><font face="Courier New,Courier">bkup filter.awk awk</font>
|
|
<br><font face="Courier New,Courier">bkup filter.awk filters</font>
|
|
<br><font face="Courier New,Courier">bkup Letter_to_Mom.txt docs</font>
|
|
<p>etc., and sort everything into whatever categories you like. Plus, the
|
|
old behavior of "bkup" is still available -
|
|
<p><font face="Courier New,Courier">bkup file.xyz</font>
|
|
<p>will send a backup of "file.xyz" to the "~/Backup" directory itself;
|
|
useful for files that fall outside of your sorting criteria.
|
|
<br>
|
|
<p>By the way: why are we appending a "/" to $2 in the "if" statement instead
|
|
of right in the "cp" line? Well, if $2 <u>doesn't</u> exist, then then
|
|
we want 'bkup' to act as it did originally, i.e., send the file to the
|
|
"Backup" directory. If we write something like
|
|
<p><font face="Courier New,Courier">cp -i $1 ~/Backup/$subdir/$1.$a</font>
|
|
<p>(note the extra "/" between $subdir and $1), and $2 isn't specified,
|
|
then $subdir becomes blank, and the line above becomes
|
|
<p><font face="Courier New,Courier">cp -i $1 ~/Backup//$1.$a</font>
|
|
<p>- not a particularly desirable result, since we want to stick with standard
|
|
shell syntactic practice wherever possible.
|
|
<p>In fact, it's a really good idea to consider all the possibilities whenever
|
|
you're building variables into a string; a classic mistake of that sort
|
|
can be seen in the following script -
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br><font face="Courier New,Courier">DO NOT USE THIS SCRIPT!</font>
|
|
<p><font face="Courier New,Courier">#!/bin/bash</font>
|
|
<br><font face="Courier New,Courier"># Written by Larry, Moe, and Shemp
|
|
- the Deleshun PoWeR TeaM!!!</font>
|
|
<br><font face="Courier New,Courier"># Checked by Curly: "Why, soitainly
|
|
it woiks! Nyuk-nyuk-nyuk!"</font>
|
|
<p><font face="Courier New,Courier"># All you've gotta do is enter the
|
|
name of this file followed by</font>
|
|
<br><font face="Courier New,Courier"># whatever you want to delete - directories,
|
|
dot files, multiple</font>
|
|
<br><font face="Courier New,Courier"># files, anything is OK!</font>
|
|
<p><font face="Courier New,Courier">rm -rf $1*</font>
|
|
<p><font face="Courier New,Courier">DO NOT USE THIS SCRIPT!</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p><Sigh> At least they commented it. :)
|
|
<p>What happens if somebody does run "<font face="Courier New,Courier">three_stooges</font>",
|
|
and <u>doesn't enter a parameter</u>? The active line in the script becomes
|
|
<p><font face="Courier New,Courier">rm -rf *</font>
|
|
<p>Assuming that you're Joe User in your home directory, the result is
|
|
pretty horrible - it'll wipe out all of your personal files. It becomes
|
|
a catastrophe if you're the root user in the root directory - <b>the entire
|
|
system goes away</b>!!
|
|
<p>Viruses seem like such friendly, harmless things about now... <grin>
|
|
<p>Be careful with your script writing. As you have just seen, you have
|
|
the power to destroy your entire system in a blink.
|
|
<p>
|
|
<hr SIZE=4 WIDTH="100%">
|
|
<br><font face="Courier New,Courier">Unix was never designed to keep people
|
|
from doing stupid things,</font>
|
|
<br><font face="Courier New,Courier">because that policy would also keep
|
|
them from doing clever things.</font>
|
|
<br><font face="Courier New,Courier">-- Doug Gwyn</font>
|
|
<p><font face="Courier New,Courier">Unix gives you just enough rope to
|
|
hang yourself -- and then a</font>
|
|
<br><font face="Courier New,Courier">couple more feet, just to be sure.</font>
|
|
<br><font face="Courier New,Courier">-- Eric Allman</font>
|
|
<br>
|
|
<hr SIZE=4 WIDTH="100%">
|
|
<p>The philosophy makes sense: unlimited power in the tools, restriction
|
|
by permissions - but it imposes a responsibility: you must take appropriate
|
|
care. As a corollary, whenever you're logged in as root, do not run any
|
|
shell scripts that are not provably harmless (note the Very Large assumptions
|
|
hanging off that phrase - "<u>provably harmless</u>"...)
|
|
<br>
|
|
<p>WRAPPING IT UP
|
|
<p>Loops and conditional execution are a very important part of most scripts.
|
|
As we analyze other shell scripts in future articles, you'll see some of
|
|
the myriad ways in which they can be used - a script of even average complexity
|
|
cannot exist without them.
|
|
<p>Next month, we'll take a look at some tools that are commonly used in
|
|
shell scripts - tools that may be very familiar to you as command-line
|
|
utilities - and explore how they may be connected together to produce desired
|
|
results. We'll also dissect a couple of scripts - mine, if no one else
|
|
is brave enough to send in the results of their keyboard concoctions. (<font color="#FF0000">Be
|
|
Afraid. Be Very Afraid.</font>) :)
|
|
<br>
|
|
<p>I welcome all comments and corrections in regard to this series of articles,
|
|
as well as any interesting scripts that you may send in. All flames will
|
|
be sent to <font face="Courier New,Courier">/dev/null</font> (Oh no, it's
|
|
full...)
|
|
<br>
|
|
<p>Until next month -
|
|
<p>Happy Linuxing!
|
|
<br>
|
|
<p>
|
|
<hr WIDTH="100%">
|
|
<br>"SCRIPT QUOTE" OF THE MONTH:
|
|
<p><font face="Courier New,Courier">``What's this script do?</font>
|
|
<p><font face="Courier New,Courier">'unzip; touch; finger; mount; gasp;
|
|
yes; umount; sleep'</font>
|
|
<p><font face="Courier New,Courier">Hint for the answer: not everything
|
|
is computer-oriented. Sometimes you're in a sleeping bag, camping out with
|
|
your girlfriend.''</font>
|
|
<br><font face="Courier New,Courier"> -- Frans van der Zande</font>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<p>REFERENCES
|
|
<p><font face="Courier New,Courier">The "man" pages for 'bash', 'seq',
|
|
'ping', 'grep'</font>
|
|
<br><font face="Courier New,Courier">The "help" command for 'for', 'while',
|
|
'until', 'if', 'case', 'test',</font>
|
|
<br><font face="Courier New,Courier">'break', 'continue'</font>
|
|
<br><font face="Courier New,Courier">"Introduction to Shell Scripting -
|
|
The Basics" by Ben Okopnik, LG #53</font>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- *** BEGIN copyright *** -->
|
|
<P> <hr> <!-- P -->
|
|
<H5 ALIGN=center>
|
|
|
|
Copyright © 2000, Ben Okopnik<BR>
|
|
Published in Issue 53 of <i>Linux Gazette</i>, May 2000</H5>
|
|
<!-- *** END copyright *** -->
|
|
|
|
<!--startcut ==========================================================-->
|
|
<!-- P --> <HR> <!-- P -->
|
|
<!-- A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue53/okopnik.html">
|
|
<FONT SIZE="+2"><EM>Talkback:</EM> Discuss this article with peers</FONT></A -->
|
|
<P>
|
|
<!-- *** BEGIN navbar *** -->
|
|
<A HREF="lamb.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A>
|
|
<IMG ALT=""
|
|
SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom" >
|
|
<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="../faq/index.html"><IMG ALT="[ FAQ ]"
|
|
SRC="./../gx/navbar/faq.jpg"WIDTH="62" 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/issue53/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
|
|
<IMG ALT=""
|
|
SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom" >
|
|
<A HREF="sipos.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A>
|
|
<!-- *** END navbar *** -->
|
|
</BODY></HTML>
|
|
<!--endcut ============================================================-->
|