最简单的例子 —— 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 <
大于或等于 -ge
小于或等于 -le
为空 -z
不为空 -n
比如:
比较字符串 a 和 b 是否相等就写作:if [ $a = $b ]
判断字符串 a 是否为空就写作: if [ -z $a ]
判断整数变量 a 是否大于 b 就写作:if [ $a -gt $b ]
更细致的文档推荐在字符串比较时尽量不要使用 -n ,而用 ! -z 来代替。(其中符号 "!" 表示求反操作)
BASH 中的变量除了用于对 整数 和 字符串 进行操作以外,另一个作用是作为文件变量。BASH 是 linux 操作系统的 Shell,因此系统的文件必然是 BASH 需要操作的重要对象,如 if [ -x /root ] 可以用于判断 /root 目录是否可以被当前用户进入。下表列出了 BASH 中用于判断文件属性的操作符:
运算符 含义( 满足下面要求时返回 TRUE )
-e file 文件 file 已经存在
-f file 文件 file 是普通文件
-s file 文件 file 大小不为零
-d file 文件 file 是一个目录
-r file 文件 file 对当前用户可以读取
-w file 文件 file 对当前用户可以写入
-x file 文件 file 对当前用户可以执行
-g file 文件 file 的 GID 标志被设置
-u file 文件 file 的 UID 标志被设置
-O file 文件 file 是属于当前用户的
-G file 文件 file 的组 ID 和当前用户相同
file1 -nt file2 文件 file1 比 file2 更新
file1 -ot file2 文件 file1 比 file2 更老
注意:上表中的 file 及 file1、file2 都是指某个文件或目录的路径。
关于局部变量
在 BASH 程序中如果一个变量被使用了,那么直到该程序的结尾,该变量都一直有效。为了使得某个变量存在于一个局部程序块中,就引入了局部变量的概念。BASH 中,在变量首次被赋初值时加上 local 关键字就可以声明一个局部变量,如下面这个例子:
#!/bin/bash
HELLO=Hello
function hello {
local HELLO=World
echo $HELLO
}
echo $HELLO
hello
echo $HELLO
该程序的执行结果是:
Hello
World
Hello
这个执行结果表明全局变量 $HELLO 的值在执行函数 hello 时并没有被改变。也就是说局部变量 $HELLO 的影响只存在于函数那个程序块中。
BASH 中的变量与 C 语言中变量的区别
这里我们为原来不熟悉 BASH 编程,但是非常熟悉 C 语言的程序员总结一下在 BASH 环境中使用变量需要注意的问题。
1,BASH 中的变量在引用时都需要在变量前加上 "$" 符号( 第一次赋值及在For循环的头部不用加 "$"符号 );
2,BASH 中没有浮点运算,因此也就没有浮点类型的变量可用;
3,BASH 中的整形变量的比较符号与 C 语言中完全不同,而且整形变量的算术运算也需要经过 let 或 expr 语句来处理;
[目录]
--------------------------------------------------------------------------------
输入输出
关于输入、输出和错误输出
在字符终端环境中,标准输入/标准输出的概念很好理解。输入即指对一个应用程序或命令的输入,无论是从键盘输入还是从别的文件输入;输出即指应用程序或命令产生的一些信息;与 Windows 系统下不同的是,linux 系统下还有一个标准错误输出的概念,这个概念主要是为程序调试和系统维护目的而设置的,错误输出于标准输出分开可以让一些高级的错误信息不干扰正常的输出信息,从而方便一般用户的使用。
在 linux 系统中:标准输入(stdin)默认为键盘输入;标准输出(stdout)默认为屏幕输出;标准错误输出(stderr)默认也是输出到屏幕(上面的 std 表示 standard)。在 BASH 中使用这些概念时一般将标准输出表示为 1,将标准错误输出表示为 2。下面我们举例来说明如何使用他们,特别是标准输出和标准错误输出。
输入、输出及标准错误输出主要用于 I/O 的重定向,就是说需要改变他们的默认设置。先看这个例子:
$ ls > 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 号输入关闭
<&- 表示关闭标准输入(键盘)
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 [list]
do
statments
done
其中 $var 是循环控制变量,[list] 是 $var 需要遍历的一个集合,do/done 对包含了循环体,相当于 C 语言中的一对大括号。另外如果do 和 for 被写在同一行,必须在 do 前面加上 ";"。如: for $var in [list]; 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 [list] 部分,则 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能够用很短的程序对文档里的资料做修改、比较、提取、打印等处理。如果使用C或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有很多比较运算符,下面列出重要的几个:
==相等
!=不相等
>大于
<小于
>=大于等于
<=小于等于
例如:gawk'$4>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<min_value{print"Illegalvalueof",$4}
gawk语言中有几个十分有用的内置变量,现在列于下面:
NR已经读取过的记录数。
FNR从当前文件中读出的记录数。
FILENAME输入文件的名字。
FS字段分隔符(缺省为空格)。
RS记录分隔符(缺省为换行)。
OFMT数字的输出格式(缺省为%g)。
OFS输出字段分隔符。
ORS输出记录分隔符。
NF当前记录中的字段数。
如果你只处理一个文件,则NR和FNR的值是一样的。但如果是多个文件,NR是对所有的文件来说的,而FNR则只是针对当前文件而言。
例如:
NR<=5{print"Notenoughfieldsintherecord"}
检查记录数是否小于5,如果小于5,则显示出错信息。
FS十分有用,因为FS控制输入文件的字段分隔符。例如,在BEGIN格式中,使用如下的命令:
FS=":"
[目录]
--------------------------------------------------------------------------------
控制结构
if表达式
if表达式的语法如下:
if(expression){
commands
}
else{
commands
}
例如:
#asimpleifloop
(if($1==0){
print"This cell has a value of zero"
}
else{
printf"The value is %d
",$1
})
再看下一个例子:
#anicely form attedi floop
(if($1>$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(var<=$3){
printf("%f
",$1*(1+$2)^var)
var++
for循环
for循环的语法如下:
for(initialization;expression;increment){
command
}
例如:
#interest calculation computes compound interest
#inputs from a fil earethea mount,interest_rateandyears
{for(var=1;var<=$3;var++){
printf("%f
",$1*(1+$2)^var)
}}
next和exit
next指令用来告诉gawk处理文件中的下一个记录,而不管现在正在做什么。语法如下:
{command1
command2
command3
next
command4
}
程序只要执行到next指令,就跳到下一个记录从头执行命令。因此,本例中,command4指令永远不会被执行。
程序遇到exit指令后,就转到程序的末尾去执行END,如果有END的话。
[目录]
--------------------------------------------------------------------------------
数组
gawk语言支持数组结构。数组不必事先初始化。声明一个数组的方法如下:
arrayname[num]=value
请看下面的例子:
#reverse lines in a file
{line[NR]=$0} #remember each line
END{var=NR #output lines in reverse order
while(var>0){
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<=7;i++)
printint(101*rand())}'
此程序会显示出范围是0到100之间的7个随机数。
ls-lfiles|gawk'{x+=$4};END{print"totalbytes:"x}'
此程序会显示出所有指定的文件的总字节数。
expandfile|gawk'{if(x<length())x=length()}
END{print"maximumlinelengthis"x}'
此程序会将指定文件里最长一行的长度显示出来。expand会将tab改成space,所以是用实际的右边界来做长度的比较。
gawk'BEGIN{FS=":"}
{print$1|"sort"}'/etc/passwd
此程序会将所有用户的登录名称,依照字母的顺序显示出来。
gawk'{nlines++}
END{printnlines}'
此程序会将一个文件的总行数显示出来。
gawk'END{printNR}'
此程序也会将一个文件的总行数显示出来,但是计算行数的工作由gawk来做。
gawk'{printNR,$0}'
此程序显示出文件的内容时,会在每行的最前面显示出行号,它的函数与‘cat-n’类似。
[目录]
--------------------------------------------------------------------------------
Perl
Perl的基本特点
“别期望在一刻钟内就能领略Perl的所有神奇之处,这种情况很像吃香蕉,用不着吃完整只香蕉后才知其味,每咬一口都是享受,并促使你再咬下一口,再下一口。”上面这段话是Perl项目发起人劳利·华尔(LarryWall)对学习Perl语言的一段经典评论,希望大家都能找到这种感觉。
Perl的设计目标是帮助UNIX用户完成一些常见的任务,这些任务对于Shell来说过于沉重或对移植性要求过于严格。Perl语言中包含了C、C++、shell,script、sed、awk这几个语言的语法,它最初的目的就是用来取代UNIX中sed/awk与脚本语言的组合,用来汇整信息,产生报表。因此Perl语言要远远比前面讲的BASH复杂和功能强大。Perl的设计原则或者说Perl的设计哲学是以实用为第一优先,也就是力图使Perl语言容易使用、有效率、而且完整。
Perl是按GNUPublicLicense和ArticticLicense两种许可证形式分发的,其实质是开源软件、自由软件的,原先运行于UNIX和类UNIX系统,现在已可以方便地在OS/2,Windows9x,Windows/NT等系统下运行。Perl是一种解释运行的语言,和BASH程序一样,一般Perl程序的第一行需注明自己是一个Perl程序而不是Shell程序,所以一般将下面一行语句:
#!/usr/bin/perl
作为文件的第一行。
Perl由于引入了模块的设计思想,随着版本的改进,功能越来越强。现在Perl的功能已经超乎原先设计时的想象,几乎任何事都可以做到,也变成每一部工作站必备的标准工具了。Perl最为著名的一点就是他对字符串的处理,由于Internet对文字信息处理的巨大需求,使得Perl的应用如日中天,而且Perl语言也的确是一个非常优秀的文字信息处理语言。
一个有用的Perl程序可以很短。例如希望更换大量文件中的一些相同内容,可以使用下面的一条命令:
perl-e's/gopher/WorldWideWeb/gi'-p-i.bak*.html
下面是一个基本的perl程序:
#!/usr/local/bin/perl
#
#Programtodotheobvious
print'Helloworld.';#只是简单地显示出Helloworld.字符串。
[目录]
--------------------------------------------------------------------------------
变量
Perl中有三种变量:标量,数组(列表)和相关数组。
Perl中最基本的变量类型是标量。标量既可以是数字,也可以是字符串,而且两者是可以互换的。具体是数字还是字符串,可以有上下文决定。标量变量的语法为$variable_name。例如:
$priority=9;
把9赋予标量变量$priority,你也可以将字符串赋予该变量:
$priority='high';
注意在Perl中,变量名的大小写是敏感的,所以$a和$A是不同的变量。
以下的数值或字符串都可以赋给标量:
12312.45E-100xff(hex)0377(octal)
'Whatyou$seeis(almost)what
youget''Don'tWalk'
"Howareyou?""Substitutevaluesof$xand
in"quotes."
`date``uptime-u``du-sk$filespec|sort-n`
$x$list_of_things[5]$lookup{'key'}
从上面可以看出,Perl中有三种类型的引用。双引号("")括起来的字符串中的任何标量和特殊意义的字符都将被Perl解释。如果不想让Perl解释字符串中的任何标量和特殊意义的字符,应该将字符串用单括号括起来。这时,Perl不解释其中的任何字符,除了和'。最后,可以用(`)将命令括起来,这样,其中的命令可以正常运行,并能得到命令的返回值。请看下面的例子:
1#!/usr/bin/perl
2$folks="100";
3print"$folks=$folks
";
4print'$folks=$folks
';
5print"
BEEP!aLSOMEBLANKELINESHERE
";
6$date=`date+%D`;
7print"Todayis[$date]
";
8chop$date;
9print"Dateafterchoppingoffcarriagereturn:[".$date."]
";
注意实际程序中不应该包括行号。其输出结果如下:
$folks=100
$folks=$folks
BEEP!someblankLINESHERE
Todayis[03/29/96]
Dateafterchoppingoffcarriagereturn:[03/29/96]
第3行显示$folks的值。$之前必须使用换码符,以便Perl显示字符串$folks而不是$folks的值100。
第4行使用的是单引号,结果Perl不解释其中的任何内容,只是原封不动地将字符串显示出来。
第6行使用的是(`),则date+%D命令的执行结果存储在标量$date中。
上例中使用了一些有特殊意义的字符,下面列出这些字符的含义:
换行。
回车。
制表符。
a蜂鸣声。
Backspace。
LE将L和E之间的字符转换成小写。
l将其后的字符转换成小写。
UE将U和E之间的字符转换成大写。
u将其后的字符转换成大写。
cC插入控制字符C。
x##十六进制数##。