Create a script

  Linux中有好多中不同的shell,但是通常我们使用bash (bourne again shell) 进行shell编程,因为bash是免费的并且很容易使用。所以在本文中笔者所提供的脚本都是使用bash(但是在大多数情况下,这些脚本同样可以在 bash的大姐,bourne shell中运行)。

如同其他语言一样,通过我们使用任意一种文字编辑器,比如nedit、kedit、emacs、vi

等来编写我们的shell程序。

程序必须以下面的行开始(必须方在文件的第一行):

#!/bin/sh

符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。

当编辑好脚本时,如果要执行该脚本,还必须使其可执行。

要使脚本可执行:

chmod +x filename

然后,您可以通过输入: ./filename 来执行您的脚本。

注释

在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。

变量

在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写:

变量名=值

取出变量值可以加一个美元符号($)在变量前面:

#!/bin/sh

#对变量赋值:

a=”hello world”

# 现在打印变量a的内容:

echo “A is:”

echo $a

在您的编辑器中输入以上内容,然后将其保存为一个文件first。之后执行chmod +x first

使其可执行,最后输入./first执行该脚本。

这个脚本将会输出:

A is:

hello world

有时候变量名很容易与其他文字混淆,比如:

num=2

echo “this is the $numnd”

这并不会打印出”this is the 2nd”,而仅仅打印”this is the “,因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:

num=2

echo “this is the ${num}nd”

这将打印: this is the 2nd

有许多变量是系统自动设定的,这将在后面使用这些变量时进行讨论。

如果您需要处理数学表达式,那么您需要使用诸如expr等程序(见下面)。

除了一般的仅在程序内有效的shell变量以外,还有环境变量。由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。

Shell命令和流程控制

在shell脚本中可以使用三类命令:

1)Unix 命令:

虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。

常用命令语法及功能

echo “some text”: 将文字内容打印在屏幕上

ls: 文件列表

wc –l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数

cp sourcefile destfile: 文件拷贝

mv oldname newname : 重命名文件或移动文件

rm file: 删除文件

grep ‘pattern’ file: 在文件内搜索字符串比如:grep ‘searchstring’ file.txt

cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,这是两个完全不同的命令

cat file.txt: 输出文件内容到标准输出设备(屏幕)上

file somefile: 得到文件类型

read var: 提示用户输入,并将输入赋值给变量

sort file.txt: 对file.txt文件中的行进行排序

uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq

expr: 进行数学运算Example: add 2 and 3expr 2 “+” 3

find: 搜索文件比如:根据文件名搜索find . -name filename -print

tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile

basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux

dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin

head file: 打印文本文件开头几行

tail file : 打印文本文件末尾几行

sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus 替换为 LinuxFocus :cat text.file | sed ‘s/linuxfocus/LinuxFocus/’ > newtext.file

awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。cat file.txt | awk -F, ‘{print $1 “,” $3 }’这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下: Adam Bor, 34, IndiaKerry Miller, 22, USA命令输出结果为:Adam Bor, IndiaKerry Miller, USA

2) 概念: 管道, 重定向和 backtick

这些不是系统命令,但是他们真的很重要。

管道 (|) 将一个命令的输出作为另外一个命令的输入。

grep “hello” file.txt | wc -l

在file.txt中搜索包含有”hello”的行并计算其行数。

在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。

重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。

> 写入文件并覆盖旧文件

>> 加到文件的尾部,保留旧文件内容。

反短斜线

使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。

命令:

find . -mtime -1 -type f -print

用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本:

#!/bin/sh

# The ticks are backticks (`) not normal quotes (‘):

tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`

3) 流程控制

“if” 表达式 如果条件为真则执行then后面的部分:

if ….; then

….

elif ….; then

….

else

….

fi

大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等…

通常用” [ ] “来表示条件测试。注意这里的空格很重要。要确保方括号的空格。

[ -f “somefile” ] :判断是否是一个文件

[ -x “/bin/ls” ] :判断/bin/ls是否存在并有可执行权限

[ -n “$var” ] :判断$var变量是否有值

[ “$a” = “$b” ] :判断$a和$b是否相等

执行man test可以查看所有测试表达式可以比较和判断的类型。

直接执行以下脚本:

#!/bin/sh

if [ “$SHELL” = “/bin/bash” ]; then

echo “your login shell is the bash (bourne again shell)”

else

echo “your login shell is not bash but $SHELL”

fi

变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。

快捷操作符

熟悉C语言的朋友可能会很喜欢下面的表达式:

[ -f “/etc/shadow” ] && echo “This computer uses shadow passwors”

这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印” This computer uses shadow passwors”。同样或操作(||)在shell编程中也是可用的。这里有个例子:

#!/bin/sh

mailfolder=/var/spool/mail/james

[ -r “$mailfolder” ]’ ‘{ echo “Can not read $mailfolder” ; exit 1; }

echo “$mailfolder has mail from:”

grep “^From ” $mailfolder

该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的”From” 一行。如果不可读则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令:

-打印错误信息

-退出程序

我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。

不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。

case表达式可以用来匹配一个给定的字符串,而不是数字。

case … in

…) do something here ;;

esac

让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如:

file lf.gz

这将返回:

lf.gz: gzip compressed data, deflated, original filename,

last modified: Mon Aug 27 23:09:18 2001, os: Unix

我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件:

#!/bin/sh

ftype=`file “$1″`

case “$ftype” in

“$1: Zip archive”*)

unzip “$1” ;;

“$1: gzip compressed”*)

gunzip “$1” ;;

“$1: bzip2 compressed”*)

bunzip2 “$1” ;;

*) error “File $1 can not be uncompressed with smartzip”;;

esac

您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。也就是说,当我们运行:

smartzip articles.zip

$1 就是字符串 articles.zip

select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。

select var in … ; do

break

done

…. now $var can be used ….

下面是一个例子:

#!/bin/sh

echo “What is your favourite OS?”

select var in “Linux” “Gnu Hurd” “Free BSD” “Other”; do

break

done

echo “You have selected $var”

下面是该脚本运行的结果:

What is your favourite OS?

1) Linux

2) Gnu Hurd

3) Free BSD

4) Other

#? 1

You have selected Linux

您也可以在shell中使用如下的loop表达式:

while …; do

….

done

while-loop 将运行直到表达式测试为真。will run while the expression that we test for is true. 关键字”break” 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。

for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量:

for var in ….; do

….

done

在下面的例子中,将分别打印ABC到屏幕上:

复制代码 代码如下:

#!/bin/sh

for var in A B C ; do

echo “var is $var”

done

Here is a more useful script, showrpm, which prints statistics for some RPM packages: /bin/sh # list a content summary of a number of RPM packages # USAGE: showrpm rpmfile1 rpmfile2 … # EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm for rpmpackage in $*; do if [ -r “$rpmpackage” ]; then echo “=============== $rpmpackage ==============” rpm -qi -p $rpmpackage else echo “ERROR: cannot read file $rpmpackage” fi done

Here comes the second special variable, $*, which contains all the input command-line argument values. If you run the showrpm openssh. RPM w3m. RPM webgrep. RPM command, $* contains three strings: openssh. RPM, w3m. RPM and webgrep. The program extends wildcards and variables before passing any arguments to the program. By extension, I mean that the program replaces wildcard characters (such as *) with appropriate file names and variables with variable values. To prevent programs from making this substitution, you can use quotation marks: Let’s take a look at an example. Suppose you have some files in the current directory, two JPG files, mail.jpg and tux.jpg.

#! /bin/sh echo *.jpg This will print the result of “mail.jpg tux.jpg”. Quotes (single and double) will prevent this wildcard extension: #! /bin/sh echo “*.jpg” echo ‘*.jpg’ This will print “*.jpg” twice. Single quotes are more strict. It prevents any variable expansion. Double quotes prevent wildcard extensions but allow variable extensions. #! /bin/sh echo $SHELL echo “$SHELL” echo ‘$SHELL’ echo “/bin/bash /bin/bash $SHELL” JPG echo $SHELL This will print: *.jpg $SHELL Here Documents Here documents is a great way to pass several lines of text to a command. It’s useful to write a helpful paragraph for each script, so if we have that here documents, we don’t have to use the echo function to print it line by line. A “Here document” begins with << and is followed by a string that must also appear at the end of the Here document. Here is an example where we rename multiple files and print help using here Documents: copy the code as follows: #! /bin/sh # we have less than 3 arguments. Print the help text: if [ $# -lt 3 ] ; then cat < ren — renames a number of files using sed regular expressions USAGE: ren ‘regexp’ ‘replacement’ files… EXAMPLE: rename all *.HTM files in *.html: ren ‘HTM$’ ‘html’ *.HTM HELP exit 0 fi OLD=”$1″ NEW=”$2″ # The shift command removes one argument from the list of # command line arguments. shift shift # $* contains now all the files: for file in $*; do if [ -f “$file” ] ; then newfile=`echo “$file” | sed “s/${OLD}/${NEW}/g”` if [ -f “$newfile” ]; then echo “ERROR: $newfile exists already” else echo “renaming $file to $newfile …” mv “$file” “$newfile” fi fi done

This is a more complicated example. Let’s discuss it in detail. The first if expression determines whether the input command line arguments are less than three (the special variable $# indicates the number of arguments contained). If the input parameters are less than three, the help text is passed to the CAT command, which prints it to the screen. The program exits after printing the help text. If the input parameters are three or more, we assign the first parameter to the variable OLD and the second parameter to the variable NEW. Next, we use the shift command to remove the first and second arguments from the argument list, so that the third argument becomes the first argument in the argument list $*. Then we begin the loop, where the list of command-line arguments is assigned to the variable $file one by one. We then determine if the file exists, and if so, search and replace with the sed command to generate a new file name. The result of the backslash command is then assigned to newfile. This is where we want to be: we have the old file name and the new file name. Then use the mv command to rename it. Functions If you write a slightly more complex program, you’ll find that you probably use the same code in several places in your program, and you’ll find it much easier if we use functions. A function looks like this: functionname() { # inside the body $1 is the first argument given to the function # $2 the second … body }

You need to declare functions at the beginning of each program.

Here is a script called Xtitlebar, with which you can change the name of the terminal window. A function called help is used here. As you can see, this defined function is used twice. Copy the following code: #! /bin/sh # vim: set sw=4 ts=4 et: help() { cat < xtitlebar — change the name of an xterm, gnome-terminal or kde konsole USAGE: xtitlebar [-h] “string_for_titelbar” OPTIONS: -h help text EXAMPLE: xtitlebar “cvs” HELP exit 0 } # in case of error or if -h is given we call the function help: [ -z “$1” ] && help [ “$1” = “-h” ] && help # send the escape sequence to change the xterm titelbar: echo -e “33]0; The $107”

It’s a good programming habit to help with scripts so that other users (and you) can use and understand them. Command line arguments we’ve seen $* and $1, $2… Special variables, such as $9, that contain parameters entered by the user from the command line. So far, we’ve only seen some simple command-line syntax (such as mandatory arguments and the -h option to view help). But as you write more complex programs, you may find that you need more customization options. The usual convention is to precede all optional arguments with a minus sign followed by the parameter value (such as the file name). There are many ways to analyze input parameters, but the following example using case expressions is definitely a good one. Copy the following code: #! /bin/sh help() { cat < This is a generic command line parser demo. USAGE EXAMPLE: cmdparser -l hello -f — -somefile1 somefile2 HELP exit 0 } while [ -n “$1” ]; do case $1 in -h) help; shift 1;; # function help is called -f) opt_f=1; shift 1;; # variable opt_f is set -l) opt_l=$2; shift 2;; # -l takes an argument -> shift by 2 –) shift; break;; # end of options -*) echo “error: no such option $1. -h for help”; exit 1;; *) break;; esac done

echo “opt_f is $opt_f”

echo “opt_l is $opt_l”

echo “first arg is $1”

echo “2nd arg is $2”

You can run the script as follows: cmdparser -l hello -f — somefile1 somefile2 Returns: Opt_f is 1 opt_l is hello first arg is-somefile1 2nd arg is somefile2 How does this script work? The script starts by looping through all the input command line arguments, comparing the input arguments to the case expression, setting a variable and removing the argument if they match. By convention on Unix systems, the first input should be a parameter containing a minus sign. Example General programming Steps Now let’s discuss the general steps of writing a script. Any good script should have help and input parameters. And it’s a good idea to write a pseudo-script (framework.sh) that contains the framework structure that most scripts need. In this case, to write a new script we just need to copy: cp framework.sh myscript and then insert our own function. Let’s look at two more examples: the binary-to-decimal conversion script B2D converts a binary number (say, 1101) to the corresponding decimal number. This is another example of using the expr command to do the math: /bin/sh # vim: set sw=4 ts=4 et: help() { cat < b2h — convert binary to decimal USAGE: b2h [-h] binarynum OPTIONS: -h help text EXAMPLE: b2h 111010 will return 58 HELP exit 0 } error() { # print an error and exit echo “$1” exit 1 } lastchar() { # return the last character of a string in $rval if [ -z “$1″ ]; then # empty string rval=”” return fi # wc puts some space behind the output this is why we need sed: numofchar=`echo -n “$1” | wc -c | sed ‘s/ //g’ ` # now cut out the last char rval=`echo -n “$1” | cut -b $numofchar` }

chop() { # remove the last character in string and return it in $rval if [ -z “$1″ ]; then # empty string rval=”” return fi # wc puts some space behind the output this is why we need sed: numofchar=`echo -n “$1” | wc -c | sed ‘s/ //g’ ` if [ “$numofchar” = “1” ]; then # only one char in string rval=”” return fi numofcharminus1=`expr $numofchar “-” 1` # now cut all but the last char: rval=`echo -n “$1” | cut -b 0-${numofcharminus1}` } while [ -n “$1” ]; do case $1 in -h) help; shift 1;; # function help is called –) shift; break;; # end of options -*) error “error: no such option $1. -h for help”;; *) break;; esac done # The main program sum=0 weight=1 # one arg must be given: [ -z “$1″ ] && help binnum=”$1″ binnumorig=”$1”

while [ -n “$binnum” ]; do lastchar “$binnum” if [ “$rval” = “1” ]; then sum=`expr “$weight” “+” “$sum”` fi # remove the last position in $binnum chop “$binnum” binnum=”$rval” weight=`expr “$weight” “*” 2` done echo “binary $binnumorig is decimal $sum” #

The script uses an algorithm that utilizes both decimal and binary weights (1,2,4,8,16,..) For example, binary “10” can be converted to decimal as follows: 0 * 1 + 1 * 2 = 2 To get a single binary number we use lastchar. This function counts the number of characters using WC -c, and then fetches the last character using the cut command. The Chop function removes the last character. File looper You might be one of those people who want to save all outgoing mail in one file, but after a few months, the file can get so large that access to it slows down. The following script, Rotatefile, solves this problem. This script can rename the mail save file (let’s say outmail) to outmail.1, and for outmail.1 to outmail.2, etc… Copy the following code: #! /bin/sh # vim: set sw=4 ts=4 et: ver=”0.1″ help() {cat < rotatefile — rotate the file name

USAGE: rotatefile [-h] filename

OPTIONS: -h help text

EXAMPLE: rotatefile out

This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1

and create an empty out-file

The max number is 10

version $ver

HELP

exit 0

}

error() { echo “$1” exit 1 } while [ -n “$1” ]; do case $1 in -h) help; shift 1;; –) break;; -*) echo “error: no such option $1. -h for help”; exit 1;; *) break;; esac done # input check: if [ -z “$1” ] ; then error “ERROR: you must specify a file, use -h for help” fi filen=”$1″ # rename any .1 , .2 etc file: for n in 9 8 7 6 5 4 3 2 1; do if [ -f “$filen.$n” ]; then p=`expr $n + 1` echo “mv $filen.$n $filen.$p” mv $filen.$n $filen.$p fi done # rename the original file: if [ -f “$filen” ]; then echo “mv $filen $filen.1” mv $filen $filen.1 fi echo touch $filen touch $filen

How does this script work? After detecting that the user has provided a filename, we do a 9 through 1 loop. File 9 is named 10, file 8 is renamed 9, and so on. After the loop is complete, we name the original file file 1 and create an empty file with the same name as the original file. Debugging The simplest debugging command is, of course, to use the echo command. You can use echo to print any variable value anywhere you suspect something has gone wrong. This is why most shell programmers spend 80% of their time debugging. The nice thing about Shell programs is that they don’t need to be recompiled, and it doesn’t take much time to insert an echo command. The shell also has a real debug mode. If there is an error in the script” Strangescript “, you can debug it by saying: sh -x Strangescript This will execute the script and display the values of all variables. The shell also has a mode where you don’t need to execute a script, just check the syntax. You can use it like this: sh -n your_script This will return all syntax errors. We hope you can start writing your own shell scripts now, and have fun. \