It seems that one of the skills required of an actor is the ability to memorize a script. I had assumed that learning a script is of greater importance to stage actors than film actors, because stage actors must deliver their lines and follow their stage directions correctly the first time. However, acquaintances that are in the business assure me scripts play a central role in both types of acting.
It seems that one of the skills required of an actor is the ability to memorize a script. I had assumed that learning a script is of greater importance to stage actors than film actors, because stage actors must deliver their lines and follow their stage directions correctly the first time. However, acquaintances that are in the business assure me scripts play a central role in both types of acting.
Scripts of a somewhat different kind are important to Linux and Unix users. Scripts (sometimes referred to as “shell scripts,” since the scripting language is built into the shell) let you teach a computer new commands of your own design. By constructing scripts that perform commonly used operations, you can reduce the tedium and effort these operations require. A thorough knowledge of scripting is one of the keystones of Linux mastery.
A First Script
With all the copyright disputes that have arisen lately, I’m surprised nobody has commented on Burger King’s ownership of the slogan “Have It Your Way.” That’s been the slogan of Linux and Unix developers since the beginning; if you don’t like how your system works, write a script to make it work your way.
For instance, suppose you often use the w command to see which users are logged into a host (see Figure One).
Figure One: Output of the w Command
[bmccarty@home bmccarty]$ w
10:06am up 149 days, 22:01, 38 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
dglyer pts/0 faedhcp117 Thu10am 23:54m 0.73s 0.01s /usr/local/bin/pdme
sniper86 pts/25 – 10:05am 1.00s 0.07s 0.05s vi . te/aliases.irc
enigma pts/22 – 7:33am 35:18 60.41s 60.41s irc -c#tech enigma
philpi pts/31 – 9:13am 5:34 0.30s 0.30s pine
mccartyp pts/21 – 9:55am 11:02 48.52s 48.51s pine -i
enigma pts/1 – 7:33am 35:40 1.16s 1.13s rc -c#linux enigma
etc.
|
It’s difficult to see if a specific user is logged on, because the w command prints its output in no particular order. By writing a script, however, you can change this behavior. Using a text editor, create a file named users with the following contents:
Now, execute the file by issuing the following command:
The output will now resemble Figure Two (pg. 20), in which lines appear in an order governed by the user name.
Figure Two: Sorted Output of the w Command
[bmccarty@home bmccarty]$ w
10:06am up 149 days, 22:01, 38 users, load average: 0.00, 0.00, 0.00
dglyer pts/0 faedhcp117 Thu10am 23:54m 0.73s 0.01s /usr/local/bin/pdme
enigma pts/22 – 7:33am 35:18 60.41s 60.41s irc -c#tech enigma
enigma pts/1 – 7:33am 35:40 1.16s 1.13s irc -c#linux enigma
mccartyp pts/21 – 9:55am 11:02 48.52s 48.51s pine -i
philpi pts/31 – 9:13am 5:34 0.30s 0.30s pine
sniper86 pts/25 – 10:05am 1.00s 0.07s 0.05s vi .te/aliases.irc
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
|
Okay, the heading line appears last in the output, but we’ll fix that in a moment. First, let’s look at how the script works. Essentially, typing the command shusers is no different than typing the command contained in the file w | sort. This command executes the w subcommand and then “pipes,” or sends, the w command’s output to the sort subcommand, which alphabetizes the w command’s output.
You may reasonably object on the grounds that it’s almost as convenient to type w | sort as it is to type sh users. Fair enough; let’s fix that. Issue the following command:
Now you can obtain the sorted listing by merely typing the command ./users. That’s easier than typing w | sort, isn’t it? Moreover, if you put the users file in a directory that’s contained in your PATH environment variable, you can simply type users.
If this hasn’t convinced you of the value of scripts, consider that scripts can contain multiple commands. Let’s demonstrate by moving the pesky heading line from our last example to the top of the output, where it belongs. To do this, we simply modify our users script to have the contents shown in Figure Three.
Figure Three: Fixing the Heading
echo “USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT”
w -h | sort
|
If you execute this script, you’ll see output that looks just like the original output of the w command, but the output appears in sorted order. In particular, the heading appears at the top of the output. How is this done? The echo command displays the desired heading, and the -h flag of the w command suppresses the heading that command otherwise displays.
Why stop here? Let’s continue by creating a script that provides still more useful output.
Consider Figure Four. This supercharged descendant of the virile, yet undistinguished, w command displays the total number of current users as the final line of its output. This is accomplished by using the echo command to display an appropriate label and then using the w command a second time, piping its output to the wc command, which counts and displays the number of lines in its input. Since the wc command receives one line of input for each user, the result is a count of logged-in users. As you can see, Linux is truly a “Have It Your Way” operating system.
Figure Four: Counting the Total Users
echo “USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT”
w -h | sort
echo -n “Total current users: ”
w -h | wc -l
|
Script Arguments
In the entertainment business, script arguments are bad news, because they can result in the loss of millions of dollars. However, in the Linux world, script arguments are good news, because they increase the flexibility of scripts.
In the context of Linux, the word argument doesn’t refer to a dispute. Instead, it refers to a value that modifies the operation of a script, allowing you to create more flexible scripts. Suppose you are interested in running the w command in order to see if your friend, whose userid is sniper86, is logged on right now. The script in Figure Five would do the job.
Figure Five: Finding a Specific User
echo “USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT”
w | grep sniper86
|
As before, the w command lists the logged-on users. Here, the grep command filters the resulting output, discarding all lines that don’t contain the text sniper86.
If you placed this script in the file friend, you could conveniently check to see if sniper86 is logged on. But suppose you have two friends. You could create a second file, perhaps named friend2, to check whether your other friend was logged on. However, if you are sociable and have many friends, this approach quickly becomes tedious. Fortunately, there’s a better way.
Consider Figure Six (pg. 20). Instead of containing the userid of one of your friends, this script contains the shell variable $1, which stands for the first argument provided by the user when the script is run. To see what this means, execute the script as follows:
Figure Six: Finding Any User
echo “USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT”
w | grep $1
|
The command argument sniper86 is bound to the shell variable $1 so that the script behaves as though its second line was w | grepsniper86 rather than w|grep $1.
What’s cool about using an argument is that you can invoke the script in a nearly infinite variety of ways, accommodating the possibility of a practically infinite number of friends. For example, you can check whether enigma is logged on by issuing the command friendenigma. No longer is a special version of the script required for each of your friends.
To Quote or Not to Quote
When arguments can consist of multiple words, script authoring can become a tad tricky. For example, consider the data file shown in Figure Seven. This file, named contacts, contains e-mail addresses and names of Linux Magazine contacts.
Figure Seven: The Contacts File
|
To help you search the file, you might choose to create a script named findcontact, resembling the following:
Better yet, you might include the -i flag so that grep ignores case distinctions. Also, you might use an absolute path to specify the location of the contacts file so that using the findcontacts command doesn’t require you to change the current working directory to the one containing the contacts file:
grep -i $1 /root/contacts
To search for a contact, you might issue a command such as this:
The command displays the line, or lines, in the contacts file that contain the text bill, without regard to case. In this example, it would display the line showing the e-mail address of Bill McCarty.
Figure Eight: The Revised Contacts File
|
That’s well and good, but suppose that someone with the name of Bill McMillan joins the Linux Magazine team, as shown in Figure Eight. Now the findcontact bill command yields two lines of output rather than a single line. Again, that’s well enough. However, suppose you’re interested only in the contact information for the newcomer, and so you issue the following command:
findcontact Bill McMillan
To our disappointment, the command will display two lines of output — the line for Bill McCarty and the line for Bill McMillan. The reason for this is that you’ve actually sent two arguments to the findcontacts command — Bill and McMillan. However, the script will only accept the first argument ($1). That is not exactly having it our way, is it?
To overcome the problem, you might attempt to enclose the arguments in quotes, converting them to a single argument consisting of two words:
findcontact “Bill McMillan”
Unfortunately, this fix doesn’t go quite far enough. When the shell variable $1 in the line
grep -i $1 /root/contacts
is replaced by “Bill McMillan”, the script operates as though the line had been written as
grep -i Bill McCarty /root/contacts
because the shell drops the surrounding quotes when it substitutes the value of the $1 variable for its name. The result is that the grep searches the files McCarty and /root/contacts for the text Bill. Of course, the file McCarty probably does not exist, so the output consists of both lines from /root/contacts containing the text Bill. This is not what’s wanted.
Fortunately, the fix is simple; both the command-line argument and the script itself must be properly quoted. Write the script like this:
And, here’s how to execute it:
Although it required some ingenuity to get here, the script now behaves as desired. Linux’s reputation as a have-it-your-way operating system is secure.
The Long and Winding Road
This column, as usual, has scarcely touched the surface of its topic, shell scripts. Among the most useful shell features not covered are multiple arguments, more sophisticated rules and methods of quoting arguments, environment variables, conditional execution, looping, backquoted expressions, and signals. Future Newbies columns will touch on these stepping-stones to Linux mastery.
The topical development of this column was inspired by Chapter Three of my favorite Unix book, Brian W. Kernighan’s and Rob Pike’s The Unix Programming Environment (Prentice-Hall, 1984). If you’re rushing down the road to Linux gurudom, you need to get and read this book. It’s a terse, and therefore somewhat tough, read that demands concentrated attention, but your efforts will be rewarded many times over. Until next month, happy scripting!
As everyone knows, the real-life actors we mentioned at the beginning of this article often have trouble finding a good script. In fact, some actors have been known to resort to writing scripts especially designed to showcase their talents. Okay, so maybe we’re stretching the actor analogy a bit thin here, but the point is that you too can write your own scripts. However, to be able to write effective scripts, you’ll need to employ a wide-ranging repertoire of Linux commands. The table below summarizes some Linux commands that are often used in scripts. Consult the man pages or other documentation to learn more about these commands.
Important Commands Often Used in Scripts
at | Execute a command at a specified time |
cat | Display contents of a file |
chmod | Change file permissions |
comm | Compare files |
cp | Copy files |
df | Display disk space usage of filesystems |
diff | Compare text files |
du | Displays disk space usage of directories |
echo | Display text |
eval | Evaluate an expression |
grep | Find strings |
kill | Terminate a process |
lpr | Print data |
less | Page through a file |
ls | List file information |
mail | Send a mail message |
mkdir | Create a directory |
mv | Move a file or directory |
nice | Set process priority |
nohup | Run a command immune to hang-ups |
od | Display file contents in octal and other formats |
passwd | Set a user’s password |
pr | Format data for printing |
ps | Display process information |
rm | Remove a file |
rmdir | Remove a directory |
sh | Launch a shell |
sleep | Pause a process |
sort | Sort data |
spell | Check spelling |
tail | Display last lines of file |
time | Time the execution of a script |
times | Displays CPU time used |
uniq | Eliminates duplicate data |
w | Displays logged in users |
wait | Waits for child processes to terminate |
wc | Counts characters, words, and lines |
who | Displays logged in users |
|
Bill McCarty is an associate professor at Azusa Pacific University. He can be reached at bmccarty@apu.edu.