9 Linux Scripting worksheet

advertisement
Ted Deacon
Linux Worksheet 4
Tools and Scripts
Learning about scripting will save you time and effort. In the past people needed to interrogate files to automate pulling bits
of information out the files. We are using bash scripts to do this having a working knowledge of this will help you save having
to do things the long way. Work through these examples and note what the outcome is this will help you when you are
required to something similar.
Redirecting output
Hopefully there's no news in the idea that you can redirect the output of a command into a file, this trivial example sends
the output of the command to list the contents of the /etc/ directory into a file called etc.list
ls /etc/ > etc.list
If the file etc.list does not already exist it is created, if however it does exist, the file is overwritten.
If we use a double angle bracket we can append to the output to a file.
date > etc.list
ls /etc/ >> etc.list
The first line creates, or overwrites the existing, etc.list file so that it contains on the current date.
The second line appends the result of the command to list /etc to the file.
We can also use the angle-brackets syntax to input data into a command from a file,
mail -s “Linux Rocks” w.gates@microsoft.com < Linux_Rocks.txt
Here we send an email to Mr Gates, the body of the message will be the contents of the file called Linux_Rocks.txt
Pipes
It should also come as no surprise that we can pipe the output of one command into another,
ls -l /usr/bin | more
The output from listing the /usr/bin directory is piped into the command more, which formats the output so that it displays
one screenful at a time.
Although this might be considered a trivial example, there are some 1870 files 1 in /usr/bin – check it out with ls /usr/bin |
wc -w which will give you a word count of the number of files.
grep
We met grep during several of the previous sessions, you will doubtless recall we used it to check the pids of various
processes,
ps -aux | grep inetd
Ted Deacon
Linux Worksheet 4
The command works by running ps aux -which returns a list of all the processes running on the system and piping the output
through grep which returns only those lines containing the expression inetd.
This combination of commands often produces two lines of output,
root@riktor:~$ ps -aux | grep inetd
80 ?
S
0:00 /usr/sbin/inetd
193 pts/0
S
0:00 grep inetd
To remove the second line of output we could pipe the output through grep again, but this time with the -v switch which
returns lines not matching a given expression,
root@riktor:~$ ps -aux | grep inetd | grep -v grep
80 ?
S
0:00 /usr/sbin/inetd
This may seem like a lot of bother to go to just to exclude one line of a two-line output, but it comes into its own inside a
script where the system would lack the discretion to exclude the irrelevant line of output.
We can also use grep on a file,
root@riktor:/tmp# grep student /etc/passwd
student:x:1000:100:,,,:/home/student:/bin/bash
dopey::0:0:students dodgy root shell:/root:/bin/bash
horrid::0:0:another dodgy root shell by student:/root:/bin/bash
which returns any line that contains “student”.
The -w switch for grep will return all lines that match the exact word specified,
root@riktor:/tmp# grep -w student /etc/passwd
student:x:1000:100:,,,:/home/student:/bin/bash
horrid::0:0:another dodgy root shell by student:/root:/bin/bash
But, we still get a line returned with “student” in the user details section, we can use a caret ( ^ ) to make sure we only get
the lines with an expression at the beginning of the line.
root@riktor:/tmp# grep ^student /etc/passwd
student:x:1000:100:,,,:/home/student:/bin/bash
To match a pattern at the end of a line use the syntax <pattern>$ with grep
grep bash$ /etc/passwd
which will return all the users with a shell of /bin/bash from the password file.
We can also use grep on a whole directory,
root@riktor:~$ grep -r sendmail /etc/rc.d/*
/etc/rc.d/rc.M:# Start the sendmail daemon:
/etc/rc.d/rc.M:if [ -x /etc/rc.d/rc.sendmail ]; then
/etc/rc.d/rc.M: . /etc/rc.d/rc.sendmail start
Which returns all the lines from any of the files in /etc/rc.d/ that contain the expression “sendmail.” Note the “-r” switch
Ted Deacon
Linux Worksheet 4
which tells grep to recurse through any subdirectories, it is, of course, redundant in this example as there no subdirectories
beneath rc.d/ in Slackware
Note also that the name of the file where the results were found is also shown in the output.
sed
We are all familiar with the idea of editing a file in a text editor, but how about editing it on the fly as a stream? Sed is the
stream editor, it usually takes lines of text from standard input and passes them to standard output but performing editing
actions on the way. But it can also take input from a file of from the output of another command,
sed 's/^telnet/#telnet/' /etc/inetd.conf
in this example sed is instructed to substitute any occurrence of telnet at the beginning of a line with #telnet thus
commenting out the telnet line in inetd.conf.
This example would not disable telnet by itself but it could be used as part of a script to perform that task.
As another example, perhaps I just wrote a PHP script and managed to declare my variable “counter” (spelled as it is there
with a lowercase 'c' , but refer to it throughout the remainder of the script with an uppercase “C” as in Counter.
sed 's/Counter/counter/g' counter.php > counter.new
the switch 'g' makes sure that sed replaces all occurrences of Counter, even if there is more than one on a line. The output is
directed into the file counter.new
I would then use cp counter.new counter.php to replace my file with the corrected version.
cut
Cut is used to extract columns or fields from a file.
The fields in the passwd file are delimited by a colon “:” for example,
student:x:1000:100:,,,:/home/student:/bin/bash
the fields are as follows,
1.
2.
3.
4.
5.
6.
7.
user name
Password required
user id (uid)
group id (gid)
“personal” details – full name, room number, telephone extension
path to user's home directory
path to user's shell
To retrieve a list of just the user names from /etc/passwd we could use the following cut command,
root@riktor:~# cut -f1 -d: /etc/passwd
root
.
.
.
nobody
student
relicnet
student
Ted Deacon
Linux Worksheet 4
The arguments to cut are,
-f1
- the first field
-d:
- the delimiter – in this case the colon.
tr
This is the command to translate or delete particular characters from standard input, the results are written to standard
output.
root@riktor:~# echo "this that and the other" | tr " " _
this_that_and_the_other
echo is a command that simply repeats whatever follows, in this example it is piped into tr which translates all instances of a
space ( “ “ ) into an underscore “_” . (Useful when converting Windows style file names with spaces into something more
sensible ;-)
sort
Sort does exactly what it says on the tin, it sorts the lines of text in a file into order. sort -r will sort the lines into reverse
order. Try it out on something simple like the .rhosts file.
If you need to sort the contents of a file and direct it back into the same file use the -o switch,
sort .rhosts -o .rhosts
do not just do this, sort .rhosts > .rhosts - you will lose the entire contents of the file.
uniq
uniq will remove duplicate entries from a sorted file. Consider my clumsily constructed .rhosts file,
root@riktor:~# cat .rhosts
weatherwax
riktor
magrat
petulia
weatherwax
petulia
If we sort the file and pipe the output through uniq the result is a sorted .rhosts with no duplicate entries,
root@riktor:~# sort -r .rhosts | uniq
weatherwax
riktor
petulia
magrat
(If we just run uniq on the file before it is sorted, it will fail to detect and remove the duplicate entries.)
Compressed Archives
Where Windows users have WinZip (and more recently Windows' own built in compression application,) Linux users have
gzip to compress files and archives and gunzip to decompress them.
However it is usual for the files also to be “tarred.” Tar is the GNU version of the tape archiving facility. According to the man
Ted Deacon
Linux Worksheet 4
page for tar it is,
“an archiving program designed to store and extract files from an archive file known as a tarfile. A tarfile may
be made on a tape drive, however, it is also common to write a tarfile to a normal file.”
Tarred and gzipped files are often referred to as a tarball. It is common, for a whole directory, (including any sub-directories,)
to be tarred and gzipped into one archive, - when it is decompressed the original directory structure is recreated.
A tarball will usually have the file extension .tar.gz or .tgz 2
Creating a tarball
To create a tarball of the all the files in your current directory in the file 2013-11-22-myfiles.tgz we would use,
tar -zcpf
2013-11-22-myfiles.tgz *
The options passed to tar are as follows,




z – pass the archive through gzip to compress it
c – create a new archive
p – preserve file permissions and ownership
f – specifies that the next argument is the file name to use for the new file

the final argument is the directory from which to create the archive.
Unpacking a tarball
To unpack the tarball you created,
tar -zxf
2013-11-22-myfiles.tgz
The options passed to tar are as follows,



z – pass the archive through gzip to compress it
x – extract files from the archive
f – specifies that the next argument is the file to be unpacked
Viewing the list of files in an archive
This will simply list the paths/filenames of the files in the archive.
tar -ztf
2013-11-22-myfiles.tgz
cron jobs
crond is the Unix clock daemon that executes commands at specified dates and times according to instructions in a
"crontab" file.
The entries in a crontab file follow this pattern,
2
min
hour
day
10
8
*
month
*
dayofweek
*
command
myscript
You may also come across .zip and .bzip as file extensions, there are usually zip / unzip and bzip / bunzip applications on the Linux system
to handle them.
Ted Deacon
Linux Worksheet 4
which runs myscript at 08:10 every day.
10 8 * * mon myscript
this would run myscript every Monday at 08:10
*/20 * * * * my_other_script
runs my_other_script every twenty minutes, always
date
The Unix / Linux date command sets or prints the date and time from the system clock,
student@weatherwax:~$ date
Sun Dec 7 14:29:21 GMT 2013
Date will also output different formats depending on the options passed, such as
%B – full month name
%d – day of month (01..31)
%m – month (01..12)
%Y – year (1970...)
%s – seconds since 00:00:00, Jan 1, 1970 – Unix time
For example,
student@weatherwax:~$ date +%d.%m.%Y
07.12.2013
Variables
In addition to using the built in commands and utilities provided by the shell, shell scripts may also have variables, control
structures, arrays and many of the other features one might expect of a programming language. (But not the complex
programming features such as objects, multi-dimensional arrays, linked lists.... if you need those it's probably easier to use a
different language, like C,C++, Java, PHP, or Perl)
Variables are declared and assigned values with the syntax, variable_name=value
and they are referenced with, $variable_name
#!/bin/sh
#
msg="hello :-)"
echo
echo $msg
Variables may also be the result of the evaluation of an expression,
#!/bin/sh
Ted Deacon
Linux Worksheet 4
#
dt=`date +%d-%m-%Y`
echo
echo $dt
Note the back quotes ` ` around the date expression which cause it to be evaluated in its entirety before its value is assigned
to the dt variable.
Note also that there are no spaces in either of the examples when assigning a value to a variable.
User Input
Variables may also be taken from user input,
#!/bin/sh
#
echo
echo -n “Please enter your name > “
read name
echo
echo “Hello $name pleased to meet you"
echo

echo -n prevents the output from moving to a new line, in this case it leaves the prompt right after the angle
bracket, waiting for the response.

read is the command that reads the input from the user, the script simply waits until the enter key is pressed, if
anything was entered at the prompt it will be assigned to the variable name.

The echo commands with no arguments simply print a blank line, they are used here to separate the script output
from the shell prompts intending to make the output (slightly?) easier to read.
if... then... else...
#!/bin/sh
#
hr=`date +%H`
period="Morning"
if [ $hr -ge 12 ]; then
period="Afternoon"
fi
if test $hr -ge 18 ; then
period="Evening"
fi
echo
echo “Good $period!"
echo


The hr variable is the current hour of the day, evaluated from an option to date as described earlier.
The period variable is initialised as “Morning” and is changed if either, (or indeed both,) of the following tests is
true.

Note the two different possibilities for the syntax of the if statement.
Ted Deacon

Linux Worksheet 4
The if statements are closed with fi
checking user input
#!/bin/sh
#
echo
echo -n “Would you like to see the fortune cookie (y/N) > “
read ans
if [ “$ans” = “y” ] || [ “$ans” == “Y” ]; then
echo
fortune -o
else
echo
echo “ok no fortune cookie"
fi
echo
 The " (y/N) " in the prompt is a convention used to suggest that “No” would be the default answer, ie. if you just hit
enter, and that you must explicitly answer “y” if you want to go ahead.
The script copes with the case of an answer of “Y” by testing for both “y” OR “Y” - note how “$ans” is also
enclosed in quotes in the test.

Contrary to many other languages, the comparison operators “ = “ and “ == ” both seem to work the same way
here.

The option -o for the fortune command prints a potentially offensive fortune cookie –

You have been warned.
loops
for loop
Bash does not provide quite the same syntax to control its for loops as some of the C-like languages, but we can get close
with,
for (( i=0; i<=10; i=$i+1 ))
do
echo $i
done
...or perhaps a little more neatly with, the seq command,
for i in `seq 10`
do
echo $i
done

In this example seq returns a list of numbers between 1 and 10 – the start point of 1 is the default. We could specify
start and end points with, for example, seq 1997 2013.

Backquotes to evaluate the expression as usual.
We can also use a hard-coded list as the control for the loop,
for i in this that and the other
do
echo $i
Ted Deacon
Linux Worksheet 4
done
or perhaps,
for i in this that “and the other”
do
echo $i
done

In the above examples everything after in is taken as an item in the list, the for loop will execute whatever
commands are specified for each item in the list.

In the second example “and the other” is taken as one single item.
We can also generate a list using other command(s),
for i in $(ls)
do
echo $i
done
the above example is the same as,
list=`ls`
for i in $list
do
echo $i
done
The difference between the two previous techniques is minimal at this level, however the second version may be easier to
use when there are more complex commands involved to generate the list.
while loop
With the exception of the slightly unusual bash syntax while loops operate much as you would expect,
declare -i counter=0
while [ $counter -lt 10 ]
do
echo $counter
counter=$counter+1
done


declare -i sets the type for the variable counter to integer
-lt in the test for the while loop means less than. Take care when attempting to use the 'usual' comparison
operators < and > in case bash thinks you are trying to read from a file or direct output into a file.
until loop
until loops work in much the same way as while loops except that they run until the test returns true,
declare -i counter=20
until [ $counter -lt 10 ]
do
echo $counter
#counter=$counter-1
Ted Deacon
Linux Worksheet 4
let counter-=1
done

Note the alternative syntax used to decrement the counter here. (we could equally have used
let counter+=1 in the previous example.)
Command line arguments
A shell script may also accept arguments on the command line,
#!/bin/sh
#
echo
echo “Name of script:
$0“
echo
echo “Hello $1“
echo “you put $# arguments on the command line"
echo
echo “here is a list of them:
$*"
echo
echo “and here they are one by one:"
count=1
for i in $*
do
echo “Argument $count was $i”
count=$[count+1]
done





The arguments are accessed by the variables $0 to $n where $0 is the name of the script itself.
$# returns the number of arguments supplied to the script
$* contains a list of all the arguments.
Note the syntax of the simple for loop that works through the list contained in $*
Note also the use of another alternative syntax to increment the counter, the concise and convenient “counter++”
syntax of C-like languages is not available in bash!
Working example
Finally here's an example of a script that actually does something useful and that I used in anger recently – I received some
ninety-odd picture files to be uploaded to a web site, my client had created them on a windows box and of course had put
spaces in the names. I'm not saying that it was quicker to write this than rename them by hand – but it was most definitely
more fun to do it this way – and it will be quicker next time ! ;-)
#!/bin/sh
#
#mvg.16.09.2013.fix-space-name remove spaces from *@$!^¬* windows file names
#
#translate spaces in filenames into "_" so that $file_list contains
#a space separated list of files
# gives an error if NO files have spaces in the name
file_list=`ls | tr "\ " _`
#
for i in $file_list
do
j=`echo $i | tr _ "\ "`
#get "real" filename (with spaces)
if echo $j | grep " " >/dev/null; then
#if there's a space in the name
Ted Deacon
mv "$j" $i
echo "Renaming $j to $i"
fi
done
Linux Worksheet 4
#rename it with "_" instead of space
#quotes around $j otherwise sh thinks it's a list!
#confirm what's being done
##else don't bother
Download