- 论坛徽章:
- 0
|
最简单的例子 —— Hello World!
几乎所有的讲解编程的书给读者的第一个例子都是 Hello World 程序,那么我们今天也就从这个例子出发,来逐步了解 BASH。
用 vi 编辑器编辑一个 hello 文件如下:
#!/bin/bash
# This is a very simple example
echo Hello World
这样最简单的一个 BASH 程序就编写完了。这里有几个问题需要说明一下:
一,第一行的 #! 是什么意思
二,第一行的 /bin/bash 又是什么意思
三,第二行是注释吗
四,echo 语句
五,如何执行该程序
#! 是说明 hello 这个文件的类型的,有点类似于 Windows 系统下用不同文件后缀来表示不同文件类型的意思(但不相同)。linux 系统根据 "#!" 及该字串后面的信息确定该文件的类型,关于这一问题同学们回去以后可以通过 "man magic"命令 及 /usr/share/magic 文件来了解这方面的更多内容。在 BASH 中 第一行的 "#!" 及后面的 "/bin/bash" 就表明该文件是一个 BASH 程序,需要由 /bin 目录下的 bash 程序来解释执行。BASH 这个程序一般是存放在 /bin 目录下,如果你的 Linux 系统比较特别,bash 也有可能被存放在 /sbin 、/usr/local/bin 、/usr/bin 、/usr/sbin 或 /usr/local/sbin 这样的目录下;如果还找不到,你可以用 "locate bash" "find / -name bash 2> /dev/null" 或 "whereis bash" 这三个命令找出 bash 所在的位置;如果仍然找不到,那你可能需要自己动手安装一个 BASH 软件包了。
第二行的 "# This is a ..." 就是 BASH 程序的注释,在 BASH 程序中从“#”号(注意:后面紧接着是“!”号的除外)开始到行尾的多有部分均被看作是程序的注释。的三行的 echo 语句的功能是把 echo 后面的字符串输出到标准输出中去。由于 echo 后跟的是 "Hello World" 这个字符串,因此 "Hello World"这个字串就被显示在控制台终端的屏幕上了。需要注意的是 BASH 中的绝大多数语句结尾处都没有分号。
如何执行该程序呢?有两种方法:一种是显式制定 BASH 去执行:
$ bash hello 或
$ sh hello (这里 sh 是指向 bash 的一个链接,“lrwxrwxrwx 1 root root 4 Aug 20 05:41 /bin/sh -> bash”)
或者可以先将 hello 文件改为可以执行的文件,然后直接运行它,此时由于 hello 文件第一行的 "#! /bin/bash" 的作用,系统会自动用/bin/bash 程序去解释执行 hello 文件的:
$ chmod u+x hello
$ ./hello
此处没有直接 “$ hello”是因为当前目录不是当前用户可执行文件的默认目录,而将当前目录“.”设为默认目录是一个不安全的设置。
需要注意的是,BASH 程序被执行后,实际上 linux 系统是另外开设了一个进程来运行的。
变量和运算
我们先来从整体上把握一下 BASH 中变量的用法,然后再去分析 BASH 中变量使用与 C 语言中的不同。BASH 中的变量都是不能含有保留字,不能含有 "-" 等保留字符,也不能含有空格。
简单变量
在 BASH 中变量定义是不需要的,没有 "int i" 这样的定义过程。如果想用一个变量,只要他没有在前面被定义过,就直接可以用,当然你使用该变量的第一条语句应该是对他赋初值了,如果你不赋初值也没关系,只不过该变量是空( 注意:是 NULL,不是 0 )。不给变量赋初值虽然语法上不反对,但不是一个好的编程习惯。好了我们看看下面的例子:
首先用 vi 编辑下面这个文件 hello2:
#!/bin/bash
# give the initialize value to STR
STR="Hello World"
echo $STR
在上面这个程序中我们需要注意下面几点:
一,变量赋值时,'='左右两边都不能有空格;
二,BASH 中的语句结尾不需要分号(";");
三,除了在变量赋值和在FOR循环语句头中,BASH 中的变量使用必须在变量前加"$"符号,同学们可以将上面程序中第三行改为 "echo STR" 再试试,看看会出什么结果。
四,由于 BASH 程序是在一个新的进程中运行的,所以该程序中的变量定义和赋值不会改变其他进程或原始 Shell 中同名变量的值,也不会影响他们的运行。
更细致的文档甚至提到以但引号括起来的变量将不被 BASH 解释为变量,如 '$STR' ,而被看成为纯粹的字符串。而且更为标准的变量引用方式是 ${STR} 这样的,$STR 自不过是对 ${STR} 的一种简化。在复杂情况下(即有可能产生歧义的地方)最好用带 {} 的表示方式。
BASH 中的变量既然不需要定义,也就没有类型一说,一个变量即可以被定义为一个字符串,也可以被再定义为整数。如果对该变量进行整数运算,他就被解释为整数;如果对他进行字符串操作,他就被看作为一个字符串。请看下面的例子:
#!/bin/bash
x=1999
let "x = $x + 1"
echo $x
x="olympic'"$x
echo $x
关于整数变量计算,有如下几种:" + - * / % ",他们的意思和字面意思相同。整数运算一般通过 let 和 expr 这两个指令来实现,如对变量 x 加 1 可以写作:let "x = $x + 1" 或者 x=`expr $x + 1`
在比较操作上,整数变量和字符串变量各不相同,详见下表:
对应的操作 整数操作 字符串操作
相同 -eq =
不同 -ne !=
大于 -gt >
小于 -lt ls_result
$ ls -l >> ls_result
上面这两个命令分别将 ls 命令的结果输出重定向到 ls_result 文件中和追加到 ls_result 文件中,而不是输出到屏幕上。">"就是输出(标准输出和标准错误输出)重定向的代表符号,连续两个 ">" 符号,即 ">>" 则表示不清除原来的而追加输出。下面再来看一个稍微复杂的例子:
$ find /home -name lost* 2> err_result
这个命令在 ">" 符号之前多了一个 "2","2>" 表示将标准错误输出重定向。由于 /home 目录下有些目录由于权限限制不能访问,因此会产生一些标准错误输出被存放在 err_result 文件中。大家可以设想一下 find /home -name lost* 2>>err_result 命令会产生什么结果?
如果直接执行 find /home -name lost* > all_result ,其结果是只有标准输出被存入 all_result 文件中,要想让标准错误输出和标准输入一样都被存入到文件中,那该怎么办呢?看下面这个例子:
$ find /home -name lost* > all_result 2>& 1
上面这个例子中将首先将标准错误输出也重定向到标准输出中,再将标准输出重定向到 all_result 这个文件中。这样我们就可以将所有的输出都存储到文件中了。为实现上述功能,还有一种简便的写法如下:
$ find /home -name lost* >& all_result
如果那些出错信息并不重要,下面这个命令可以让你避开众多无用出错信息的干扰:
$ find /home -name lost* 2> /dev/null
同学们回去后还可以再试验一下如下几种重定向方式,看看会出什么结果,为什么?
$ find /home -name lost* > all_result 1>& 2
$ find /home -name lost* 2> all_result 1>& 2
$ find /home -name lost* 2>& 1 > all_result
另外一个非常有用的重定向操作符是 "-",请看下面这个例子:
$ (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xvfp -)
该命令表示把 /source/directory 目录下的所有文件通过压缩和解压,快速的全部移动到 /dest/directory 目录下去,这个命令在 /source/directory 和 /dest/directory 不处在同一个文件系统下时将显示出特别的优势。
下面还几种不常见的用法:
n&- 表示将 n 号输出关闭
>&- 表示将标准输出关闭
[目录]
--------------------------------------------------------------------------------
流程控制
BASH 中的基本流程控制语法
BASH 中几乎含有 C 语言中常用的所有控制结构,如条件分支、循环等,下面逐一介绍。
if...then...else
if 语句用于判断和分支,其语法规则和 C 语言的 if 非常相似。其几种基本结构为:
if [ expression ]
then
statments
fi
或者
if [ expression ]
then
statments
else
statments
fi
或者
if [ expression ]
then
statments
else if [ expression ]
then
statments
else
statments
fi
或者
if [ expression ]
then
statments
elif [ expression ]
then
statments
else
statments
fi
值得说明的是如果你将 if 和 then 简洁的写在一行里面,就必须在 then 前面加上分号,如:if [ expression ]; then下面这个例子说明了如何使用 if 条件判断语句:
#!/bin/bash
if [ $1 -gt 90 ]
then
echo "Good, $1"
elif [ $1 -gt 70 ]
then
echo "OK, $1"
else
echo "Bad, $1"
fi
exit 0
上面例子中的 $1 是指命令行的第一个参数,这个会在后面的“BASH 中的特殊保留字”中讲解。
for
for 循环结构与 C 语言中有所不同,在 BASH 中 for 循环的基本结构是:
for $var in
do
statments
done
其中 $var 是循环控制变量, 是 $var 需要遍历的一个集合,do/done 对包含了循环体,相当于 C 语言中的一对大括号。另外如果do 和 for 被写在同一行,必须在 do 前面加上 ";"。如: for $var in ; do 。下面是一个运用 for 进行循环的例子:
#!/bin/bash
for day in Sun Mon Tue Wed Thu Fri Sat
do
echo $day
done
# 如果列表被包含在一对双引号中,则被认为是一个元素
for day in "Sun Mon Tue Wed Thu Fri Sat"
do
echo $day
done
exit 0
注意上面的例子中,在 for 所在那行的变量 day 是没有加 "$" 符号的,而在循环体内,echo 所在行变量 $day 是必须加上 "$" 符号的。另外如果写成 for day 而没有后面的 in 部分,则 day 将取遍命令行的所有参数。如这个程序:
#!/bin/bash
for param
do
echo $param
done
exit 0
上面这个程序将列出所有命令行参数。for 循环结构的循环体被包含在 do/done 对中,这也是后面的 while、until 循环所具有的特点。
while
while 循环的基本结构是:
while [ condition ]
do
statments
done
这个结构请大家自己编写一个例子来验证。
until
until 循环的基本结构是:
until [ condition is TRUE ]
do
statments
done
这个结构也请大家自己编写一个例子来验证。
case
BASH 中的 case 结构与 C 语言中的 switch 语句的功能比较类似,可以用于进行多项分支控制。其基本结构是:
case "$var" in
condition1 )
statments1;;
condition2 )
statments2;;
...
* )
default statments;;
esac
下面这个程序是运用 case 结构进行分支执行的例子:
#!/bin/bash
echo "Hit a key, then hit return."
read Keypress
case "$Keypress" in
[a-z] ) echo "Lowercase letter";;
[A-Z] ) echo "Uppercase letter";;
[0-9] ) echo "Digit";;
* ) echo "Punctuation, whitespace, or other";;
esac
exit 0
上面例子中的第四行 "read Keypress" 一句中的 read 语句表示从键盘上读取输入。这个命令将在本讲义的 BASH 的其他高级问题中讲解。
break/continue
熟悉 C 语言编程的都很熟悉 break 语句和 continue 语句。BASH 中同样有这两条语句,而且作用和用法也和 C 语言中相同,break 语句可以让程序流程从当前循环体中完全跳出,而 continue 语句可以跳过当次循环的剩余部分并直接进入下一次循环。
[目录]
--------------------------------------------------------------------------------
函数
函数的使用
BASH 是一个相对简单的脚本语言,不过为了方便结构化的设计,BASH 中也提供了函数定义的功能。BASH 中的函数定义很简单,只要向下面这样写就可以了:
function my_funcname {
code block
}
或者
my_funcname() {
code block
}
上面的第二种写法更接近于 C 语言中的写法。BASH 中要求函数的定义必须在函数使用之前,这是和 C 语言用头文件说明函数方法的不同。
更进一步的问题是如何给函数传递参数和获得返回值。BASH 中函数参数的定义并不需要在函数定义处就制定,而只需要在函数被调用时用 BASH 的保留变量 $1 $2 ... 来引用就可以了;BASH 的返回值可以用 return 语句来指定返回一个特定的整数,如果没有 return 语句显式的返回一个返回值,则返回值就是该函数最后一条语句执行的结果(一般为 0,如果执行失败返回错误码)。函数的返回值在调用该函数的程序体中通过 $? 保留字来获得。下面我们就来看一个用函数来计算整数平方的例子:
#!/bin/bash
square() {
let "res = $1 * $1"
return $res
}
square $1
result=$?
echo $result
exit 0
[目录]
--------------------------------------------------------------------------------
bash快捷键
关于bash在控制台下的快捷键
ctrl+u 删除光标以前的所有字符
ctrl+d 删除光标以前的一个字符
ctrl+k 删除光标以后的所有字符
ctrl+h 删除光标以后的一个字符
ctrl+t 调换光标前两个字符的次序
ctrl+a 移动光标到最前面
ctrl+e 移动光标到最后面
ctrl+p 上一个命令
ctrl+n 下一个命令
ctrl+s 锁定输入
ctrl+q 解除锁定
ctrl+f 移动光标到后一个字符
ctrl+b 移动光标到前一个字符
ctrl+x 标记一个位置
ctrl+c 清除当前的输入
[目录]
--------------------------------------------------------------------------------
gawk
awk是一种程序语言,对文档资料的处理具有很强的功能。awk名称是由它三个最初设计者的姓氏的第一个字母而命名的:AlfredV.Aho、PeterJ.Weinberger、BrianW.Kernighan。
awk 最初在1977年完成。1985年发表了一个新版本的awk,它的功能比旧版本增强了不少。awk能够用很短的程序对文档里的资料做修改、比较、提? ⒋蛴〉却怼H绻?褂肅或Pascal等语言代码编写完成上述的任务会十分不方便而且很花费时间,所写的程序也会很大。
awk不仅仅是一个编程语言,它还是linux系统管理员和程序员的一个不可缺少的工具。
awk语言本身十分好学,易于掌握,并且特别的灵活。
gawk是GNU计划下所做的awk,gawk最初在1986年完成,之后不断地被改进、更新。gawk包含awk的所有功能。
基本上有两种方法可以执行gawk程序。
如果gawk程序很短,则可以将gawk直接写在命令行,如下所示:
gawk'program'input-file1input-file2...
其中program包括一些pattern和action。
如果gawk程序较长,较为方便的做法是将gawk程序存在一个文件中,gawk的格式如下所示:
gawk-fprogram-fileinput-file1input-file2...
gawk程序的文件不止一个时,执行gawk的格式如下所示:
gawk-fprogram-file1-fprogram-file2...input-file1input-file2...
[目录]
--------------------------------------------------------------------------------
文件、记录和字段
一般情况下,gawk可以处理文件中的数值数据,但也可以处理字符串信息。如果数据没有存储在文件中,可以通过管道命令和其他的重定向方法给gawk提供输入。当然,gawk只能处理文本文件(ASCII码文件)。电话号码本就是一个gawk可以处理的文件的简单例子。电话号码本由很多条目组成,每一个条目都有同样的格式:姓、名、地址、电话号码。每一个条目都是按字母顺序排列。
在gawk中,每一个这样的条目叫做一个记录。它是一个完整的数据的集合。例如,电话号码本中的SmithJohn这个条目,包括他的地址和电话号码,就是一条记录。记录中的每一项叫做一个字段。在gawk 中,字段是最基本的单位。多个记录的集合组成了一个文件。大多数情况下,字段之间由一个特殊的字符分开,像空格、TAB、分号等。这些字符叫做字段分隔符。请看下面这个 /etc/passwd文件:
tparker;t36s62hsh;501;101;TimParker;/home/tparker;/bin/bash
etreijs;2ys639dj3h;502;101;EdTreijs;/home/etreijs;/bin/tcsh
ychow;1h27sj;503;101;YvonneChow;/home/ychow;/bin/bash
你可以看出/etc/passwd文件使用分号作为字段分隔符。/etc/passwd文件中的每一行都包括七个字段:用户名;口令;用户ID;工作组 ID;注释;home目录;启始的外壳。如果你想要查找第六个字段,只需数过五个分号即可。但考虑到以下电话号码本的例子,你就会发现一些问题:
SmithJohn13WilsonSt.555-1283
SmithJohn2736ArtsideDrApt123555-2736
SmithJohn125WestmountCr555-1726
虽然我们能够分辨出每个记录包括四个字段,但gawk却无能为力。电话号码本使用空格作为分隔符,所以gawk认为Smith是第一个字段,John是第二个字段,13是第三个字段,依次类推。就gawk而言,如果用空格作为字段分隔符的话,则第一个记录有六个字段,而第二个记录有八个字段。所以,我们必须找出一个更好的字段分隔符。例如,像下面一样使用斜杠作为字段分隔符:
Smith/John/13WilsonSt./555-1283
Smith/John/2736ArtsideDr/Apt/123/555-2736
Smith/John/125WestmountCr/555-1726
如果你没有指定其他的字符作为字段分隔符,那么gawk将缺省地使用空格或TAB作为字段分隔符。
[目录]
--------------------------------------------------------------------------------
模式和动作
在gawk语言中每一个命令都由两部分组成:一个模式(pattern)和一个相应的动作(action)。只要模式符合,gawk就会执行相应的动作。其中模式部分用两个斜杠括起来,而动作部分用一对花括号括起来。例如:
/pattern1/{action1}
/pattern2/{action2}
/pattern3/{action3}
所有的gawk程序都是由这样的一对对的模式和动作组成的。其中模式或动作都能够被省略,但是两个不能同时被省略。如果模式被省略,则对于作为输入的文件里面的每一行,动作都会被执行。如果动作被省略,则缺省的动作被执行,既显示出所有符合模式的输入行而不做任何的改动。
下面是一个简单的例子,因为gawk程序很短,所以将gawk程序直接写在外壳命令行:
gawk'/tparker/'/etc/passwd
此程序在上面提到的/etc/passwd文件中寻找符合tparker模式的记录并显示(此例中没有动作,所以缺省的动作被执行)。
让我们再看一个例子:
gawk'/UNIX/{print$2}'file2.data
此命令将逐行查找file2.data文件中包含UNIX的记录,并打印这些记录的第二个字段。
你也可以在一个命令中使用多个模式和动作对,例如:
gawk'/scandal/{print$1}/rumor/{print$2}'gossip_file
此命令搜索文件gossip_file中包括scandal的记录,并打印第一个字段。然后再从头搜索gossip_file中包括rumor的记录,并打印第二个字段。
[目录]
--------------------------------------------------------------------------------
运算
gawk有很多比较运算符,下面列出重要的几个:
==相等
!=不相等
>大于
=大于等于
100'testfile将会显示文件testfile中那些第四个字段大于100的记录。
下表列出了gawk中基本的数值运算符。
运算符说明示例
+加法运算2+6
-减法运算6-3
*乘法运算2*5
/除法运算8/4
^乘方运算3^2(=9)
%求余数9%4(=1)
例如:{print$3/2}显示第三个字段被2除的结果。
在gawk 中,运算符的优先权和一般的数学运算的优先权一样。例如:{print$1+$2*$3}显示第二个字段和第三个字段相乘,然后和第一个字段相加的结果。你也可以用括号改变优先次序。例如:{print($1+$2)*$3}显示第一个字段和第二个字段相加,然后和第三个字段相乘的结果。
[目录]
--------------------------------------------------------------------------------
内部函数
gawk中有各种的内部函数,现在介绍如下:
sqrt(x)求x的平方根
sin(x)求x的正弦函数
cos(x)求x的余弦函数
atan2(x,y)求x/y的余切函数
log(x)求x的自然对数
exp(x)求x的e次方
int(x)求x的整数部分
rand()求0和1之间的随机数
srand(x)将x设置为rand()的种子数
index(in,find)在字符串in中寻找字符串find第一次出现的地方,返回值是字符串find出现在字符串in里面的位置。如果在字符串in里面找不到字符串find,则返回值为0。
例如:printindex("peanut","an")
显示结果3。
length(string)求出string有几个字符。
例如:length("abcde")
显示结果5。
match (string,regexp)在字符串string中寻找符合regexp的最长、最靠左边的子字符串。返回值是regexp在string的开始位置,即index值。match函数将会设置系统变量RSTART等于index的值,系统变量RLENGTH等于符合的字符个数。如果不符合,则会设置 RSTART为0、RLENGTH为-1。
sprintf(format,expression1,...)和printf类似,但是sprintf并不显示,而是返回字符串。
例如:sprintf("pi=%.2f(approx.)",22/7)
返回的字符串为pi=3.14(approx.)
sub(regexp,replacement,target)在字符串target中寻找符合regexp的最长、最靠左的地方,以字串replacement代替最左边的regexp。
例如:
str="water,water,everywhere"
sub(/at/,"ith",str)
结果字符串str会变成
wither,water,everywhere
gsub(regexp,replacement,target)与前面的sub类似。在字符串target中寻找符合regexp的所有地方,以字符串replacement代替所有的regexp。
例如:str="water,water,everywhere"
gsub(/at/,"ith",str)
结果字符串str会变成
wither,wither,everywhere
substr(string,start,length)返回字符串string的子字符串,这个子字符串的长度为length,从第start个位置开始。
例如:substr("washington",5,3)返回值为ing如果没有length,则返回的子字符串是从第start个位置开始至结束。
例如:substr("washington",5)
返回值为ington。
tolower(string)将字符串string的大写字母改为小写字母。
例如:tolower("MiXeDcAsE123")
返回值为mixedcase123。
toupper(string)将字符串string的小写字母改为大写字母。
例如:toupper("MiXeDcAsE123")
返回值为MIXEDCASE123。
输入输出的内部函数
close(filename)将输入或输出的文件filename关闭。
system(command)此函数允许用户执行操作系统的指令,执行完毕后将回到gawk程序。
例如:BEGIN{system("ls")}
[目录]
--------------------------------------------------------------------------------
字符串和数字
字符串就是一连串的字符,它可以被gawk逐字地翻译。字符串用双引号括起来。数字不能用双引号括起来,并且gawk将它当作一个数值。
例如:gawk'$1!="Tim"{print}'testfile
此命令将显示第一个字段和Tim不相同的所有记录。如果命令中Tim两边不用双引号,gawk将不能正确执行。
再如:gawk'$1=="50"{print}'testfile
此命令将显示所有第一个字段和50这个字符串相同的记录。gawk不管第一字段中的数值的大小,而只是逐字地比较。这时,字符串50和数值50并不相等。
我们可以让动作显示一些比较复杂的结果。例如:
gawk'$1!="Tim"{print$1,$5,$6,$2}'testfile
你也可以使用一些换码控制符格式化整行的输出。之所以叫做换码控制符,是因为gawk对这些符号有特殊的解释。下面列出常用的换码控制符:
a警告或响铃字符。
『笸艘桓瘛? f换页。
换行。
回车。
Tab。
v垂直的tab。
在gawk中,缺省的字段分隔符一般是空格符或TAB。但你可以在命令行使用-F选项改变字符分隔符,只需在-F后面跟着你想用的分隔符即可。
gawk-F";"'/tparker/{print}'/etc/passwd
在此例中,你将字符分隔符设置成分号。注意:-F必须是大写的,而且必须在第一个引号之前。
[目录]
--------------------------------------------------------------------------------
元字符
gawk语言在格式匹配时有其特殊的规则。例如,cat能够和记录中任何位置有这三个字符的字段匹配。但有时你需要一些更为特殊的匹配。如果你想让cat只和concatenate匹配,则需要在格式两端加上空格:
/cat/{print}
再例如,你希望既和cat又和CAT匹配,则可以使用或(|):
/cat|CAT/{print}
在gawk中,有几个字符有特殊意义。下面列出可以用在gawk格式中的这些字符:
?^表示字段的开始。
例如:
$3~/^b/
如果第三个字段以字符b开始,则匹配。
?$表示字段的结束。
例如:
$3~/b$/
如果第三个字段以字符b结束,则匹配。
?.表示和任何单字符m匹配。
例如:
$3~/i.m/
如果第三个字段有字符i,则匹配。
?|表示“或”。
例如:
/cat|CAT/
和cat或CAT字符匹配。
?*表示字符的零到多次重复。
例如:
/UNI*X/
和UNX、UNIX、UNIIX、UNIIIX等匹配。
?+表示字符的一次到多次重复。
例如:
/UNI+X/
和UNIX、UNIIX等匹配。
?{a,b}表示字符a次到b次之间的重复。
例如:
/UNI{1,3}X
和UNIX、UNIIX和UNIIIX匹配。
??表示字符零次和一次的重复。
例如:
/UNI?X/
和UNX和UNIX匹配。
?[]表示字符的范围。
例如:
/I[BDG]M/
和IBM、IDM和IGM匹配
?[^]表示不在[]中的字符。
例如:
/I[^DE]M/
和所有的以I开始、M结束的包括三个字符的字符串匹配,除了IDM和IEM之外。
[目录]
--------------------------------------------------------------------------------
调用gawk程序
当需要很多对模式和动作时,你可以编写一个gawk程序(也叫做gawk脚本)。在gawk程序中,你可以省略模式和动作两边的引号,因为在gawk程序中,模式和动作从哪开始和从哪结束时是很显然的。
你可以使用如下命令调用gawk程序:
gawk-fscriptfilename
此命令使gawk对文件filename执行名为script的gawk程序。
如果你不希望使用缺省的字段分隔符,你可以在f选项后面跟着F选项指定新的字段分隔符(当然你也可以在gawk程序中指定),例如,使用分号作为字段分隔符:
gawk-fscript-F";"filename
如果希望gawk程序处理多个文件,则把各个文件名罗列其后:
gawk-fscriptfilename1filename2filename3...
缺省情况下,gawk的输出将送往屏幕。但你可以使用linux的重定向命令使gawk的输出送往一个文件:
gawk-fscriptfilename>save_file
[目录]
--------------------------------------------------------------------------------
BEGIN和END
有两个特殊的模式在gawk中非常有用。BEGIN模式用来指明gawk开始处理一个文件之前执行一些动作。BEGIN经常用来初始化数值,设置参数等。END模式用来在文件处理完成后执行一些指令,一般用作总结或注释。
BEGIN和END中所有要执行的指令都应该用花括号括起来。BEGIN和END必须使用大写。
请看下面的例子:
BEGIN{print"Startingtheprocessthefile"}
$1=="UNIX"{print}
$2>10{printf"Thislinehasavalueof%d",$2}
END{print"Finishedprocessingthefile.Bye!"}
此程序中,先显示一条信息:Startingtheprocessthefile,然后将所有第一个字段等于UNIX的整条记录显示出来,然后再显示第二个字段大于10的记录,最后显示信息:Finished processingthefile.Bye!。
[目录]
--------------------------------------------------------------------------------
变量
在gawk中,可以用等号(=)给一个变量赋值:
var1=10
在gawk中,你不必事先声明变量类型。
请看下面的例子:
$1=="Plastic"{count=count+1}
如果第一个字段是Plastic,则count的值加1。在此之前,我们应当给count赋予过初值,一般是在BEGIN部分。
下面是比较完整的例子:
BEGIN{count=0}
$5=="UNIX"{count=count+1}
END{printf"%doccurrencesofUNIXwerefound",count}
变量可以和字段和数值一起使用,所以,下面的表达式均为合法:
count=count+$6
count=$5-8
count=$5+var1
变量也可以是格式的一部分,例如:
$2>max_value{print"Maxvalueexceededby",$2-max_value}
$4-var1
gawk语言中有几个十分有用的内置变量,现在列于下面:
NR已经读取过的记录数。
FNR从当前文件中读出的记录数。
FILENAME输入文件的名字。
FS字段分隔符(缺省为空格)。
RS记录分隔符(缺省为换行)。
OFMT数字的输出格式(缺省为%g)。
OFS输出字段分隔符。
ORS输出记录分隔符。
NF当前记录中的字段数。
如果你只处理一个文件,则NR和FNR的值是一样的。但如果是多个文件,NR是对所有的文件来说的,而FNR则只是针对当前文件而言。
例如:
NR$2){
print"The first column is larger"
}
else{
print"The second column is larger"
})
while循环
while循环的语法如下:
while(expression){
commands
}
例如:
#interest calculation computes compound interest
#inputs from a file arethea mount,interest_rateandyears
{var=1
while(var0){
printline[var]
var--
}
此段程序读取一个文件的每一行,并用相反的顺序显示出来。我们使用NR作为数组的下标来存储文件的每一条记录,然后在从最后一条记录开始,将文件逐条地显示出来。
[目录]
--------------------------------------------------------------------------------
自定义函数
用户自定义函数
复杂的gawk程序常常可以使用自己定义的函数来简化。调用用户自定义函数与调用内部函数的方法一样。函数的定义可以放在gawk程序的任何地方。
用户自定义函数的格式如下:
functionname(parameter-list){
body-of-function
}
name 是所定义的函数的名称。一个正确的函数名称可包括一序列的字母、数字、下标线(underscores),但是不可用数字做开头。parameter- list是函数的全部参数的列表,各个参数之间以逗点隔开。body-of-function包含gawk的表达式,它是函数定义里最重要的部分,它决定函数实际要做的事情。
下面这个例子,会将每个记录的第一个字段的值的平方与第二个字段的值的平方加起来。
{print"sum=",SquareSum($1,$2)}
function SquareSum(x,y){
sum=x*x+y*y
returnsum
}
[目录]
--------------------------------------------------------------------------------
几个实例
最后,再举几个gawk的例子:
gawk'{if(NF>max)max=NF}
END{printmax}'
此程序会显示所有输入行之中字段的最大个数。
gawk'length($0)>80'
此程序会显示出超过80个字符的每一行。此处只有模式被列出,动作是采用缺省值显示整个记录。
gawk'NF>0'
显示拥有至少一个字段的所有行。这是一个简单的方法,将一个文件里的所有空白行删除。
gawk'BEGIN{for(i=1;i$y如果$x比$y大,则返回真。
$x>=$y如果$x大于等于$y,则返回真。
$xeq$y如果字符串$x和字符串$y相同,则返回真。
数组
数组也叫做列表,是由一系列的标量组成的。数组变量以@开头。请看以下的赋值语句:
@food=("apples","pears","eels");
@music=("whistle","flute");
数组的下标从0开始,你可以使用方括号引用数组的下标:
$food[2]
返回eels。注意@已经变成了$,因为eels是一个标量。
在Perl中,数组有多种赋值方法,例如:
@moremusic=("organ",@music,"harp");
@moremusic=("organ","whistle","flute","harp");
还有一种方法可以将新的元素增加到数组中:
push(@food,"eggs");
把eggs增加到数组@food的末端。要往数组中增加多个元素,可以使用下面的语句:
push(@food,"eggs","lard");
push(@food,("eggs","lard"));
push(@food,@morefood);
push返回数组的新长度。
pop用来将数组的最后一个元素删除,并返回该元素。例如:
@food=("apples","pears","eels");
$grub=pop(@food);#此时$grub="eels"
请看下面的例子:
1#!/usr/bin/perl
2#
3#AnexampletoshowhowarraysworkinPerl
4#
5@amounts=(10,24,39);
6@parts=('computer','rat',"kbd");
7
8$a=1;$b=2;$c='3';
9@count=($a,$b,$c);
10
11@empty=();
12
13@spare=@parts;
14
15print'@amounts=';
16print"@amounts ";
17
18print'@parts=';
19print"@parts ";
20
21print'@count=';
22print"@count ";
23
24print'@empty=';
25print"@empty ";
26
27print'@spare=';
28print"@spare ";
29
30
31#
32#Accessingindividualitemsinanarray
33#
34print'$amounts[0]=';
35print"$amounts[0] ";
36print'$amounts[1]=';
37print"$amounts[1] ";
38print'$amounts[2]=';
39print"$amounts[2] ";
40print'$amounts[3]=';
41print"$amounts[3] ";
42
43print"Itemsin@amounts=$#amounts ";
44$size=@amounts;print"SizeofAmount=$size ";
45print"Item0in@amounts=$amounts[$[] ";
以下是显示结果:
@amounts=102439
@parts=computerratkbd
@count=123
@empty=
@spare=computerratkbd
$amounts[0]=10
$amounts[1]=24
$amounts[2]=39
$amounts[3]=
Itemsin@amounts=2
SizeofAmount=3
Item
第5 行,三个整数值赋给了数组@amounts。第6行,三个字符串赋给了数组@parts。第8行,字符串和数字分别赋给了三个变量,然后将三个变量赋给了数组@count。11行创建了一个空数组。13行将数组@spare赋给了数组@parts。15到28行输出了显示的前5行。34到41行分别存取数组@amounts的每个元素。注意$amount[3]不存在,所以显示一个空项。43行中使用$#array方式显示一个数组的最后一个下标,所以数组@amounts的大小是($#amounts+1)。44行中将一个数组赋给了一个标量,则将数组的大小赋给了标量。45行使用了一个Perl中的特殊变量$[,用来表示一个数组的起始位置(缺省为0)。
相关数组
一般的数组允许我们通过数字下标存取其中的元素。例如数组 @food的第一个元素是$food[0],第二个元素是$food[1],以此类推。但Perl允许创建相关数组,这样我们可以通过字符串存取数组。其实,一个相关数组中每个下标索引对应两个条目,第一个条目叫做关键字,第二个条目叫做数值。这样,你就可以通过关键字来读取数值。相关数组名以百分号 (%)开头,通过花括号({})引用条目。例如:
%ages=("MichaelCaine",39,
"DirtyDen",34,
"Angie",27,
"Willy","21indogyears",
"TheQueenMother",108);
这样我们可以通过下面的方法读取数组的值:
$ages{"MichaelCaine"};#Returns39
$ages{"DirtyDen"};#Returns34
$ages{"Angie"};#Returns27
$ages{"Willy"};#Returns"21indogyears"
$ages{"TheQueenMother"};#Returns108
[目录]
--------------------------------------------------------------------------------
文件操作
文件句柄和文件操作
我们可以通过下面的程序了解一下文件句柄的基本用法。此程序的执行结果和UNIX系统
的cat命令一样:
#!/usr/local/bin/perl
#
#Programtoopenthepasswordfile,readitin,
#printit,andcloseitagain.
$file='/etc/passwd';#Namethefile
open(INFO,$file);#Openthefile
@lines=;#Readitintoanarray
close(INFO);#Closethefile
print@lines;#Printthearray
open函数打开一个文件以供读取,其中第一个参数是文件句柄(filehandle)。文件句柄用来供Perl在以后指向该文件。第二个参数指向该文件的文件名。close函数关闭该文件。
open函数还可以用来打开文件以供写入和追加,只须分别在文件名之前加上>和>>:
open(INFO,$file);#Openforinput
open(INFO,">$file");#Openforoutput
open(INFO,">>$file");#Openforappending
open(INFO,"-');#Openstandardoutput
一个Perl程序在它一启动时就已经有了三个文件句柄:STDIN(标准输入设备),STDOUT(标准输出设备)和STDERR(标准错误信息输出设备)。如果想要从一个已经打开的文件句柄中读取信息,可以使用运算符。
使用read和write函数可以读写一个二进制的文件。其用法如下:
read(HANDLE,$buffer,$length[,$offset]);
此命令可以把文件句柄是HANDLE的文件从文件开始位移$offset处,共$length字节,读到$buffer中。其中$offset是可选项,如果省略$offset,则read()从当前位置的前$length个字节读取到当前位置。可以使用下面的命令查看是否到了文件末尾:
eof(HANDLE);
如果返回一个非零值,则说明已经到达文件的末尾。
打开文件时可能出错,所以可以使用die()显示出错信息。下面打开一个叫做“test.data”的文件:
open(TESTFILE,"test.data")||die" $0Cannotopen$! ";
[目录]
--------------------------------------------------------------------------------
流程控制
foreach循环
在Perl中,可以使用foreach循环来读取数组或其他类似列表结构中的每一行。请看下面的例子:
foreach$morsel(@food)#Visiteachiteminturn
#andcallit$morsel
{
print"$morsel ";#Printtheitem
print"Yumyum ";#Thatwasnice
}
每次要执行的命令用花括号括出。第一次执行时$morsel被赋予数组@food的第一个元素的值,第二次执行时$morsel被赋予数组@food的第二个元素的值,以此类推直到数组的最后一个元素。
判断运算
在Perl中任何非零的数字和非空的字符串都被认为是真。零、全为零的字符串和空字符串都为假。
下面是一些判断运算符:
$a==$b如果$a和$b相等,则返回真。
$a!=$b如果$a和$b不相等,则返回真。
$aeq$b如果字符串$a和字符串$b相同,则返回真
$ane$b如果字符串$a和字符串$b不相同,则返回真。
你可以使用逻辑运算符:
($a&&$b)$a与$b。
($a||$b)$a或$b。
!($a)非$a。
for循环
Perl中的for结构和C语言中的for结构基本一样:
for(initialise;test;inc){
first_action;
second_action;
etc
}
下面是一个for循环的例子,用来显示从0到9的数字:
for($i=0;$i$_[1]){
$_[0];
}
else{
$_[1];
$biggest=&maximum(37,24);#Now$biggestis37
[目录]
--------------------------------------------------------------------------------
例子
最后请看一个Perl语言的完整的例子。
此程序从一个记录学生信息的文件stufile和一个记录学生成绩的文件scorefile中生成一个
学生成绩报告单。
输入文件stufile由学生ID、姓名和年级三个字段组成,其间由分号隔开:
123456;Washington,George;SR
246802;Lincoln,Abraham"Abe";SO
357913;Jefferson,Thomas;JR
212121;Roosevelt,Theodore"Teddy";SO
文件scorefile由学生ID、科目号、分数三个字段组成,由空格隔开:
123456 1 98
212121 1 86
246802 1 89
357913 1 90
123456 2 96
212121 2 88
357913 2 92
123456 3 97
212121 3 96
246802 3 95
357913 3 94
程序应该输出如下的结果:
Stu-IDName...123Totals:
357913Jefferson,Thomas909294276
246802Lincoln,Abraham"Abe"8995184
212121Roosevelt,Theodore"Teddy"868896270
123456Washington,George989697291
Totals:363276382
源程序如下:
#!/usr/local/bin/perl
#Gradebook-demonstratesI/O,associative
#arrays,sorting,andreportformatting.
#Thisaccommodatesanynumberofexamsandstudents
#andmissingdata.Inputfilesare:
$stufile='stufile';
$scorefile='scorefile';
#Iffileopenssuccessfully,thisevaluatesas"true",andPerl
#doesnotevaluaterestofthe"or""||"
open(NAMES,"$maxnamelength){
$maxnamelength=length($name);
}}
closeNAMES;
#Buildatablefromthetestscores:
while(){
($stuid,$examno,$score)=split;
$score{$stuid,$examno}=$score;
if($examno>$maxexamno){
$maxexamno=$examno;
}}
closeSCORES;
#Printthereportfromaccumulateddata!
printf"%6s%-${maxnamelength}s",
'Stu-ID','Name...';
foreach$examno(1..$maxexamno){
printf"%4d",$examno;
}
printf"%10s ",'Totals:';
#Subroutine"byname"isusedtosortthe%namearray.
#The"sort"functiongivesvariables$aand$bto
#subroutinesitcalls.
#"xcmpy"functionreturns-1ifx #+1ifx>y.SeethePerldocumentationfordetails.
subbyname{$name{$a}cmp$name{$b}}
#OrderstudentIDssothenamesappearalphabetically:
foreach$stuid(sortbynamekeys(%name)){
#Printscoresforastudent,andatotal:
printf"%6d%-${maxnamelength}s",
$stuid,$name{$stuid};
$total=0;
foreach$examno(1..$maxexamno){
printf"%4s",$score{$stuid,$examno};
$total+=$score{$stuid,$examno};
$examtot{$examno}+=$score{$stuid,$examno};
}
printf"%10d ",$total;
}
printf" %6s%${maxnamelength}s","Totals:";
foreach$examno(1..$maxexamno){
printf"%4d",$examtot{$examno};
}
print" ";
exit(0);
[目录]
--------------------------------------------------------------------------------
正则表达式
正则表达式
正则表达式在 shell、工具程序、Perl 语言中有非常重要的地位。正则表达式通过一些特殊符号表示特定的字符串模式。常见的特殊字符包括:
字符 功能
^ 置于待搜索的字符串之前,匹配行首的字
$ 置于待搜索的字符串之后,匹配行末的字
匹配一个字的字尾
. 匹配任意单个正文字符
[str] 匹配字符串 str 中的任意单个字符
[^str] 匹配不在字符串 str 中的任意单个字符
[a-c] 匹配从 a 到 c 之间的任一字符
* 匹配前一个字符的 0 次或多次出现
忽略特殊字符的特殊含义,将其看作普通字符
扩充的特殊字符:
字符 功能
+ 重复匹配前一项 1 次以上
? 重复匹配前一项 0 次或 1 次
{j} 重复匹配前一项 j 次
{j, } 重复匹配前一项 j 次以上
{, k} 重复匹配前一项最多 k 次
{j, k} 重复匹配前一项 j 到 k 次
s | t 匹配 s 或 t 中的一项
(exp) 将表达式 exp 作为单项处理
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/31455/showart_1414858.html
|
|