免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 31637 | 回复: 7
打印 上一主题 下一主题

Shell/sed/awk学习参考资料[转贴] [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-05-06 08:18 |只看该作者 |倒序浏览
Bourne Shell及shell编程(1)
作者: 何斌武 (hbwork@dlut.edu.cn,大连理工大学网络中心,April 1999.)

  
Bourne Shell

介绍:Bourne Shell 基础及其他很多有用的特性,shell编程及组织。

主要内容:
.shell基础 基本介绍,环境,选项,特殊字符
.shell变量 用户定义变量,环境变量,位置变量(shell 参数)
.shell script编程
条件测试,循环及重复控制
.shell定制

1.shell基础知识
作者:Stephen Bourne 在Bell实验室开发
建议:man sh 查看相关UNIX上的改进或特性

(1)shell提示符及其环境
/etc/passwd文件
提示符:$
/etc/profile $HOME/.profile
(2)shell执行选项
-n 测试shell script语法结构,只读取shell script但不执行
-x 进入跟踪方式,显示所执行的每一条命令,用于调度
-a Tag all variables for export
-c "string" 从strings中读取命令
-e 非交互方式
-f 关闭shell文件名产生功能
-h locate and remember functions as defind
-i 交互方式
-k 从环境变量中读取命令的参数
-r 限制方式
-s 从标准输入读取命令
-t 执行命令后退出(shell exits)
-u 在替换中如使用未定义变量为错误
-v verbose,显示shell输入行

这些选项可以联合使用,但有些显然相互冲突,如-e和-i.

(3)受限制shell(Restircted Shell)
sh -r 或 /bin/rsh

不能执行如下操作:cd, 更改PATH,指定全路径名,输出重定向,因此可以提供一个较
好的控制和安全机制。通常rsh用于应用型用户及拨号用户,这些用户通常是看不到提
示符的。通常受限制用户的主目录是不可写的。

不足:如果用户可以调用sh,则rsh的限制将不在起作用,事实上如果用户在vi及more
程序中调用shell,而这时rsh的限制将不再起作用。

(4)用set改变 shell选项
用户可以在$提示符下用set命令来设置或取消shell的选项。使用-设置选项,+取消相应
选项,大多数UNIX系统允许a,e,f,h,k,n,u,v和x的开关设置/取消。

set -xv
启动跟踪方式;显示所有的命令及替换,同样显示输入。
set -tu
关闭在替换时对未定义变量的检查。

使用echo $-显示所有已设置的shell选项。


(5)用户启动文件 .profile
PATH=$PATH:/usr/loacl/bin; export PATH

(6)shell环境变量
CDPATH 用于cd命令的查找路径
HOME /etc/passwd文件中列出的用户主目录
IFS Internal Field Separator,默认为空格,tab及换行符
MAIL /var/mail/$USERNAME mail等程序使用
PATH
PS1,PS2 默认提示符($)及换行提示符(>
TERM 终端类型,常用的有vt100,ansi,vt200,xterm等

示例:$PS1="test:";export PS1
test: PS1="\$";export PS1
$echo $MAIL
/var/mail/username
(7)保留字符及其含义
$ shell变量名的开始,如$var
| 管道,将标准输出转到下一个命令的标准输入
# 注释开始
& 在后台执行一个进程
? 匹配一个字符
* 匹配0到多个字符(与DOS不同,可在文件名中间使用,并且含.)
$- 使用set及执行时传递给shell的标志位
$! 最后一个子进程的进程号
$# 传递给shell script的参数个数
$* 传递给shell script的参数
$@ 所有参数,个别的用双引号括起来
$? 上一个命令的返回代码
$0 当前shell的名字
$n (n:1-) 位置参数
$$ 进程标识号(Process Identifier Number, PID)
>file 输出重定向
`command` 命令替换,如 filename=`basename /usr/local/bin/tcsh`
>>fiile 输出重定向,append

转义符及单引号:
$echo "$HOME $PATH"
/home/hbwork /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:
$echo '$HOME $PATH'
$HOME $PATH
$echo \$HOME $PATH
$HOME /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/hbwork/bin

其他:
$dir=ls
$$dir
$alias dir ls
$dir

ls > filelist
ls >> filelist
wc -l < filelist
wc -l filelist
sleep 5; echo 5 seconds reaches; ls -l
ps ax |egrep inetd
find / -name core -exec rm {} \; &
filename=`date "+%Y%m%d"`.log

2. shell变量
变量:代表某些值的符号,如$HOME,cd命令查找$HOME,在计算机语言中可以使用变量可以
进行多种运算和控制。

Bourne Shell有如下四种变量:
.用户自定义变量
.位置变量即 shell script之参数
.预定义变量(特殊变量)
.环境变量(参考shell定制部分)
(1)用户自定义变量(数据的存储)
$ COUNT=1
$ NAME="He Binwu"

技巧:因为大部分UNIX命令使用小写字符,因此在shell编程中通常使用全大写变量,
当然这并不是强制性的,但使用大写字符可以在编程中方便地识别变量。

变量的调用:在变量前加$
$ echo $HOME
/home/hbwork
$ WEEK=Satur
$ echo Today is $WEEKday
Today is
$echo Today is ${WEEK}day
Today is Saturday

Shell变量赋值从右从左进行(Linux Shell/bash从左向右赋值!)
$ X=$Y Y=y
$ echo $X
y
$ Z=z Y=$Z
$ echo $Y

$

使用unset命令删除变量的赋值
$ Z=hello
$ echo $Z
hello
$ unset Z
$ echo $Z

$

有条件的命令替换
在Bourne Shell中可以使变量替换在特定条件下执行,即有条件的环境变量替换。
这种变量替换总是用大括号括起来的。

.设置变量的默认值
在变量未赋值之前其值为空。Bourne Shell允许对变量设置默认值,其格式如下:
${variable:-defaultvalue}
例:
$ echo Hello $UNAME
Hello
$ echo Hello ${UNAME:-there}
Hello there
$ echo $UNAME #变量值并未发生变化

$ UNAME=hbwork
$ echo Hello ${UNAME:-there}
Hello hbwork
$
.另一种情况:改变变量的值,格式如下:
${variable:=value}

例:
$ echo Hello $UNAME
Hello
$ echo Hello ${UNAME:=there}
Hello there
$ echo $UNAME #变量值并未发生变化
there
$
.变量替换中使用命令替换
$USERDIR=${$MYDIR:-`pwd`}

.在变量已赋值时进行替换 ${variable:+value}
.带有错误检查的有条件变量替换
${variablevalue}
例:
$ UNAME=
$ echo ${UNAME"UNAME has not been set"}
UNAME: UNAME has not been set
$ echo ${UNAME}
UNAME: parameter null or not set

(2)位置变量(Shell参数)
在shell script中位置参数可用$1..$9表示,$0表示内容通常为当前执行程序的文件名。
.防止变量值被替换 readonly variable
.使用export命令输出变量,使得变量对子shell可用,当shell执行一下程序时,shell
将为其设置一个新的环境让其执行,这称之了subshell. 在Bourne Shell中变量通常
被认为是本地变量,也就是说在对其赋值之外的shell环境之外是不认识此变量的。使
用export对subshell可用。

例:对变量PS1的export操作,shell的提示符将发生变化。
$ PS1=`hostname`$
peony$sh
$ echo $PS1
$ <-输出结果
$ exit
peony$export PS1
peony$sh
peony$ echo $PS1
peony$ <-输出结果
peony$


3.Shell Script编程
目的:使用UNIX所提供的最常用工具来完成所需复杂任务的强大功能。

(1)最简单的Shell 编程
$ls -R / |grep myname |more

每天数据的备份:
$ cd /usr/yourname; ls * |cpio -o > /dev/rmt/0h

书写程序的目的是一次编程,多次使用(执行)!

$ cat > backup.sh
cd /home/hbwork
ls * | cpio -o > /dev/rmt/0h
^D

执行:
$ sh backup.sh

或:
$ chmod +x backup.sh
$ ./backup.sh

技巧:在shell script中加入必要的注释,以便以后阅读及维护。

(2)shell是一个(编程)语言
同传统的编程语言一样,shell提供了很多特性,这些特性可以使你的shell script
编程更为有用,如:数据变量、参数传递、判断、流程控制、数据输入和输出,子
程序及以中断处理等。

. 在shell编程中使用数据变量可以将程序变量更为通用,如在上面backup.sh中:
cd $WORKDIR
ls * | cpio -o > /dev/rmt/0h

. Shell编程中的注释以#开头
. 对shell变量进行数字运算,使用expr命令
expr integer operator integer
其中operator为+ - * / %, 但对*的使用要用转义符\,如:
$expr 4 \* 5
20
$int=`expr 5 + 7`
$echo $int
12

(3)Shell编程的参数传递, 可通过命令行参数以及交互式输入变量(read)

restoreall.sh 对backup.sh程序的备份磁带进行恢复
$cat > restoreall.sh
cd $WORKDIR
cpio -i < /dev/rmt/0h
^D
restore1.sh:只能恢复一个文件
#restore1 --program to restore a single file
cd $WORKDIR
cpio -i $i < /dev/rmt/0h

$restore1 file1

恢复多个文件restoreany :
#restoreany --program to restore a single file
cd $WORKDIR
cpio -i $* < /dev/rmt/0h

$ restoreany file1 file2 file3


(4)条件判断
. if-then语句,格式如下:
if command_1
then
command_2
command_3
fi
command_4

在if-then语句中使用了命令返回码$?,即当command_1执行成功时才执行command_2和
command_3,而command_4总是执行.

示例程序unload: 在备份成功时删除原始文件,带有错误检查

cd $1
#备份时未考虑不成功的情况!
ls -a | cpio -o > /dev/rmt/0h
rm -rf *

改进如下:

#当使用了管道命令时,管理命令的返回代码为最后一个命令的返回代码
if ls -a | cpio -o > /dev/rmt/0h
then
rm -rf *
fi

. if-then-else语句
if command_1
then
command_2
else
command_3
fi

技巧: 由于shell对命令中的多余的空格不作任何处理,一个好的程序员会用这一特性
对自己的程序采用统一的缩进格式,以增强自己程序的可读性.

. 使用test命令进行进行条件测试
格式: test conditions

test在以下四种情况下使用: a. 字符比较 b.两个整数值的比较
c. 文件操作,如文件是否存在及文件的状态等
d. 逻辑操作,可以进行and/or,与其他条件联合使用

a. 测试字符数据: shell变量通常民政部下均作为字符变量
str1 = str2 二者相长,相同
str1 != str2 不同
-n string string不为空(长度不为零)
-z string string为空
string string不为空

例:
$ str1=abcd #在含有空格时必须用引号括起来
$ test $str1=abcd
$ echo $?
0
$ str1="abcd "
$ test $str1=abcd
$ echo $?
1
Note: 在test处理含有空格的变量时最好用引号将变量括起来,否则会出现错误的结果,
因为shell在处理命令行时将会去掉多余的空格,而用引号括起来则可以防止
shell去掉这些空格.
例:
$ str1=" "
$ test $str1
$ echo $?
1
$ test "$str1"
$ echo $?
0
$ test -n $str1
test: argument expected
$ test -n "$str1"
$ echo $?
0
$

b. 整数测试: test与expr相同,可以将字符型变量转换为整数进行操作,expr进行
整数的算术运算,而test则进行逻辑运算.

表达式 说明
---------------------------------------
int1 -eq int2 相等?
int1 -ne int2 不等?
int1 -gt int2 int1 > int2 ?
int1 -ge int2 int1 >= int2 ?
int1 -lt int2 int1 < int2 ?
int1 -le int2 int1 <= int2 ?

例:
$ int1=1234
$ int2=01234
$ test $int1 -eq $int2
$ echo $?
0

c. 文件测试:检查文件状态如存在及读写权限等

-r filename 用户对文件filename有读权限?
-w filename 用户对文件filename有写权限?
-x filename 用户对文件filename有可执行权限?
-f filename 文件filename为普通文件?
-d filename 文件filename为目录?
-c filename 文件filename为字符设备文件?
-b filename 文件filename为块设备文件?
-s filename 文件filename大小不为零?
-t fnumb 与文件描述符fnumb(默认值为1)相关的设备是一个终端设备?

d. 测试条件之否定,使用!
例:
$ cat /dev/null > empty
$ test -r empty
$ echo $?
0
$ test -s empty
1
$ test ! -s empty
$ echo $?
0
e. 测试条件之逻辑运算
-a And
-o Or

例: $ test -r empty -a -s empty
$ echo $?
1
f. 进行test测试的标准方法
因为test命令在 shell编程中占有很重要的地位,为了使shell能同其他编程语言一样
便于阅读和组织, Bourne Shell在使用test测试时使用了另一种方法:用方括号将整个
test测试括起来:

$ int1=4
$ [ $int1 -gt 2 ]
$ echo $?
0

例: 重写unload程序,使用test测试
#!/bin/sh
#unload - program to backup and remove files
#syntax: unload directory

#check arguments
if [ $# -ne 1 ]
then
echo "usage: $0 directory"
exit 1
fi

#check for valid directory name
if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 2
fi

cd $1

ls -a | cpio -o > /dev/rmt/0h

if [ $? -eq 0 ]
then
rm -rf *
else
echo "A problem has occured in creating backup"
echo "The directory will not be ereased"
echo "lease check the backup device"
exit 3
fi
# end of unload

在如上示例中出现了exit, exit有两个作用:一是停止程序中其他命令的执行,二是
设置程序的退出状态

g. if嵌套及elif结构
if command
then
command
else
if command
then
command
else
if command
then
command
fi
fi
fi

改进:使用elif结构
if command
then
command
elif command
then
command
elif command
then
command
fi

elif结构同if结构类似,但结构更清淅,其执行结果完全相同.


Bourne Shell及shell编程(2)
作者: 何斌武 (hbwork@dlut.edu.cn,大连理工大学网络中心,April 1999.)


  
Bourne Shell及Shell编程(2)
h.交互式从键盘读入数据
使用read语句,其格式如下:

read var1 var2 ... varn

read将不作变量替换,但会删除多余的空格,直到遇到第一个换行符(回车),
并将输入值依次赋值给相应的变量。

例:
$ read var1 var2 var3
Hello my friends
$ echo $var1 $var2 $var3
Hello my friends
$ echo $var1
Hello
$ read var1 var2 var3
Hello my dear friends
$ echo $var3
dear friends <-输入多余变量时,输入值余下的内容赋给最后一个变量
$ read var1 var2 var3
Hello friends
$ echo $var3
<- var3为空
$

在shell script中可使用read语句进行交互操作:

...
#echo -n message 输出结果后不换行
echo -n "Do you want to continue: Y or N"
read ANSWER

if [ $ANSWER=N -o $ANSWER=n ]
then
exit
fi

i. case结构:结构较elif-then结构更清楚

比较if-then语句:

if [ variable1 = value1 ]
then
command
command
elif [ variable1 = value2 ]
then
command
command
elif [ variable1 = value3 ]
then
command
command
fi

相应的case结构:

case value in
pattern1)
command
command;;
pattern2)
command
command;;
...
patternn)
command;
esac

* case语句只执行第一个匹配模式

例:使用case语句建立一个菜单选择shell script

#Display a menu
echo _
echo "1 Restore"
echo "2 Backup"
echo "3 Unload"
echo

#Read and excute the user's selection
echo -n "Enter Choice:"
read CHOICE

case "$CHOICE" in
1) echo "Restore";;
2) echo "Backup";;
3) echo "Unload";;
*) echo "Sorry $CHOICE is not a valid choice
exit 1
esac

在上例中,*指默认匹配动作。此外,case模式中也可以使用逻辑操作,如下所示:

pattern1 | pattern2 ) command
command ;;

这样可以将上面示例程序中允许用户输入数字或每一个大写字母。

case "$CHOICE" in
1|R) echo "Restore";;
2|B) echo "Backup";;
3|U) echo "Unload";;
*) echo "Sorry $CHOICE is not a valid choice
exit 1
esac

(5)循环控制
<1> while循环:
格式:
while command
do
command
command
command
...
done

例: 计算1到5的平方
#!/bin/sh
#
#Filename: square.sh
int=1

while [ $int -le 5 ]
do
sq=`expr $int \* $int`
echo $sq
int=`expr $int + 1`
done
echo "Job completed"

$ sh square.sh
1
4
9
16
25
Job completed

<2> until循环结构:
格式:
until command
do
command
command
....
command
done

示例:使用until结构计算1-5的平方
#!/bin/sh

int=1

until [ $int -gt 5 ]
do
sq=`expr $int \* $int`
echo $sq
int=`expr $int + 1`
done
echo "Job completed"

<3> 使用shift对不定长的参数进行处理
在以上的示例中我们总是假设命令行参数唯一或其个数固定,或者使用$*将整个命令
行参数传递给shell script进行处理。对于参数个数不固定并且希望对每个命令参数
进行单独处理时则需要shift命令。使用shift可以将命令行位置参数依次移动位置,
即$2->$1, $3->$2. 在移位之前的第一个位置参数$1在移位后将不在存在。

示例如下:

#!/bin/sh
#
#Filename: shifter

until [ $# -eq 0 ]
do
echo "Argument is $1 and `expr $# - 1` argument(s) remain"
shift
done


$ shifter 1 2 3 4
Argument is 1 and 3 argument(s) remain
Argument is 2 and 2 argument(s) remain
Argument is 3 and 1 argument(s) remain
Argument is 4 and 0 argument(s) remain
$

使用shift时,每进行一次移位,$#减1,使用这一特性可以用until循环对每个参
数进行处理,如下示例中是一个求整数和的shell script:

#!/bin/sh
# sumints - a program to sum a series of integers
#

if [ $# -eq 0 ]
then
echo "Usage: sumints integer list"
exit 1
fi

sum=0

until [ $# -eq 0 ]
do
sum=`expr $sum + $1`
shift
done
echo $sum


$ sh sumints 324 34 34 12 34
438
$

使用shift的另一个原因是Bourne Shell的位置参数变量为$1~$9, 因此通过位置变量
只能访问前9个参数。但这并不等于在命令行上最多只能输入9个参数。此时如果想访问
前9个参数之后的参数,就必须使用shift.

另外shift后可加整数进行一次多个移位,如:

shift 3


<4>. for循环
格式:
for var in arg1 arg2 ... argn
do
command
....
command
done

示例:
$ for letter in a b c d e; do echo $letter;done
a
b
c
d
e

对当前目录下的所有文件操作:
$ for i in *
do
if [ -f $i ]
then
echo "$i is a file"
elif [ -d $i ]
echo "$i is a directory"
fi
done

求命令行上所有整数之和:
#!/bin/sh

sum=0

for INT in $*
do
sum=`expr $sum + $INT`
done

echo $sum


<6> 从循环中退出: break和continue命令
break 立即退出循环
continue 忽略本循环中的其他命令,继续下一下循环

在shell编程中有时我们要用到进行无限循环的技巧,也就是说这种循环一直执行碰
到break或continue命令。这种无限循环通常是使用true或false命令开始的。UNIX
系统中的true总是返加0值,而false则返回非零值。如下所示:

#一直执行到程序执行了break或用户强行中断时才结束循环
while true
do
command
....
command
done

上面所示的循环也可以使用until false, 如下:

until false
do
command
....
command
done

在如下shell script中同时使用了continue,break以及case语句中的正规表达式用法:

#!/bin/sh
# Interactive program to restore, backup, or unload
# a directory

echo "Welcome to the menu driven Archive program"

while true
do
# Display a Menu
echo
echo "Make a Choice from the Menu below"
echo _
echo "1 Restore Archive"
echo "2 Backup directory"
echo "3 Unload directory"
echo "4 Quit"
echo

# Read the user's selection
echo -n "Enter Choice: "

read CHOICE

case $CHOICE in
[1-3] ) echo
# Read and validate the name of the directory

echo -n "What directory do you want? "
read WORKDIR

if [ ! -d "$WORKDIR" ]
then
echo "Sorry, $WORKDIR is not a directory"
continue
fi

# Make the directory the current working directory
cd $WORKDIR;;

4) :;; # :为空语句,不执行任何动作
*) echo "Sorry, $CHOICE is not a valid choice"
continue
esac

case "$CHOICE" in
1) echo "Restoring..."
cpio -i
2) echo "Archiving..."
ls | cpio -o >/dev/rmt/0h;;

3) echo "Unloading..."
ls | cpio -o >/dev/rmt/0h;;

4) echo "Quitting"
break;;
esac

#Check for cpio errors

if [ $? -ne 0 ]
then
echo "A problem has occurred during the process"
if [ $CHOICE = 3 ]
then
echo "The directory will not be erased"
fi

echo "lease check the device and try again"
continue
else
if [ $CHOICE = 3 ]
then
rm *
fi
fi
done


(6)结构化编程:定义函数
同其他高级语言一样,shell也提供了函数功能。函数通常也称之为子过程(subroutine),
其定义格式如下:

funcname()
{
command
...
command; #分号
}

定义函数之后,可以在shell中对此函数进行调用,使用函数定义可以将一个复杂的程序分
为多个可管理的程序段,如下所示:

# start program

setup ()
{ command list ; }

do_data ()
{ command list ; }

cleanup ()
{ command list ; }

errors ()
{ command list ; }

setup
do_data
cleanup
# end program

技巧:
. 在对函数命名时最好能使用有含义的名字,即函数名能够比较准确的描述函数所完成
的任务。
. 为了程序的维护方便,请尽可能使用注释


使用函数的另一个好处就是可以在一个程序中的不同地方执行相同的命令序列(函数),
如下所示:

iscontinue()
{
while true
do
echo -n "Continue?(Y/N)"
read ANSWER

case $ANSWER in
[Yy]) return 0;;
[Nn]) return 1;;
*) echo "Answer Y or N";;
esac
done
}

这样可以在shell编程中调用iscontinue确定是否继续执行:

if iscontinue
then
continue
else
break
fi


** shell函数与shell程序非常相似,但二者有一个非常重要的差别:shell程序是由子shell
执行的,而shell函数则是作为当前shell的一部分被执行的,因此在当前shell中可以改
变函数的定义。此外在任意shell(包括交互式的shell)中均可定义函数,如:

$ dir
dir: not found
$ dir () { ls -l ;}
$ dir
total 5875
-rw-r--r-- 1 hbwork 100 Nov 10 23:16 doc
-rw-r--r-- 1 hbwork 2973806 Nov 10 23:47 ns40docs.zip
-rw-r--r-- 1 hbwork 1715011 Nov 10 23:30 ns840usr.pdf
-rw-r--r-- 1 hbwork 1273409 Sep 23 1998 radsol21b6.tar.Z
-rw-r--r-- 1 hbwork 7526 Nov 10 23:47 wget-log
-rw-r--r-- 1 hbwork 1748 Nov 13 21:51 wget-log.1
$ unset dir
$ dir () {
> echo "ermission Link Owner Group File_SZ LastAccess FileName"
> echo "-----------------------------------------------------------"
> ls -l $*;
> }

$ dir
Permission Link Owner Group File_SZ LastAccess FileName
-----------------------------------------------------------
total 5875
-rw-r--r-- 1 hbwork 100 Nov 10 23:16 doc
-rw-r--r-- 1 hbwork 2973806 Nov 10 23:47 ns40docs.zip
-rw-r--r-- 1 hbwork 1715011 Nov 10 23:30 ns840usr.pdf
-rw-r--r-- 1 hbwork 1273409 Sep 23 1998 radsol21b6.tar.Z
-rw-r--r-- 1 hbwork 7526 Nov 10 23:47 wget-log
-rw-r--r-- 1 hbwork 1748 Nov 13 21:51 wget-log.1

通常情况下,shell script是在子shell中执行的,困此在此子shell中对变量所作的
修改对父shell不起作用。点(.) 命令使用shell在不创建子shell而由当前shell读取
并执行一个shell script, 可以通过这种方式来定义函数及变量。此外点(.)命令最
常用的功能就是通过读取.profile来重新配置初始化login变量。如下所示:

$ . .profile
(csh相对于.命令的是source命令).

(7)使用And/Or结构进行有条件的命令执行
<1> And , 仅当第一个命令成功时才有执行后一个命令,如同在逻辑与表达式中如果前面的
结果为真时才有必要继续运算,否则结果肯定为假。

格式如下:

command1 && command2

例:rm $TEMPDIR/* && echo "File successfully removed"

等价于

if rm $TEMPDIR/*
then
echo "File successfully removed"
fi

<2>Or, 与AND相反,仅当前一个命令执行出错时才执行后一条命令

例: rm $TEMPDIR/* || echo "File not removed"

等价与:

if rm $TEMPDIR/*
then
command
else
echo "File not removed"
fi

<3> 混合命令条件执行
command1 && command2 && command3
当command1, command2成功时才执行command3

command1 && command2 || comamnd3
仅当command1成功,command2失败时才执行command3

当然可以根据自己的需要进行多种条件命令的组合,在此不多讲述。


( 使用getopts命令读取unix格式选项
UNIX格式选项指如下格式的命令行参数:
command -options parameters

使用格式:
getopts option_string variable

具体使用方法请参考getopts的在线文档(man getopts).

示例如下:

#newdate
if [ $# -lt 1 ]
then
date
else
while getopts mdyDHMSTjJwahr OPTION
do
case $OPTION
in
m) date '+%m ';; # Month of Year
d) date '+%d ';; # Day of Month
y) date '+%y ';; # Year
D) date '+%D ';; # MM/DD/YY
H) date '+%H ';; # Hour
M) date '+%M ';; # Minute
S) date '+%S ';; # Second
T) date '+%T ';; # HH:MM:SS
j) date '+%j ';; # day of year
J) date '+%y%j ';;# 5 digit Julian date
w) date '+%w ';; # Day of the Week
a) date '+%a ';; # Day abbreviation
h) date '+%h ';; # Month abbreviation
r) date '+%r ';; # AM-PM time
\?) echo "Invalid option $OPTION";;
esac
done
fi

$ newdate -J
94031
$ newdate -a -h -d
Mon
Jan
31
$ newdate -ahd
Mon
Jan
31
$


示例程序:复制程序

# Syntax: duplicate [-c integer] [-v] filename
# where integer is the number of duplicate copies
# and -v is the verbose option

COPIES=1
VERBOSE=N


while getopts vc: OPTION
do
case $OPTION
in
c) COPIES=$OPTARG;;
v) VERBOSE=Y;;
\?) echo "Illegal Option"
exit 1;;
esac
done

if [ $OPTIND -gt $# ]
then
echo "No file name specified"
exit 2
fi


shift `expr $OPTIND -1`

FILE=$1
COPY=0

while [ $COPIES -gt $COPY ]
do
COPY=`expr $COPY + 1`
cp $FILE ${FILE}${COPY}
if [ VERBOSE = Y ]
then
echo ${FILE}${COPY}
fi
done


$ duplicate -v fileA
fileA1
$ duplicate -c 3 -v fileB
fileB1
fileB2
fileB3


4. Shell的定制
通常使用shell的定制来控制用户自己的环境,比如改变shell的外观(提示符)以及增强
自己的命令。

(1)通常环境变量来定制shell
通常改变环境变量可以定制shell的工作环境。shell在处理信息时会参考这些环境变量
,改变环境变量的值在一定程度上改变shell的操作方式,比如改变命令行提示符。

.使用IFS增加命令行分隔符
默认状态下shell的分隔符为空格、制表符及换行符,但可以通过改变IFS的值加入自己
的分隔符。如下所示:


$ IFS=":"
$ echo:Hello:my:Friend
Hello my Friend

(2)加入自己的命令及函数
如下程序:
#Directory and Prompt change program
#Syntax: chdir directory

if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 1
fi

cd $1
PS1=`pwd`$
export PS1

$ chdir /usr/home/teresa
$

但此程序在执行时系统提示符并不会改变,因为此程序是在子shell中执行的。因此其变量
对当前shell并无影响,要想对当前shell起作用,最好是将此作为函数写在自己的.profile中
或建立自己的个人函数文件.persfuncs

#Personal function file persfuncs

chdir()
{
#Directory and Prompt change program
#Syntax: chdir directory
if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 1
fi

cd $1
PS1=`pwd`$
export PS1;
}

再执行:
$ . .persfuncs
$ chdir temp
/home/hbbwork/temp$

也可在自己的.profile文件中用 . .persfuncs调用.persfuncs.

说明:在bash/tcsh中已经使用别名,相对而言别名比此方法更为方便。


5. 有关shell的专门讨论
(1)shell程序的调试
切记:程序员(人)总是会犯错误的,而计算机是不会错的。
使用-x进行跟踪执行,执行并显示每一条指令。

(2)命令组
用小括号将一组命令括起来,则这些命令会由子shell来完成;而{command_list;}则在当
前shell中执行。这两者的主要区别在于其对shell变量的影响,子shell执行的命令不会
影响当前shell中的变量。

$ NUMBER=2
$ (A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER)
4
$ echo $NUMBER
2
$ { A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER; }
4
$ echo $NUMBER
4


总结:
在本章中讲述了Bourne Shell的基本知识,使用shell变量,shell script基础。这些概念
对于理解学习Korn Shell, csh以及其他script编程都是非常有用的。

很多OS都有不少语言及一些script功能,但很少有象UNIX SHELL这样灵活强大的script脚
本语言能力。

对于系统管理员或程序员来说,熟练地使用shell script将对日常工作(系统维护及管理)
非常有用,如果你想作一个合格的系统管理员,强烈建议你进一步深入的了解和使用
shell.

另外,对于系统管理员来说,PERL也是一个必不可少的script编程语言,尤其是对于处理
文本格式的各种文件,PERL具有shell, awk, sed, grep等的功能,但使用起来更为灵活,
功能也更强大。大家可以参考“Perl By Examples"来学习和使用PERL。

论坛徽章:
0
2 [报告]
发表于 2003-05-06 08:20 |只看该作者

Shell/sed/awk学习参考资料[转贴]

Unix awk使用手册

作者:莫名     发表时间:2002/01/27 01:39pm

什么是awk?
你可能对UNIX比较熟悉,但你可能对awk很陌生,这一点也不奇怪,的确,与其优秀的功能相比,awk还远没达到它应有的知名度。awk是什么?与其它大多数UNIX命令不同的是,从名字上看,我们不可能知道awk的功能:它既不是具有独立意义的英文单词,也不是几个相关单词的缩写。事实上,awk是三个人名的缩写,他们是:Aho、(Peter)Weinberg和(Brain)Kernighan。正是这三个人创造了awk---一个优秀的样式扫描与处理工具。

AWK的功能是什么?与sed和grep很相似,awk是一种样式扫描与处理工具。但其功能却大大强于sed和grep。awk提供了极其强大的功能:它几乎可以完成grep和sed所能完成的全部工作,同时,它还可以进行样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。它具备了一个完整的语言所应具有的几乎所有精美特性。实际上,awk的确拥有自己的语言:awk程序设计语言,awk的三位创建者已将它正式定义为:样式扫描和处理语言。

为什么使用awk?

即使如此,你也许仍然会问,我为什么要使用awk?
使用awk的第一个理由是基于文本的样式扫描和处理是我们经常做的工作,awk所做的工作有些象数据库,但与数据库不同的是,它处理的是文本文件,这些文件没有专门的存储格式,普通的人们就能编辑、阅读、理解和处理它们。而数据库文件往往具有特殊的存储格式,这使得它们必须用数据库处理程序来处理它们。既然这种类似于数据库的处理工作我们经常会遇到,我们就应当找到处理它们的简便易行的方法,UNIX有很多这方面的工具,例如sed 、grep、sort以及find等等,awk是其中十分优秀的一种。

使用awk的第二个理由是awk是一个简单的工具,当然这是相对于其强大的功能来说的。的确,UNIX有许多优秀的工具,例如UNIX天然的开发工具C语言及其延续C++就非常的优秀。但相对于它们来说,awk完成同样的功能要方便和简捷得多。这首先是因为awk提供了适应多种需要的解决方案:从解决简单问题的awk命令行到复杂而精巧的awk程序设计语言,这样做的好处是,你可以不必用复杂的方法去解决本来很简单的问题。例如,你可以用一个命令行解决简单的问题,而C不行,即使一个再简单的程序,C语言也必须经过编写、编译的全过程。其次,awk本身是解释执行的,这就使得awk程序不必经过编译的过程,同时,这也使得它与shell script程序能够很好的契合。最后,awk本身较C语言简单,虽然awk吸收了C语言很多优秀的成分,熟悉C语言会对学习awk有很大的帮助,但awk本身不须要会使用C语言——一种功能强大但需要大量时间学习才能掌握其技巧的开发工具。

使用awk的第三个理由是awk是一个容易获得的工具。与C和C++语言不同,awk只有一个文件(/bin/awk),而且几乎每个版本的UNIX都提供各自版本的awk,你完全不必费心去想如何获得awk。但C语言却不是这样,虽然C语言是UNIX天然的开发工具,但这个开发工具却是单独发行的,换言之,你必须为你的UNIX版本的C语言开发工具单独付费(当然使用D版者除外),获得并安装它,然后你才可以使用它。

基于以上理由,再加上awk强大的功能,我们有理由说,如果你要处理与文本样式扫描相关的工作,awk应该是你的第一选择。在这里有一个可遵循的一般原则:如果你用普通的shell工具或shell script有困难的话,试试awk,如果awk仍不能解决问题,则便用C语言,如果C语言仍然失败,则移至C++。

awk的调用方式

前面曾经说过,awk提供了适应多种需要的不同解决方案,它们是:
一、awk命令行,你可以象使用普通UNIX命令一样使用awk,在命令行中你也可以使用awk程序设计语言,虽然awk支持多行的录入,但是录入长长的命令行并保证其正确无误却是一件令人头疼的事,因此,这种方法一般只用于解决简单的问题。当然,你也可以在shell script程序中引用awk命令行甚至awk程序脚本。

二、使用-f选项调用awk程序。awk允许将一段awk程序写入一个文本文件,然后在awk命令行中用-f选项调用并执行这段程序。具体的方法我们将在后面的awk语法中讲到。

三、利用命令解释器调用awk程序:利用UNIX支持的命令解释器功能,我们可以将一段awk程序写入文本文件,然后在它的第一行加上:
#!/bin/awk -f
并赋予这个文本文件以执行的权限。这样做之后,你就可以在命令行中用类似于下面这样的方式调用并执行这段awk程序了。

$awk脚本文本名 待处理文件

awk的语法:
与其它UNIX命令一样,awk拥有自己的语法:
awk [ -F re] [parameter...] ['prog'] [-f progfile][in_file...]
参数说明:
-F re:允许awk更改其字段分隔符。
parameter: 该参数帮助为不同的变量赋值。
'prog': awk的程序语句段。这个语句段必须用单拓号:'和'括起,以防被shell解释。这个程序语句段的标准形式为:
'pattern {action}'
其中pattern参数可以是egrep正则表达式中的任何一个,它可以使用语法/re/再加上一些样式匹配技巧构成。与sed类似,你也可以使用","分开两样式以选择某个范围。关于匹配的细节,你可以参考附录,如果仍不懂的话,找本UNIX书学学grep和sed(本人是在学习ed时掌握匹配技术的)。action参数总是被大括号包围,它由一系列awk语句组成,各语句之间用";"分隔。awk解释它们,并在pattern给定的样式匹配的记录上执行其操作。与shell类似,你也可以使用“#”作为注释符,它使“#”到行尾的内容成为注释,在解释执行时,它们将被忽略。你可以省略pattern和action之一,但不能两者同时省略,当省略pattern时没有样式匹配,表示对所有行(记录)均执行操作,省略action时执行缺省的操作——在标准输出上显示。

-f progfile:允许awk调用并执行progfile指定有程序文件。progfile是一个文本文件,他必须符合awk的语法。

in_file:awk的输入文件,awk允许对多个输入文件进行处理。值得注意的是awk不修改输入文件。如果未指定输入文件,awk将接受标准输入,并将结果显示在标准输出上。awk支持输入输出重定向。

awk的记录、字段与内置变量:

前面说过,awk处理的工作与数据库的处理方式有相同之处,其相同处之一就是awk支持对记录和字段的处理,其中对字段的处理是grep和sed不能实现的,这也是awk优于二者的原因之一。在awk中,缺省的情况下总是将文本文件中的一行视为一个记录,而将一行中的某一部分作为记录中的一个字段。为了操作这些不同的字段,awk借用shell的方法,用$1,$2,$3...这样的方式来顺序地表示行(记录)中的不同字段。特殊地,awk用$0表示整个行(记录)。不同的字段之间是用称作分隔符的字符分隔开的。系统默认的分隔符是空格。awk允许在命令行中用-F re的形式来改变这个分隔符。事实上,awk用一个内置的变量FS来记忆这个分隔符。awk中有好几个这样的内置变量,例如,记录分隔符变量RS、当前工作的记录数NR等等,本文后面的附表列出了全部的内置变量。这些内置的变量可以在awk程序中引用或修改,例如,你可以利用NR变量在模式匹配中指定工作范围,也可以通过修改记录分隔符RS让一个特殊字符而不是换行符作为记录的分隔符。

例:显示文本文件myfile中第七行到第十五行中以字符%分隔的第一字段,第三字段和第七字段:

awk -F % 'NR==7,NR==15 {printf $1 $3 $7}'

awk的内置函数

awk之所以成为一种优秀的程序设计语言的原因之一是它吸收了某些优秀的程序设计语言(例如C)语言的许多优点。这些优点之一就是内置函数的使用,awk定义并支持了一系列的内置函数,由于这些函数的使用,使得awk提供的功能更为完善和强大,例如,awk使用了一系列的字符串处理内置函数(这些函数看起来与C语言的字符串处理函数相似,其使用方式与C语言中的函数也相差无几),正是由于这些内置函数的使用,使awk处理字符串的功能更加强大。本文后面的附录中列有一般的awk所提供的内置函数,这些内置函数也许与你的awk版本有些出入,因此,在使用之前,最好参考一下你的系统中的联机帮助。

作为内置函数的一个例子,我们将在这里介绍awk的printf函数,这个函数使得awk与c语言的输出相一致。实际上,awk中有许多引用形式都是从C语言借用过来的。如果你熟悉C语言,你也许会记得其中的printf函数,它提供的强大格式输出功能曾经带我们许多的方便。幸运的是,我们在awk中又和它重逢了。awk中printf几乎与C语言中一模一样,如果你熟悉C语言的话,你完全可以照C语言的模式使用awk中的printf。因此在这里,我们只给出一个例子,如果你不熟悉的话,请随便找一本C语言的入门书翻翻。

例:显示文件myfile中的行号和第3字段:
$awk '{printf"%03d%s\n",NR,$1}' myfile

在命令行使用awk
按照顺序,我们应当讲解awk程序设计的内容了,但在讲解之前,我们将用一些例子来对前面的知识进行回顾,这些例子都是在命令行中使用的,由此我们可以知道在命令行中使用awk是多么的方便。这样做的原因一方面是为下面的内容作铺垫,另一方面是介绍一些解决简单问题的方法,我们完全没有必要用复杂的方法来解决简单的问题----既然awk提供了较为简单的方法的话。

例:显示文本文件mydoc匹配(含有)字符串"sun"的所有行。

$awk '/sun/{print}' mydoc

由于显示整个记录(全行)是awk的缺省动作,因此可以省略action项。
$awk '/sun/' mydoc
例:下面是一个较为复杂的匹配的示例:
$awk '/[Ss]un/,/[Mm]oon/ {print}' myfile
它将显示第一个匹配Sun或sun的行与第一个匹配Moon或moon的行之间的行,并显示到标准输出上。
例:下面的示例显示了内置变量和内置函数length()的使用:
$awk 'length($0)>80 {print NR}' myfile
该命令行将显示文本myfile中所有超过80个字符的行号,在这里,用$0表示整个记录(行),同时,内置变量NR不使用标志符'$'。
例:作为一个较为实际的例子,我们假设要对UNIX中的用户进行安全性检查,方法是考察/etc下的passwd文件,检查其中的passwd字段(第二字段)是否为"*",如不为"*",则表示该用户没有设置密码,显示出这些用户名(第一字段)。我们可以用如下语句实现:
#awk -F: '$2=="" {printf("%s no password!\n",$1' /etc/passwd
在这个示例中,passwd文件的字段分隔符是“:”,因此,必须用-F:来更改默认的字段分隔符,这个示例中也涉及到了内置函数printf的使用。

awk的变量

如同其它程序设计语言一样,awk允许在程序语言中设置变量,事实上,提供变量的功能是程序设计语言的其本要求,不提供变量的程序设计语言本人还从未见过。
awk提供两种变量,一种是awk内置的变量,这前面我们已经讲过,需要着重指出的是,与后面提到的其它变量不同的是,在awk程序中引用内置变量不需要使用标志符"$"(回忆一下前面讲过的NR的使用)。awk提供的另一种变量是自定义变量。awk允许用户在awk程序语句中定义并调用自已的变量。当然这种变量不能与内置变量及其它awk保留字相同,在awk中引用自定义变量必须在它前面加上标志符"$"。与C语言不同的是,awk中不需要对变量进行初始化,awk根据其在awk中第一次出现的形式和上下文确定其具体的数据类型。当变量类型不确定时,awk默认其为字符串类型。这里有一个技巧:如果你要让你的awk程序知道你所使用的变量的明确类型,你应当在在程序中给它赋初值。在后面的实例中,我们将用到这一技巧。

运算与判断:
作为一种程序设计语言所应具有的特点之一,awk支持多种运算,这些运算与C语言提供的基本相同:如+、-、*、/、%等等,同时,awk也支持C语言中类似++、--、+=、-=、=+、=-之类的功能,这给熟悉C语言的使用者编写awk程序带来了极大的方便。作为对运算功能的一种扩展,awk还提供了一系列内置的运算函数(如log、sqr、cos、sin等等)和一些用于对字符串进行操作(运算)的函数(如length、substr等等)。这些函数的引用大大的提高了awk的运算功能。

作为对条件转移指令的一部分,关系判断是每种程序设计语言都具备的功能,awk也不例外。awk中允许进行多种测试,如常用的==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)等等,同时,作为样式匹配,还提供了~(匹配于)和!~(不匹配于)判断。

作为对测试的一种扩充,awk也支持用逻辑运算符:!(非)、&&(与)、||(或)和括号()进行多重判断,这大大增强了awk的功能。本文的附录中列出了awk所允许的运算、判断以及操作符的优先级。

awk的流程控制

流程控制语句是任何程序设计语言都不能缺少的部分。任何好的语言都有一些执行流程控制的语句。awk提供的完备的流程控制语句类似于C语言,这给我们编程带来了极大的方便。

1、BEGIN和END:
在awk中两个特别的表达式,BEGIN和END,这两者都可用于pattern中(参考前面的awk语法),提供BEGIN和END的作用是给程序赋予初始状态和在程序结束之后执行一些扫尾的工作。任何在BEGIN之后列出的操作(在{}内)将在awk开始扫描输入之前执行,而END之后列出的操作将在扫描完全部的输入之后执行。因此,通常使用BEGIN来显示变量和预置(初始化)变量,使用END来输出最终结果。

例:累计销售文件xs中的销售金额(假设销售金额在记录的第三字段):

$awk
>'BEGIN { FS=":";print "统计销售金额";total=0}
>{print $3;total=total+$3;}
>END {printf "销售金额总计:%.2f",total}' sx
(注:>是shell提供的第二提示符,如要在shell程序awk语句和awk语言中换行,则需在行尾加反斜杠\)
在这里,BEGIN预置了内部变量FS(字段分隔符)和自定义变量total,同时在扫描之前显示出输出行头。而END则在扫描完成后打印出总合计。

2、流程控制语句
awk提供了完备的流程控制语句,其用法与C语言类似。下面我们一一加以说明:
2.1、if...else语句:
格式:
if(表达式)
语句1
else
语句2
格式中"语句1"可以是多个语句,如果你为了方便awk判断也方便你自已阅读,你最好将多个语句用{}括起来。awk分枝结构允许嵌套,其格式为:
if(表达式1)
{if(表达式2)
语句1
else
语句2
}
语句3
else {if(表达式3)
语句4
else
语句5
}
语句6
当然实际操作过程中你可能不会用到如此复杂的分枝结构,这里只是为了给出其样式罢了。

2.2、while语句
格式为:
while(表达式)
语句

2.3、do-while语句
格式为:
do
{
语句
}while(条件判断语句)

2.4、for语句
格式为:
for(初始表达式;终止条件;步长表达式)
{语句}

在awk的 while、do-while和for语句中允许使用break,continue语句来控制流程走向,也允许使用exit这样的语句来退出。break中断当前正在执行的循环并跳到循环外执行下一条语句。continue从当前位置跳到循环开始处执行。对于exit的执行有两种情况:当exit语句不在END中时,任何操作中的exit命令表现得如同到了文件尾,所有模式或操作执行将停止,END模式中的操作被执行。而出现在END中的exit将导致程序终止。

例:
awk中的自定义函数
定义和调用用户自己的函数是几乎每个高级语言都具有的功能,awk也不例外,但原始的awk并不提供函数功能,只有在nawk或较新的awk版本中才可以增加函数。
函数的使用包含两部分:函数的定义与函数调用。其中函数定义又包括要执行的代码(函数本身)和从主程序代码传递到该函数的临时调用。

awk函数的定义方法如下:
function 函数名(参数表){
函数体
}

在gawk中允许将function省略为func,但其它版本的awk不允许。函数名必须是一个合法的标志符,参数表中可以不提供参数(但在调用函数时函数名后的一对括号仍然是不可缺少的),也可以提供一个或多个参数。与C语言相似,awk的参数也是通过值来传递的。
在awk中调用函数比较简单,其方法与C语言相似,但awk比C语言更为灵活,它不执行参数有效性检查。换句话说,在你调用函数时,可以列出比函数预计(函数定义中规定)的多或少的参数,多余的参数会被awk所忽略,而不足的参数,awk将它们置为缺省值0或空字符串,具体置为何值,将取决于参数的使用方式。
awk函数有两种返回方式:隐式返回和显式返回。当awk执行到函数的结尾时,它自动地返回到调用程序,这是函数是隐式返回的。如果需要在结束之前退出函数,可以明确地使用返回语句提前退出。方法是在函数中使用形如:return 返回值 格式的语句。
例:下面的例子演示了函数的使用。在这个示例中,定义了一个名为print_header的函数,该函数调用了两个参数FileName和PageNum,FileName参数传给函数当前使用的文件名,PageNum参数是当前页的页号。这个函数的功能是打印(显示)出当前文件的文件名,和当前页的页号。完成这个功能后,这个函数将返回下一页的页号。
nawk
>'BEGIN{pageno=1;file=FILENAME
>pageno=print_header(file,pageno);#调用函数print_header
>printf("当前页页号是:%d\n",pageno);
>}
>#定义函数print_header
>function print_header(FileName,PageNum){
>printf("%s %d\n",FileName,PageNum);
>ageNum++;return PageNUm;
>}
>}' myfile

执行这个程序将显示如下内容:

myfile 1
当前页页号是:2

awk高级输入输出
1.读取下一条记录:
awk的next语句导致awk读取下一个记录并完成模式匹配,然后立即执行相应的操作。通常它用匹配的模式执行操作中的代码。next导致这个记录的任何额外匹配模式被忽略。

2.简单地读取一条记录
awk的 getline语句用于简单地读取一条记录。如果用户有一个数据记录类似两个物理记录,那么getline将尤其有用。它完成一般字段的分离(设置字段变量$0 FNR NF NR)。如果成功则返回1,失败则返回0(到达文件尾)。如果需简单地读取一个文件,则可以编写以下代码:

例:示例getline的使用

{while(getline==1)
{
#process the inputted fields
}
}

也可以使getline保存输入数据在一个字段中,而不是通过使用getline variable的形式处理一般字段。当使用这种方式时,NF被置成0,FNR和NR被增值。
用户也可以使用getline<"filename"方式从一个给定的文件中输入数据,而不是从命令行所列内容输入数据。此时,getline将完成一般字段分离(设置字段变量$0和NF)。如果文件不存在,返回-1,成功,返回1,返回0表示失败。用户可以从给定文件中读取数据到一个变量中,也可以用stdin(标准输入设备)或一个包含这个文件名的变量代替filename。值得注意的是当使用这种方式时不修改FNR和NR。
另一种使用getline语句的方法是从UNIX命令接受输入,例如下面的例子:

例:示例从UNIX命令接受输入
{while("who -u"|getline)
{
#process each line from the who command
}
}

当然,也可以使用如下形式:

"command" | getline variable

3.关闭文件:
awk中允许在程序中关闭一个输入或输出文件,方法是使用awk的close语句。
close("filename"
filename可以是getline打开的文件(也可以是stdin,包含文件名的变量或者getline使用的确切命令)。或一个输出文件(可以是stdout,包含文件名的变量或使用管道的确切命令)。

4.输出到一个文件:
awk中允许用如下方式将结果输出到一个文件:
printf("hello word!\n">"datafile"

printf("hello word!\n">>"datafile"

5.输出到一个命令
awk中允许用如下方式将结果输出到一个命令:
printf("hello word!\n"|"sort -t','"
awk与shell script混合编程
因为awk可以作为一个shell命令使用,因此awk能与shell批处理程序很好的融合在一起,这给实现awk与shell程序的混合编程提供了可能。实现混合编程的关键是awk与shell script之间的对话,换言之,就是awk与shell script之间的信息交流:awk从shell script中获取所需的信息(通常是变量的值)、在awk中执行shell命令行、shell script将命令执行的结果送给awk处理以及shell script读取awk的执行结果等等。

1.awk读取Shell script程序变量
在awk中我们可以通过“'$变量名'”的方式读取shell scrpit程序中的变量。
例:在下面的示例中,我们将读取shell scrpit程序中的变量Name,该变量存放的是文本myfile的撰写者,awk将打印出这个人名。
$cat writename
:
# @(#)
#
.
.
.
Name="张三" nawk 'BEGIN {name="'Name'";\ printf("\t%s\t撰写者%s\n",FILENAME,name";}\
{...}END{...}' myfile
.
.
.

2.将shell命令的执行结果送给awk处理
作为信息传送的一种方法,我们可以将一条shell命令的结果通过管道线(|)传递给awk处理:
例:示例awk处理shell命令的执行结果
$who -u | awk '{printf("%s正在执行%s\n",$2,$1)}'
该命令将打印出注册终端正在执行的程序名。
3.shell script程序读awk的执行结果
为了实现shell script程序读取awk执行的结果,我们可以采取一些特殊的方法,例如我们可以用变量名=`awk语句`的形式将awk执行的结果存放入一个shell script变量。当然也可以用管道线的方法将awk执行结果传递给shell script程序处理。
例:作为传送消息的机制之一,UNIX提供了一个向其所有用户传送消息的命令wall(意思是write to all写给所有用户),该命令允许向所有工作中的用户(终端)发送消息。为此,我们可以通过一段shell批处理程序wall.shell来模拟这一程序(事实上比较老的版本中wall就是一段shell批处理程序:
$cat wall.shell
:
# @(#) wall.shell:发送消息给每个已注册终端
#
cat >/tmp/$$
#用户录入消息文本 who -u | awk '{print $2}' | while read tty
do
cat /tmp/$$>$tty
done
在这个程序里,awk接受who -u命令的执行结果,该命令打印出所有已注册终端的信息,其中第二个字段是已注册终端的设备名,因此用awk命令析出该设备名,然后用while read tty语句循环读出这些文件名到变量(shell script变量)tty中,作为信息传送的终结地址。

4.在awk中执行shell命令行----嵌入函数system()
system()是一个不适合字符或数字类型的嵌入函数,该函数的功能是处理作为参数传递给它的字符串。system对这个参数的处理就是将其作为命令处理,也就是说将其当作命令行一样加以执行。这使得用户在自己的awk程序需要时可以灵活地执行命令或脚本。
例:下面的程序将使用system嵌入函数打印用户编制好的报表文件,这个文件存放在名为myreport.txt的文件中。为简约起见,我们只列出了其END部分:
.
.
.
END {close("myreport.txt";system("lp myreport.txt";}

在这个示例中,我们首先使用close语句关闭了文件myreport.txt文件,然后使用system嵌入函数将myreport.txt送入打印机打印。
写到这里,我不得不跟朋友们说再见了,实在地说,这些内容仍然是awk的初步知识,电脑永远是前进的科学,awk也不例外,本篇所能做的只是在你前行的漫漫长途中铺平一段小小开端,剩下的路还得靠你自己去走。老实说,如果本文真能给你前行的路上带来些许的方便,那本人就知足了!
如对本篇有任何疑问,请E-mail To:Chizlong@yeah.net或到主页http://chizling.yeah.net中留言。

附录:
1.awk的常规表达式元字符
\ 换码序列
^ 在字符串的开头开始匹配
$ 在字符串的结尾开始匹配
. 与任何单个字符串匹配
[ABC] 与[]内的任一字符匹配
[A-Ca-c] 与A-C及a-c范围内的字符匹配(按字母表顺序)
[^ABC] 与除[]内的所有字符以外的任一字符匹配
Desk|Chair 与Desk和Chair中的任一个匹配
[ABC][DEF] 关联。与A、B、C中的任一字符匹配,且其后要跟D、E、F中的任一个字符。
* 与A、B或C中任一个出现0次或多次的字符相匹配
+ 与A、B或C中任何一个出现1次或多次的字符相匹配
? 与一个空串或A、B或C在任何一个字符相匹配
(Blue|Black)berry 合并常规表达式,与Blueberry或Blackberry相匹配

2.awk算术运算符
运算符 用途
------------------
x^y x的y次幂
x**y 同上
x%y 计算x/y的余数(求模)
x+y x加y
x-y x减y
x*y x乘y
x/y x除y
-y 负y(y的开关符号);也称一目减
++y y加1后使用y(前置加)
y++ 使用y值后加1(后缀加)
--y y减1后使用y(前置减)
y-- 使用后y减1(后缀减)
x=y 将y的值赋给x
x+=y 将x+y的值赋给x
x-=y 将x-y的值赋给x
x*=y 将x*y的值赋给x
x/=y 将x/y的值赋给x x%=y 将x%y的值赋给x
x^=y 将x^y的值赋给x
x**=y 将x**y的值赋给x

3.awk允许的测试:
操作符 含义
x==y x等于y
x!=y x不等于y
x>y x大于y
x>=y x大于或等于y
x<y x小于y
x<=y x小于或等于y?
x~re x匹配正则表达式re?
x!~re x不匹配正则表达式re?

4.awk的操作符(按优先级升序排列)
= 、+=、 -=、 *= 、/= 、 %=
||
&&
> >= < <= == != ~ !~
xy (字符串连结,'x''y'变成"xy")
+ -
* / %
++ --

5.awk内置变量(预定义变量)
说明:表中v项表示第一个支持变量的工具(下同):A=awk,N=nawk,P=POSIX awk,G=gawk
V 变量                           含义 缺省值
---------------------------------------------------------------------------
N  ARGC                     命令行参数个数
G  ARGIND                 当前被处理文件的ARGV标志符
N  ARGV                     命令行参数数组
G  CONVFMT             数字转换格式 %.6g
P  ENVIRON UNIX     环境变量
N  ERRNO UNIX        系统错误消息
G  FIELDWIDTHS     输入字段宽度的空白分隔字符串
A  FILENAME             当前输入文件的名字
P  FNR                        当前记录数
A  FS                           输入字段分隔符 空格
G  IGNORECASE      控制大小写敏感0(大小写敏感)
A  NF                           当前记录中的字段个数
A  NR                          已经读出的记录数
A  OFMT                     数字的输出格式 %.6g
A  OFS                        输出字段分隔符 空格
A  ORS                       输出的记录分隔符 新行
A  RS                          输入的记录他隔符 新行
N  RSTART                被匹配函数匹配的字符串首
N  RLENGTH             被匹配函数匹配的字符串长度
N  SUBSEP               下标分隔符 "\034"

6.awk的内置函数
V  函数                                                用途或返回值
---------------------------------------------------------------------------------------------------------------------
N gsub(reg,string,target)                    每次常规表达式reg匹配时替换target中的string
N index(search,string)                        返回string中search串的位置
A length(string)                                   求串string中的字符个数
N match(string,reg)                             返回常规表达式reg匹配的string中的位置
N printf(format,variable)                      格式化输出,按format提供的格式输出变量variable。
N split(string,store,delim)                   根据分界符delim,分解string为store的数组元素
N sprintf(format,variable)                    返回一个包含基于format的格式化数据,
                                                              variables是要放到串中的数据
G strftime(format,timestamp)             返回一个基于format的日期或者时间串,
                                                              timestmp是systime()函数返回的时间
N sub(reg,string,target)                       第一次当常规表达式reg匹配,替换target串中的字符串
A substr(string,position,len)                返回一个以position开始len个字符的子串
P tolower(string)                                  返回string中对应的小写字符
P toupper(string)                                 返回string中对应的大写字符
A atan(x,y)                                            x的余切(弧度)
N cos(x)                                                x的余弦(弧度)
A exp(x)                                                e的x幂
A int(x)                                                  x的整数部分
A log(x)                                                 x的自然对数值
N rand()                                                0-1之间的随机数
N sin(x)                                                 x的正弦(弧度)
A sqrt(x)                                               x的平方根
A srand(x)                                           初始化随机数发生器。如果忽略x,则使用system()
G system()                                          返回自1970年1月1日以来经过的时间(按秒计算)

*****************************************************
通用线程:Awk 实例


作者:donggua1     发表时间:2002/08/29 08:24am

通用线程:Awk 实例
Daniel Robbins (drobbins@gentoo.org)
第一部分
Awk 是一种非常好的语言,同时有一个非常奇怪的名称。在本系列(共三篇文章)的第一篇文章中,Daniel Robbins 将使您迅速掌握 awk 编程技巧。随着本系列的进展,将讨论更高级的主题,最后将演示一个真正的高级 awk 演示程序。
捍卫 awk
在本系列文章中,我将使您成为精通 awk 的编码人员。我承认,awk 并没有一个非常好听且又非常“时髦”的名字。awk 的 GNU 版本(叫作 gawk)听起来非常怪异。那些不熟悉这种语言的人可能听说过 "awk",并可能认为它是一组落伍且过时的混乱代码。它甚至会使最博学的 UNIX 权威陷于错乱的边缘(使他不断地发出 "kill -9!" 命令,就象使用咖啡机一样)。
的确,awk 没有一个动听的名字。但它是一种很棒的语言。awk 适合于文本处理和报表生成,它还有许多精心设计的特性,允许进行需要特殊技巧程序设计。与某些语言不同,awk 的语法较为常见。它借鉴了某些语言的一些精华部分,如 C 语言、python 和 bash(虽然在技术上,awk 比 python 和 bash 早创建)。awk 是那种一旦学会了就会成为您战略编码库的主要部分的语言。
第一个 awk
让我们继续,开始使用 awk,以了解其工作原理。在命令行中输入以下命令:
$ awk '{ print }' /etc/passwd
您将会见到 /etc/passwd 文件的内容出现在眼前。现在,解释 awk 做了些什么。调用 awk 时,我们指定 /etc/passwd 作为输入文件。执行 awk 时,它依次对 /etc/passwd 中的每一行执行 print 命令。所有输出都发送到 stdout,所得到的结果与与执行catting /etc/passwd完全相同。
现在,解释 { print } 代码块。在 awk 中,花括号用于将几块代码组合到一起,这一点类似于 C 语言。在代码块中只有一条 print 命令。在 awk 中,如果只出现 print 命令,那么将打印当前行的全部内容。
这里是另一个 awk 示例,它的作用与上例完全相同:
$ awk '{ print $0 }' /etc/passwd
在 awk 中,$0 变量表示整个当前行,所以 print 和 print $0 的作用完全一样。
如果您愿意,可以创建一个 awk 程序,让它输出与输入数据完全无关的数据。以下是一个示例:
$ awk '{ print "" }' /etc/passwd
只要将 "" 字符串传递给 print 命令,它就会打印空白行。如果测试该脚本,将会发现对于 /etc/passwd 文件中的每一行,awk 都输出一个空白行。再次说明, awk 对输入文件中的每一行都执行这个脚本。以下是另一个示例:
$ awk '{ print "hiya" }' /etc/passwd
运行这个脚本将在您的屏幕上写满 hiya。
多个字段
awk 非常善于处理分成多个逻辑字段的文本,而且让您可以毫不费力地引用 awk 脚本中每个独立的字段。以下脚本将打印出您的系统上所有用户帐户的列表:

$ awk -F":" '{ print $1 }' /etc/passwd
上例中,在调用 awk 时,使用 -F 选项来指定 ":" 作为字段分隔符。awk 处理 print $1 命令时,它会打印出在输入文件中每一行中出现的第一个字段。以下是另一个示例
$ awk -F":" '{ print $1 $3 }' /etc/passwd
以下是该脚本输出的摘录
halt7operator11root0shutdown6sync5bin1....etc.
如您所见,awk 打印出 /etc/passwd 文件的第一和第三个字段,它们正好分别是用户名和用户标识字段。现在,当脚本运行时,它并不理想 -- 在两个输出字段之间没有空格!如果习惯于使用 bash 或 python 进行编程,那么您会指望 print $1 $3 命令在两个字段之间插入空格。然而,当两个字符串在 awk 程序中彼此相邻时,awk 会连接它们但不在它们之间添加空格。以下命令会在这两个字段中插入空格:
$ awk -F":" '{ print $1 " " $3 }' /etc/passwd
以这种方式调用 print 时,它将连接 $1、" " 和 $3,创建可读的输出。当然,如果需要的话,我们还可以插入一些文本标签:
$ awk -F":" '{ print "username: " $1 "\t\tuid:" $3" }' /etc/passwd
这将产生以下输出:
username: halt          uid:7username: operator      uid:11username: root          uid:0username: shutdown      uid:6username: sync          uid:5username: bin           uid:1....etc.
外部脚本
将脚本作为命令行自变量传递给 awk 对于小的单行程序来说是非常简单的,而对于多行程序,它就比较复杂。您肯定想要在外部文件中撰写脚本。然后可以向 awk 传递 -f 选项,以向它提供此脚本文件:
$ awk -f myscript.awk myfile.in
将脚本放入文本文件还可以让您使用附加 awk 功能。例如,这个多行脚本与前面的单行脚本的作用相同,它们都打印出 /etc/passwd 中每一行的第一个字段:
BEGIN {    FS=":"}{ print $1 }
这两个方法的差别在于如何设置字段分隔符。在这个脚本中,字段分隔符在代码自身中指定(通过设置 FS 变量),而在前一个示例中,通过在命令行上向 awk 传递 -F":" 选项来设置 FS。通常,最好在脚本自身中设置字段分隔符,只是因为这表示您可以少输入一个命令行自变量。我们将在本文的后面详细讨论 FS 变量。
BEGIN 和 END 块
通常,对于每个输入行,awk 都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在 awk 开始处理输入文件中的文本之前执行初始化代码。对于这种情况,awk 允许您定义一个 BEGIN 块。我们在前一个示例中使用了 BEGIN 块。因为 awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。
awk 还提供了另一个特殊块,叫作 END 块。awk 在处理了输入文件中的所有行之后执行这个块。通常,END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
规则表达式和块
awk 允许使用规则表达式,根据规则表达式是否匹配当前行来选择执行独立代码块。以下示例脚本只输出包含字符序列 foo 的那些行:
/foo/ { print }
当然,可以使用更复杂的规则表达式。以下脚本将只打印包含浮点数的行:
/[0-9]+\.[0-9]*/ { print }
表达式和块
还有许多其它方法可以选择执行代码块。我们可以将任意一种布尔表达式放在一个代码块之前,以控制何时执行某特定块。仅当对前面的布尔表达式求值为真时,awk 才执行代码块。以下示例脚本输出将输出其第一个字段等于 fred 的所有行中的第三个字段。如果当前行的第一个字段不等于 fred,awk 将继续处理文件而不对当前行执行 print 语句:
$1 == "fred" { print $3 }
awk 提供了完整的比较运算符集合,包括 "=="、"<"、">"、"<="、">=" 和 "!="。另外,awk 还提供了 "~" 和 "!~" 运算符,它们分别表示“匹配”和“不匹配”。它们的用法是在运算符左边指定变量,在右边指定规则表达式。如果某一行的第五个字段包含字符序列 root,那么以下示例将只打印这一行中的第三个字段:
$5 ~ /root/ { print $3 }
条件语句
awk 还提供了非常好的类似于 C 语言的 if 语句。如果您愿意,可以使用 if 语句重写前一个脚本:
{     if ( $5 ~ /root/ ) {         print $3     }}
这两个脚本的功能完全一样。第一个示例中,布尔表达式放在代码块外面。而在第二个示例中,将对每一个输入行执行代码块,而且我们使用 if 语句来选择执行 print 命令。这两个方法都可以使用,可以选择最适合脚本其它部分的一种方法。
以下是更复杂的 awk if 语句示例。可以看到,尽管使用了复杂、嵌套的条件语句,if 语句看上去仍与相应的 C 语言 if 语句一样:
{    if ( $1 == "foo" ) {        if ( $2 == "foo" ) {            print "uno"        } else {            print "one"        }    } else if ($1 == "bar" ) {        print "two"    } else {        print "three"    }}
使用 if 语句还可以将代码:
! /matchme/ { print $1 $3 $4 }
转换成:
{       if ( $0 !~ /matchme/ ) {        print $1 $3 $4    }}
这两个脚本都只输出不包含 matchme 字符序列的那些行。此外,还可以选择最适合您的代码的方法。它们的功能完全相同。
awk 还允许使用布尔运算符 "||"(逻辑与)和 "&&"(逻辑或),以便创建更复杂的布尔表达式:
( $1 == "foo" ) && ( $2 == "bar" ) { print }
这个示例只打印第一个字段等于 foo 且第二个字段等于 bar 的那些行。
数值变量!
至今,我们不是打印字符串、整行就是特定字段。然而,awk 还允许我们执行整数和浮点运算。通过使用数学表达式,可以很方便地编写计算文件中空白行数量的脚本。以下就是这样一个脚本:
BEGIN   { x=0 }/^$/    { x=x+1 }END     { print "I found " x " blank lines. " }
在 BEGIN 块中,将整数变量 x 初始化成零。然后,awk 每次遇到空白行时,awk 将执行 x=x+1 语句,递增 x。处理完所有行之后,执行 END 块,awk 将打印出最终摘要,指出它找到的空白行数量。
字符串化变量
awk 的优点之一就是“简单和字符串化”。我认为 awk 变量“字符串化”是因为所有 awk 变量在内部都是按字符串形式存储的。同时,awk 变量是“简单的”,因为可以对它执行数学操作,且只要变量包含有效数字字符串,awk 会自动处理字符串到数字的转换步骤。要理解我的观点,请研究以下这个示例:
x="1.01"# We just set x to contain the *string* "1.01"x=x+1# We just added one to a *string* print x# Incidentally, these are comments
awk 将输出:
2.01
有趣吧!虽然将字符串值 1.01 赋值给变量 x,我们仍然可以对它加一。但在 bash 和 python 中却不能这样做。首先,bash 不支持浮点运算。而且,如果 bash 有“字符串化”变量,它们并不“简单”;要执行任何数学操作,bash 要求我们将数字放到丑陋的 $( ) ) 结构中。如果使用 python,则必须在对 1.01 字符串执行任何数学运算之前,将它转换成浮点值。虽然这并不困难,但它仍是附加的步骤。如果使用 awk,它是全自动的,而那会使我们的代码又好又整洁。如果想要对每个输入行的第一个字段乘方并加一,可以使用以下脚本:
{ print ($1^2)+1 }
如果做一个小实验,就可以发现如果某个特定变量不包含有效数字,awk 在对数学表达式求值时会将该变量当作数字零处理。
众多运算符
awk 的另一个优点是它有完整的数学运算符集合。除了标准的加、减、乘、除,awk 还允许使用前面演示过的指数运算符 "^"、模(余数)运算符 "%" 和其它许多从 C 语言中借入的易于使用的赋值操作符。
这些运算符包括前后加减(i++、--foo)、加/减/乘/除赋值运算符( a+=3、b*=2、c/=2.2、d-=6.2)。不仅如此 -- 我们还有易于使用的模/指数赋值运算符(a^=2、b%=4)。
字段分隔符
awk 有它自己的特殊变量集合。其中一些允许调整 awk 的运行方式,而其它变量可以被读取以收集关于输入的有用信息。我们已经接触过这些特殊变量中的一个,FS。前面已经提到过,这个变量让您可以设置 awk 要查找的字段之间的字符序列。我们使用 /etc/passwd 作为输入时,将 FS 设置成 ":"。当这样做有问题时,我们还可以更灵活地使用 FS。
FS 值并没有被限制为单一字符;可以通过指定任意长度的字符模式,将它设置成规则表达式。如果正在处理由一个或多个 tab 分隔的字段,您可能希望按以下方式设置 FS:
FS="\t+"
以上示例中,我们使用特殊 "+" 规则表达式字符,它表示“一个或多个前一字符”。
如果字段由空格分隔(一个或多个空格或 tab),您可能想要将 FS 设置成以下规则表达式:
FS="[[]+]"
这个赋值表达式也有问题,它并非必要。为什么?因为缺省情况下,FS 设置成单一空格字符,awk 将这解释成表示“一个或多个空格或 tab”。在这个特殊示例中,缺省 FS 设置恰恰是您最想要的!
复杂的规则表达式也不成问题。即使您的记录由单词 "foo" 分隔,后面跟着三个数字,以下规则表达式仍允许对数据进行正确的分析:
FS="foo[0-9][0-9][0-9]"
字段数量
接着我们要讨论的两个变量通常并不是需要赋值的,而是用来读取以获取关于输入的有用信息。第一个是 NF 变量,也叫做“字段数量”变量。awk 会自动将该变量设置成当前记录中的字段数量。可以使用 NF 变量来只显示某些输入行:
NF == 3 { print "this particular record has three fields: " $0 }
当然,也可以在条件语句中使用 NF 变量,如下:
{       if ( NF > 2 ) {        print $1 " " $2 ":" $3     }}
记录号
记录号 (NR) 是另一个方便的变量。它始终包含当前记录的编号(awk 将第一个记录算作记录号 1)。迄今为止,我们已经处理了每一行包含一个记录的输入文件。对于这些情况,NR 还会告诉您当前行号。然而,当我们在本系列以后部分中开始处理多行记录时,就不会再有这种情况,所以要注意!可以象使用 NF 变量一样使用 NR 来只打印某些输入行:
(NR < 10 ) || (NR > 100) { print "We are on record number 1-9 or 101+" }
另一个示例:
{    #skip header    if ( NR > 10 ) {        print "ok, now for the real information!"    }}
awk 提供了适合各种用途的附加变量。我们将在以后的文章中讨论这些变量。
现在已经到了初次探索 awk 的尾声。随着本系列的开展,我将演示更高级的 awk 功能,我们将用一个真实的 awk 应用程序作为本系列的结尾。同时,如果急于学习更多知识,请参考以下列出的参考资料。
第二部分
在这篇 awk 简介的续集中,Daniel Robbins 继续探索 awk(一种很棒但有怪异名称的语言)。Daniel 将演示如何处理多行记录、使用循环结构,以及创建并使用 awk 数组。阅读完本文后,您将精通许多 awk 的功能,而且可以编写您自己的功能强大的 awk 脚本。
多行记录
awk 是一种用于读取和处理结构化数据(如系统的 /etc/passwd 文件)的极佳工具。/etc/passwd 是 UNIX 用户数据库,并且是用冒号定界的文本文件,它包含许多重要信息,包括所有现有用户帐户和用户标识,以及其它信息。在我的前一篇文章中,我演示了 awk 如何轻松地分析这个文件。我们只须将 FS(字段分隔符)变量设置成 ":"。
正确设置了 FS 变量之后,就可以将 awk 配置成分析几乎任何类型的结构化数据,只要这些数据是每行一个记录。然而,如果要分析占据多行的记录,仅仅依靠设置 FS 是不够的。在这些情况下,我们还需要修改 RS 记录分隔符变量。RS 变量告诉 awk 当前记录什么时候结束,新记录什么时候开始。
譬如,让我们讨论一下如何完成处理“联邦证人保护计划”所涉及人员的地址列表的任务:
Jimmy the Weasel100 Pleasant DriveSan Francisco, CA 12345Big Tony200 Incognito Ave.Suburbia, WA 67890
理论上,我们希望 awk 将每 3 行看作是一个独立的记录,而不是三个独立的记录。如果 awk 将地址的第一行看作是第一个字段 ($1),街道地址看作是第二个字段 ($2),城市、州和邮政编码看作是第三个字段 $3,那么这个代码就会变得很简单。以下就是我们想要得到的代码:
BEGIN {    FS="\n"    RS=""}
在上面这段代码中,将 FS 设置成 "\n" 告诉 awk 每个字段都占据一行。通过将 RS 设置成 "",还会告诉 awk 每个地址记录都由空白行分隔。一旦 awk 知道是如何格式化输入的,它就可以为我们执行所有分析工作,脚本的其余部分很简单。让我们研究一个完整的脚本,它将分析这个地址列表,并将每个记录打印在一行上,用逗号分隔每个字段。
address.awk
BEGIN {    FS="\n"    RS=""}{    print $1 ", " $2 ", " $3}
如果这个脚本保存为 address.awk,地址数据存储在文件 address.txt 中,可以通过输入 "awk -f address.awk address.txt" 来执行这个脚本。此代码将产生以下输出:
Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345Big Tony, 200 Incognito Ave., Suburbia, WA 67890
OFS 和 ORS
在 address.awk 的 print 语句中,可以看到 awk 会连接(合并)一行中彼此相邻的字符串。我们使用此功能在同一行上的三个字段之间插入一个逗号和空格 (", "。这个方法虽然有用,但比较难看。与其在字段间插入 ", " 字符串,倒不如让通过设置一个特殊 awk 变量 OFS,让 awk 完成这件事。请参考下面这个代码片断。
print "Hello", "there", "Jim!"
这行代码中的逗号并不是实际文字字符串的一部分。事实上,它们告诉 awk "Hello"、"there" 和 "Jim!" 是单独的字段,并且应该在每个字符串之间打印 OFS 变量。缺省情况下,awk 产生以下输出:
Hello there Jim!
这是缺省情况下的输出结果,OFS 被设置成 " ",单个空格。不过,我们可以方便地重新定义 OFS,这样 awk 将插入我们中意的字段分隔符。以下是原始 address.awk 程序的修订版,它使用 OFS 来输出那些中间的 ", " 字符串:
address.awk 的修订版
BEGIN {    FS="\n"    RS=""    OFS=", "}{    print $1, $2, $3}
awk 还有一个特殊变量 ORS,全称是“输出记录分隔符”。通过设置缺省为换行 ("\n" 的 OFS,我们可以控制在 print 语句结尾自动打印的字符。缺省 ORS 值会使 awk 在新行中输出每个新的 print 语句。如果想使输出的间隔翻倍,可以将 ORS 设置成 "\n\n"。或者,如果想要用单个空格分隔记录(而不换行),将 ORS 设置成 ""。
将多行转换成用 tab 分隔的格式
假设我们编写了一个脚本,它将地址列表转换成每个记录一行,且用 tab 定界的格式,以便导入电子表格。使用稍加修改的 address.awk 之后,就可以清楚地看到这个程序只适合于三行的地址。如果 awk 遇到以下地址,将丢掉第四行,并且不打印该行:
Cousin VinnieVinnie's Auto Shop300 City AlleySosueme, OR 76543
要处理这种情况,代码最好考虑每个字段的记录数量,并依次打印每个记录。现在,代码只打印地址的前三个字段。以下就是我们想要的一些代码:
适合具有任意多字段的地址的 address.awk 版本
BEGIN {  FS="\n" RS=""  ORS="" }  {  x=1 while ( x<NF ) { print $x "\t"  x++ }  print $NF "\n" }
首先,将字段分隔符 FS 设置成 "\n",将记录分隔符 RS 设置成 "",这样 awk 可以象以前一样正确分析多行地址。然后,将输出记录分隔符 ORS 设置成 "",它将使 print 语句在每个调用结尾不输出新行。这意味着如果希望任何文本从新的一行开始,那么需要明确写入 print "\n"。
在主代码块中,创建了一个变量 x 来存储正在处理的当前字段的编号。起初,它被设置成 1。然后,我们使用 while 循环(一种 awk 循环结构,等同于 C 语言中的 while 循环),对于所有记录(最后一个记录除外)重复打印记录和 tab 字符。最后,打印最后一个记录和换行;此外,由于将 ORS 设置成 "",print 将不输出换行。程序输出如下,这正是我们所期望的:
我们想要的输出。不算漂亮,但用 tab 定界,以便于导入电子表格
Jimmy the Weasel        100 Pleasant Drive      San Francisco, CA 12345 Big Tony        200 Incognito Ave.      Suburbia, WA 67890Cousin Vinnie   Vinnie's Auto Shop      300 City Alley  Sosueme, OR 76543
循环结构
我们已经看到了 awk 的 while 循环结构,它等同于相应的 C 语言 while 循环。awk 还有 "do...while" 循环,它在代码块结尾处对条件求值,而不象标准 while 循环那样在开始处求值。它类似于其它语言中的 "repeat...until" 循环。以下是一个示例:
do...while 示例
{ count=1   do {  print "I get printed at least once no matter what"  } while ( count != 1 )}
与一般的 while 循环不同,由于在代码块之后对条件求值,"do...while" 循环永远都至少执行一次。换句话说,当第一次遇到普通 while 循环时,如果条件为假,将永远不执行该循环。
for 循环
awk 允许创建 for 循环,它就象 while 循环,也等同于 C 语言的 for 循环:
for ( initial assignment; comparison; increment ) {  code block}
以下是一个简短示例:
for ( x = 1; x<= 4; x++ ) { print "iteration",x}
此段代码将打印:
iteration 1iteration 2iteration 3iteration 4
break 和 continue
此外,如同 C 语言一样,awk 提供了 break 和 continue 语句。使用这些语句可以更好地控制 awk 的循环结构。以下是迫切需要 break 语句的代码片断:
while 死循环
while (1) {    print "forever and ever..."}
因为 1 永远代表是真,这个 while 循环将永远运行下去。以下是一个只执行十次的循环:
break 语句示例
x=1  while(1) { print "iteration",x  if ( x == 10 ) {  break  } x++}
这里,break 语句用于“逃出”最深层的循环。"break" 使循环立即终止,并继续执行循环代码块后面的语句。
continue 语句补充了 break,其作用如下:
x=1 while (1) { if ( x == 4 ) {  x++  continue  }  print "iteration",x    if ( x > 20 ) {  break  }  x++}
这段代码打印 "iteration 1" 到 "iteration 21","iteration 4" 除外。如果迭代等于 4,则增加 x 并调用 continue 语句,该语句立即使 awk 开始执行下一个循环迭代,而不执行代码块的其余部分。如同 break 一样,continue 语句适合各种 awk 迭代循环。在 for 循环主体中使用时,continue 将使循环控制变量自动增加。以下是一个等价循环:
for ( x=1; x<=21; x++ ) {  if ( x == 4 ) {  continue  }    print "iteration",x}
在 while 循环中时,在调用 continue 之前没有必要增加 x,因为 for 循环会自动增加 x。
数组
如果您知道 awk 可以使用数组,您一定会感到高兴。然而,在 awk 中,数组下标通常从 1 开始,而不是 0:
myarray[1]="jim"myarray[2]=456
awk 遇到第一个赋值语句时,它将创建 myarray,并将元素 myarray[1] 设置成 "jim"。执行了第二个赋值语句后,数组就有两个元素了。
数组迭代
定义之后,awk 有一个便利的机制来迭代数组元素,如下所示:
for ( x in myarray ) { print myarray[x]}
这段代码将打印数组 myarray 中的每一个元素。当对于 for 使用这种特殊的 "in" 形式时,awk 将 myarray 的每个现有下标依次赋值给 x(循环控制变量),每次赋值以后都循环一次循环代码。虽然这是一个非常方便的 awk 功能,但它有一个缺点 -- 当 awk 在数组下标之间轮转时,它不会依照任何特定的顺序。那就意味着我们不能知道以上代码的输出是
jim456
还是
456jim
套用 Forrest Gump 的话来说,迭代数组内容就像一盒巧克力 -- 您永远不知道将会得到什么。因此有必要使 awk 数组“字符串化”,我们现在就来研究这个问题。
数组下标字符串化
在我的前一篇文章中,我演示了 awk 实际上以字符串格式来存储数字值。虽然 awk 要执行必要的转换来完成这项工作,但它却可以使用某些看起来很奇怪的代码:
a="1"b="2"c=a+b+3
执行了这段代码后,c 等于 6。由于 awk 是“字符串化”的,添加字符串 "1" 和 "2" 在功能上并不比添加数字 1 和 2 难。这两种情况下,awk 都可以成功执行运算。awk 的“字符串化”性质非常可爱 -- 您可能想要知道如果使用数组的字符串下标会发生什么情况。例如,使用以下代码:
myarr["1"]="Mr. Whipple"print myarr["1"]
可以预料,这段代码将打印 "Mr. Whipple"。但如果去掉第二个 "1" 下标中的引号,情况又会怎样呢?
myarr["1"]="Mr. Whipple"print myarr[1]
猜想这个代码片断的结果比较难。awk 将 myarr["1"] 和 myarr[1] 看作数组的两个独立元素,还是它们是指同一个元素?答案是它们指的是同一个元素,awk 将打印 "Mr. Whipple",如同第一个代码片断一样。虽然看上去可能有点怪,但 awk 在幕后却一直使用数组的字符串下标!
了解了这个奇怪的真相之后,我们中的一些人可能想要执行类似于以下的古怪代码:
myarr["name"]="Mr. Whipple"print myarr["name"]
这段代码不仅不会产生错误,而且它的功能与前面的示例完全相同,也将打印 "Mr. Whipple"!可以看到,awk 并没有限制我们使用纯整数下标;如果我们愿意,可以使用字符串下标,而且不会产生任何问题。只要我们使用非整数数组下标,如 myarr["name"],那么我们就在使用关联数组。从技术上讲,如果我们使用字符串下标,awk 的后台操作并没有什么不同(因为即便使用“整数”下标,awk 还是会将它看作是字符串)。但是,应该将它们称作关联数组 -- 它听起来很酷,而且会给您的上司留下印象。字符串化下标是我们的小秘密。
数组工具
谈到数组时,awk 给予我们许多灵活性。可以使用字符串下标,而且不需要连续的数字序列下标(例如,可以定义 myarr[1] 和 myarr[1000],但不定义其它所有元素)。虽然这些都很有用,但在某些情况下,会产生混淆。幸好,awk 提供了一些实用功能有助于使数组变得更易于管理。
首先,可以删除数组元素。如果想要删除数组 fooarray 的元素 1,输入:
delete fooarray[1]
而且,如果想要查看是否存在某个特定数组元素,可以使用特殊的 "in" 布尔运算符,如下所示:
if ( 1 in fooarray ) { print "Ayep!  It's there."} else { print "Nope!  Can't find it."}
下一篇
本文中,我们已经讨论了许多基础知识。下一篇中,我将演示如何使用 awk 的数学运算和字符串函数,以及如何创建您自己的函数,使您完全掌握 awk 知识。我还将指导您创建支票簿结算程序。那时,我会鼓励您编写自己的 awk 程序。请查阅以下参考资料。
在 awk 系列的这篇总结中,Daniel 向您介绍 awk 重要的字符串函数,以及演示了如何从头开始编写完整的支票簿结算程序。在这个过程中,您将学习如何编写自己的函数,并使用 awk 的多维数组。学完本文之后,您将掌握更多 awk 经验,可以让您创建功能更强大的脚本。

第三部分
格式化输出
虽然大多数情况下 awk 的 print 语句可以完成任务,但有时我们还需要更多。在那些情况下,awk 提供了两个我们熟知的老朋友 printf() 和 sprintf()。是的,如同其它许多 awk 部件一样,这些函数等同于相应的 C 语言函数。printf() 会将格式化字符串打印到 stdout,而 sprintf() 则返回可以赋值给变量的格式化字符串。如果不熟悉 printf() 和 sprintf(),介绍 C 语言的文章可以让您迅速了解这两个基本打印函数。在 Linux 系统上,可以输入 "man 3 printf" 来查看 printf() 帮助页面。
以下是一些 awk sprintf() 和 printf() 的样本代码。可以看到,它们几乎与 C 语言完全相同。
x=1 b="foo" printf("%s got a %d on the last test\n","Jim",83) myout=("%s-%d",b,x) print myout
此代码将打印:
Jim got a 83 on the last testfoo-1
字符串函数
awk 有许多字符串函数,这是件好事。在 awk 中,确实需要字符串函数,因为不能象在其它语言(如 C、C++ 和 Python)中那样将字符串看作是字符数组。例如,如果执行以下代码:
mystring="How are you doing today?"print mystring[3]
将会接收到一个错误,如下所示:
awk: string.gawk:59: fatal: attempt to use scalar as array
噢,好吧。虽然不象 Python 的序列类型那样方便,但 awk 的字符串函数还是可以完成任务。让我们来看一下。
首先,有一个基本 length() 函数,它返回字符串的长度。以下是它的使用方法:
print length(mystring)
此代码将打印值:
24
好,继续。下一个字符串函数叫作 index,它将返回子字符串在另一个字符串中出现的位置,如果没有找到该字符串则返回 0。使用 mystring,可以按以下方法调用它:
print index(mystring,"you")
awk 会打印:
9
让我们继续讨论另外两个简单的函数,tolower() 和 toupper()。与您猜想的一样,这两个函数将返回字符串并且将所有字符分别转换成小写或大写。请注意,tolower() 和 toupper() 返回新的字符串,不会修改原来的字符串。这段代码:
print tolower(mystring)print toupper(mystring)print mystring
……将产生以下输出:
how are you doing today?
HOW ARE YOU DOING TODAY?
How are you doing today?
到现在为止一切不错,但我们究竟如何从字符串中选择子串,甚至单个字符?那就是使用 substr() 的原因。以下是 substr() 的调用方法:
mysub=substr(mystring,startpos,maxlen)
mystring 应该是要从中抽取子串的字符串变量或文字字符串。startpos 应该设置成起始字符位置,maxlen 应该包含要抽取的字符串的最大长度。请注意,我说的是最大长度;如果 length(mystring) 比 startpos+maxlen 短,那么得到的结果就会被截断。substr() 不会修改原始字符串,而是返回子串。以下是一个示例:
print substr(mystring,9,3)
awk 将打印:
you
如果您通常用于编程的语言使用数组下标访问部分字符串(以及不使用这种语言的人),请记住 substr() 是 awk 代替方法。需要使用它来抽取单个字符和子串;因为 awk 是基于字符串的语言,所以会经常用到它。
现在,我们讨论一些更耐人寻味的函数,首先是 match()。match() 与 index() 非常相似,它与 index() 的区别在于它并不搜索子串,它搜索的是规则表达式。match() 函数将返回匹配的起始位置,如果没有找到匹配,则返回 0。此外,match() 还将设置两个变量,叫作 RSTART 和 RLENGTH。RSTART 包含返回值(第一个匹配的位置),RLENGTH 指定它占据的字符跨度(如果没有找到匹配,则返回 -1)。通过使用 RSTART、RLENGTH、substr() 和一个小循环,可以轻松地迭代字符串中的每个匹配。以下是一个 match() 调用示例:
print match(mystring,/you/), RSTART, RLENGTH
awk 将打印:
9 9 3
字符串替换
现在,我们将研究两个字符串替换函数,sub() 和 gsub()。这些函数与目前已经讨论过的函数略有不同,因为它们确实修改原始字符串。以下是一个模板,显示了如何调用 sub():
sub(regexp,replstring,mystring)
调用 sub() 时,它将在 mystring 中匹配 regexp 的第一个字符序列,并且用 replstring 替换该序列。sub() 和 gsub() 用相同的自变量;唯一的区别是 sub() 将替换第一个 regexp 匹配(如果有的话),gsub() 将执行全局替换,换出字符串中的所有匹配。以下是一个 sub() 和 gsub() 调用示例:
sub(/o/,"O",mystring)print mystringmystring="How are you doing today?"gsub(/o/,"O",mystring)print mystring
必须将 mystring 复位成其初始值,因为第一个 sub() 调用直接修改了 mystring。在执行时,此代码将使 awk 输出:
HOw are you doing today?HOw are yOu dOing tOday?
当然,也可以是更复杂的规则表达式。我把测试一些复杂规则表达式的任务留给您来完成。
通过介绍函数 split(),我们来汇总一下已讨论过的函数。split() 的任务是“切开”字符串,并将各部分放到使用整数下标的数组中。以下是一个 split() 调用示例:
numelements=split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",mymonths,",")
调用 split() 时,第一个自变量包含要切开文字字符串或字符串变量。在第二个自变量中,应该指定 split() 将填入片段部分的数组名称。在第三个元素中,指定用于切开字符串的分隔符。split() 返回时,它将返回分割的字符串元素的数量。split() 将每一个片段赋值给下标从 1 开始的数组,因此以下代码:
print mymonths[1],mymonths[numelements]
……将打印:
Jan Dec
特殊字符串形式
简短注释 -- 调用 length()、sub() 或 gsub() 时,可以去掉最后一个自变量,这样 awk 将对 $0(整个当前行)应用函数调用。要打印文件中每一行的长度,使用以下 awk 脚本:
{ print length() }
财务上的趣事
几星期前,我决定用 awk 编写自己的支票簿结算程序。我决定使用简单的 tab 定界文本文件,以便于输入最近的存款和提款记录。其思路是将这个数据交给 awk 脚本,该脚本会自动合计所有金额,并告诉我余额。以下是我决定如何将所有交易记录到 "ASCII checkbook" 中:
23 Aug 2000 food    -   -   Y   Jimmy's Buffet      30.25
此文件中的每个字段都由一个或多个 tab 分隔。在日期(字段 1,$1)之后,有两个字段叫做“费用分类帐”和“收入分类帐”。以上面这行为例,输入费用时,我在费用字段中放入四个字母的别名,在收入字段中放入 "-"(空白项)。这表示这一特定项是“食品费用”。 以下是存款的示例:
23 Aug 2000 -   inco    -   Y   Boss Man        2001.00
在这个实例中,我在费用分类帐中放入 "-"(空白),在收入分类帐中放入 "inco"。"inco" 是一般(薪水之类)收入的别名。使用分类帐别名让我可以按类别生成收入和费用的明细分类帐。至于记录的其余部分,其它所有字段都是不需加以说明的。“是否付清?”字段("Y" 或 "N")记录了交易是否已过帐到我的帐户;除此之外,还有一个交易描述,和一个正的美元金额。
用于计算当前余额的算法不太难。awk 只需要依次读取每一行。如果列出了费用分类帐,但没有收入分类帐(为 "-"),那么这一项就是借方。如果列出了收入分类帐,但没有费用分类帐(为 "-"),那么这一项就是贷方。而且,如果同时列出了费用和收入分类帐,那么这个金额就是“分类帐转帐”;即,从费用分类帐减去美元金额,并将此金额添加到收入分类帐。此外,所有这些分类帐都是虚拟的,但对于跟踪收入和支出以及预算却非常有用。
代码
现在该研究代码了。我们将从第一行(BEGIN 块和函数定义)开始:
balance,第 1 部分
#!/usr/bin/env awk -f
BEGIN { FS="\t+"  months="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"}function monthdigit(mymonth) {return (index(months,mymonth)+3)/4}
首先执行 "chmod +x myscript" 命令,那么将第一行 "#!..." 添加到任何 awk 脚本将使它可以直接从 shell 中执行。其余行定义了 BEGIN 块,在 awk 开始处理支票簿文件之前将执行这个代码块。我们将 FS(字段分隔符)设置成 "\t+",它会告诉 awk 字段由一个或多个 tab 分隔。另外,我们定义了字符串 months,下面将出现的 monthdigit() 函数将使用它。
最后三行显示了如何定义自己的 awk 。格式很简单 -- 输入 "function",再输入名称,然后在括号中输入由逗号分隔的参数。在此之后,"{ }" 代码块包含了您希望这个函数执行的代码。所有函数都可以访问全局变量(如 months 变量)。另外,awk 提供了 "return" 语句,它允许函数返回一个值,并执行类似于 C 和其它语言中 "return" 的操作。这个特定函数将以 3 个字母字符串格式表示的月份名称转换成等价的数值。例如,以下代码:
print monthdigit("Mar")
……将打印:
3
现在,让我们讨论其它一些函数。
财务函数
以下是其它三个执行簿记的函数。我们即将见到的主代码块将调用这些函数之一,按顺序处理支票簿文件的每一行,从而将相应交易记录到 awk 数组中。有三种基本交易,贷方 (doincome)、借方 (doexpense) 和转帐 (dotransfer)。您会发现这三个函数全都接受一个自变量,叫作 mybalance。mybalance 是二维数组的一个占位符,我们将它作为自变量进行传递。目前,我们还没有处理过二维数组;但是,在下面可以看到,语法非常简单。只须用逗号分隔每一维就行了。
我们将按以下方式将信息记录到 "mybalance" 中。数组的第一维从 0 到 12,用于指定月份,0 代表全年。第二维是四个字母的分类帐,如 "food" 或 "inco";这是我们处理的真实分类帐。因此,要查找全年食品分类帐的余额,应查看 mybalance[0,"food"]。要查找 6 月的收入,应查看 mybalance[6,"inco"]。
balance,第 2 部分
function doincome(mybalance) {
           mybalance[curmonth,$3] += amount    mybalance[0,$3] += amount}
function doexpense(mybalance) {
           mybalance[curmonth,$2] -= amount    mybalance[0,$2] -= amount}
function dotransfer(mybalance) {
        mybalance[0,$2] -= amount    mybalance[curmonth,$2] -= amount    mybalance[0,$3] += amount    mybalance[curmonth,$3] += amount}
调用 doincome() 或任何其它函数时,我们将交易记录到两个位置 -- mybalance[0,category] 和 mybalance[curmonth, category],它们分别表示全年的分类帐余额和当月的分类帐余额。这让我们稍后可以轻松地生成年度或月度收入/支出明细分类帐。
如果研究这些函数,将发现在我的引用中传递了 mybalance 引用的数组。另外,我们还引用了几个全局变量:curmonth,它保存了当前记录所属的月份的数值,$2(费用分类帐),$3(收入分类帐)和金额($7,美元金额)。调用 doincome() 和其它函数时,已经为要处理的当前记录(行)正确设置了所有这些变量。
主块
以下是主代码块,它包含了分析每一行输入数据的代码。请记住,由于正确设置了 FS,可以用 $ 1 引用第一个字段,用 $2 引用第二个字段,依次类推。调用 doincome() 和其它函数时,这些函数可以从函数内部访问 curmonth、$2、$3 和金额的当前值。请先研究代码,在代码之后可以见到我的说明。
balance,第 3 部分
{    curmonth=monthdigit(substr($1,4,3))    amount=$7        #record all the categories encountered   
     if ( $2 != "-" )        globcat[$2]="yes"
     if ( $3 != "-" )        globcat[$3]="yes"    #tally up the transaction properly
     if ( $2 == "-" )    {  if ( $3 == "-" ) {  print "Error: inc and exp fields are both blank!"  exit 1 }
                                  else { doincome(balance) if ( $5 == "Y" )  doincome(balance2) }
         } else   if ( $3 == "-" ) {  doexpense(balance)  if ( $5 == "Y" ) doexpense(balance2) }
                     else { dotransfer(balance)  if ( $5 == "Y" )  dotransfer(balance2)   }  }
在主块中,前两行将 curmonth 设置成 1 到 12 之间的整数,并将金额设置成字段 7(使代码易于理解)。然后,是四行有趣的代码,它们将值写到数组 globcat 中。globcat,或称作全局分类帐数组,用于记录在文件中遇到的所有分类帐 -- "inco"、"misc"、"food"、"util" 等。例如,如果 $2 == "inco",则将 globcat["inco"] 设置成 "yes"。稍后,我们可以使用简单的 "for (x in globcat)" 循环来迭代分类帐列表。
在接着的大约二十行中,我们分析字段 $2 和 $3,并适当记录交易。如果 $2=="-" 且 $3!="-",表示我们有收入,因此调用 doincome()。如果是相反的情况,则调用 doexpense();如果 $2 和 $3 都包含分类帐,则调用 dotransfer()。每次我们都将 "balance" 数组传递给这些函数,从而在这些函数中记录适当的数据。
您还会发现几行代码说“if ( $5 == "Y" ),那么将同一个交易记录到 balance2 中”。我们在这里究竟做了些什么?您将回忆起 $5 包含 "Y" 或 "N",并记录交易是否已经过帐到帐户。由于仅当过帐了交易时我们才将交易记录到 balance2,因此 balance2 包含了真实的帐户余额,而 "balance" 包含了所有交易,不管是否已经过帐。可以使用 balance2 来验证数据项(因为它应该与当前银行帐户余额匹配),可以使用 "balance" 来确保没有透支帐户(因为它会考虑您开出的尚未兑现的所有支票)。
生成报表
主块重复处理了每一行记录之后,现在我们有了关于比较全面的、按分类帐和按月份划分的借方和贷方记录。现在,在这种情况下最合适的做法是只须定义生成报表的 END 块:
balance,第 4 部分
END { bal=0    bal2=0      for (x in globcat)
         { bal=bal+balance[0,x]        bal2=bal2+balance2[0,x]    }
           printf("Your available funds: %10.2f\n", bal)
           printf("Your account balance: %10.2f\n", bal2)  }
这个报表将打印出汇总,如下所示:
Your available funds:1174.22Your account balance:2399.33
在 END 块中,我们使用 "for (x in globcat)" 结构来迭代每一个分类帐,根据记录在案的交易结算主要余额。实际上,我们结算两个余额,一个是可用资金,另一个是帐户余额。要执行程序并处理您在文件 "mycheckbook.txt" 中输入的财务数据,将以上所有代码放入文本文件 "balance",执行 "chmod +x balance",然后输入 "./balance mycheckbook.txt"。然后 balance 脚本将合计所有交易,打印出两行余额汇总。
升级
我使用这个程序的更高级版本来管理我的个人和企业财务。我的版本(由于篇幅限制不能在此涵盖)会打印出收入和费用的月度明细分类帐,包括年度总合、净收入和其它许多内容。它甚至以 HTML 格式输出数据,因此我可以在 Web 浏览器中查看它。 如果您认为这个程序有用,我建议您将这些特性添加到这个脚本中。不必将它配置成要 记录任何附加信息;所需的全部信息已经在 balance 和 balance2 里面了。只要升级 END 块就万事具备了!
我希望您喜欢本系列。有关 awk 的详细信息,请参考以下列出的参考资料。

参考资料
·如果想看好的老式书籍,O'Reilly 的 sed & awk, 2ndEdition 是极佳选择。
·请参考 comp.lang.awkFAQ。它还包含许多附加 awk 链接。
·Patrick Hartigan 的 awk tutorial 还包括了实用的 awk 脚本。
·Thompson's TAWKCompiler 将 awk 脚本编译成快速二进制可执行文件。可用版本有 Windows 版、OS/2 版、DOS 版和 UNIX 版。
·The GNUAwk User's Guide 可用于在线参考。
关于作者
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo Technologies, Inc. 的总裁兼 CEO,Gentoo Linux(用于 PC 的高级 Linux)和 Portage 系统(Linux 的下一代移植系统)的创始人。他还是 Macmillan 书籍 Caldera OpenLinux Unleashed、SuSE Linux Unleashed 和 Samba Unleashed 的合作者。Daniel 自二年级起就与计算机结下不解之缘,那时他首先接触的是 Logo 程序语言,并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 的首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 和新出生的女儿 Hadassah 一起共度时光。可通过 drobbins@gentoo.org 与 Daniel 联系。

Awk 编程学习笔记之一(原创)

作者:donggua1     发表时间:2002/08/23 05:56pm

Awk 编程实例分析(学习笔记之一)
文件处理
1.有文件gz.txt(工资)
4367422926350133100 张三 1250.00
4367422926351220178 李四 1300.00
4367422926351220546 王二 0
苏五丙 1340.00
4367422926351220178 孙六月 1390.00
…… ……
要求:按账号19位、姓名8位、工资8位来排列,且如姓名不足8位在之后补足,工资不足8位则在工资之前补。同时要求去掉工资为0的名单,没有账号在前补19位空格,并输出工资总数加以核对,处理后应如下排列:
4367422926350133100张三     1250.00
4367422926351220178李四     1300.00
                  苏五丙   1340.00
4367422926351220178孙六月   1390.00
…… ……
awk程序:
#------------------------------------
#shgz1.sh
sblank=" "
awk '$NF!="0"{print $0}' $1 > tmp.txt    #删除工资为0的人数
awk '{                              
if($1!~/[0-9]/){
  printf("%-19.19s%-8.8s%8.2f\n","'"$kk"'",$1,$2)}       #如果没有账号补上空格
else{  
  printf("%-19.19s%-8.8s%8.2f\n",$1,$2,$3)}
}' tmp.txt > $2
awk '$NF~/[0-9]/{
sum=sum+$NF
}
END{
system("rm tmp.txt")
printf("The sum is%16.2f!\n", sum)                #输出工资总数
}' $2
----------------------------------------
本人刚刚开始学awk,也会把自己的一些心得与体会发上来其中尚有许多不成熟的地方,愿得各位指正!
                                                        东瓜

Awk 编程学习笔记之二(原创)


作者:donggua1     发表时间:2002/08/26 02:43pm

2.
有工资文件gz2.txt如下:
姓名    账号                金额
-----------------------------------
张三    43674229263501331001250
李四    43674229263512201781300
王二    43674229263512205460
苏五丙                     1340
孙六月  4367422926351220178390
…… ……
这个工资文件相比以上要特殊一些,首先人名在前,而且账号与金额联在一起,19位账号就是金额。
要求:按账号19位、姓名8位、工资8位来排列,且如姓名不足8位在之后补足,工资不足8位则在工资之前补。同时要求去掉前面两行及工资为0的名单,没有账号在前补19位空格,并输出工资总数加以核对,处理后应如下排列:
4367422926350133100张三     1250.00
4367422926351220178李四     1300.00
                  苏五丙   1340.00
4367422926351220178孙六月    390.00
…… ……
awk程序:
--------------------------------
#shgz2.sh
cut -c1-8 $1>tmp1.txt     #用cut命令分别提出三个字段的内容。
cut -c9-27 $1>tmp2.txt
cut -c28-60 $1>tmp3.txt
paste -d, tmp2.txt tmp1.txt tmp3.txt|tr -d " ">tmp4.txt  #三个文件合一,并用”,”为分隔符
awk -F, '{                   #-F, 表示分隔符为”,”
if ($1~/^2/)
   printf("%-19.19s%-8.8s%8.2f\n",$1,$2,$3)
else
  printf("%-19.19s%-8.8s%8.2f\n","'"$kk"'",$2,$3)}' tmp4.txt > tmp5.txt
sed '/ 0.00$/d' tmp5.txt>$2   #去掉金额为0的行
awk '$NF~/[0-9]/{
sum=sum+$NF
}
END{
system("rm tmp*.txt")                             #删除临时生成的文件
printf("The sum is%16.2f!\n", sum)                #输出工资总数
}' $2
-----------------------------------------------
附注:
  本例中结合了cut,sed与awk的用法!相关命令及参数可参考相关书籍如cut -c1-8 表示提出每一行的1到8位字符.  
  与awk一样,cut也可以按分隔符来分离字段,而且缺省的分隔符为空格,当然也可修改.cut -f1 gz2.txt就会取出姓名这个字段.但是我们可以看到,账号与金额是分不出来的,所以也是我们用cut -c1-8的原因.

论坛徽章:
0
3 [报告]
发表于 2003-05-06 08:22 |只看该作者

Shell/sed/awk学习参考资料[转贴]

通用线程 -- sed 实例,第 1 部分[转帖]


作者:sd-feng     发表时间:2002/11/06 05:02pm

在本文章系列中,Daniel Robbins 将为您演示如何使用功能十分强大(但常被遗忘)的 UNIX 流编辑器 sed。sed 是用批处理方式编辑文件或以十分有效的方式创建 shell 脚本以修改现有文件的理想工具。

挑选编辑器
在 UNIX 世界中有很多文本编辑器可供我们选择。思考一下 -- vi、emacs 和 jed 以及很多其它工具都会浮现在脑海中。我们都有自己已逐渐了解并且喜爱的编辑器(以及我们喜爱的组合键)。有了可信赖的编辑器,我们可以轻松处理任何数量与 UNIX 有关的管理或编程任务。

虽然交互式编辑器很棒,但却有其限制。尽管其交互式特性可以成为强项,但也有其不足之处。考虑一下需要对一组文件执行类似更改的情形。您可能会本能地运行自己所喜爱的编辑器,然后手工执行一组烦琐、重复和耗时的编辑任务。然而,有一种更好的方法。

进入 sed
如果可以使编辑文件的过程自动化,以便用“批处理”方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了。幸运的是,对于这种情况,有一种更好的方法 -- 这种更好的方法称为 "sed"。

sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed 有许多很好的特性。首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍。其次,因为 sed 是一种流编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑。因此,无需将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出到 sed,所以,将 sed 用作强大的 shell 脚本中长而复杂的管道很容易。试一下用您所喜爱的编辑器去那样做。

GNU sed
对 Linux 用户来说幸运的是,最好的 sed 版本之一恰好是 GNU sed,其当前版本是 3.02。每一个 Linux 发行版都有(或至少应该有)GNU sed。GNU sed 之所以流行不仅因为可以自由分发其源代码,还因为它恰巧有许多对 POSIX sed 标准便利、省时的扩展。另外,GNU 没有 sed 早期专门版本的很多限制,如行长度限制 -- GNU 可以轻松处理任意长度的行。

最新的 GNU sed
在研究这篇文章之时我注意到:几个在线 sed 爱好者提到 GNU sed 3.02a。奇怪的是,在ftp.gnu.org(有关这些链接,请参阅参考资料)上找不到 sed 3.02a,所以,我只得在别处寻找。我在alpha.gnu.org 的 /pub/sed 中找到了它。于是我高兴地将其下载、编译然后安装,而几分钟后我发现最新的 sed 版本却是 3.02.80 -- 可在alpha.gnu.org 上 3.02a 源代码旁边找到其源代码。安装完 GNU sed 3.02.80 之后,我就完全准备好了。
正确的 sed
在本系列中,将使用 GNU sed 3.02.80。在即将出现的本系列后续文章中,某些(但非常少)最高级的示例将不能在 GNU sed 3.02 或 3.02a 中使用。如果您使用的不是 GNU sed,那么结果可能会不同。现在为什么不花些时间安装 GNU sed 3.02.80 呢?那样,不仅可以为本系列的余下部分作好准备,而且还可以使用可能是目前最好的 sed。

sed 示例
sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出 (stdout),它不修改任何输入文件。

让我们看一些示例。头几个会有些奇怪,因为我要用它们演示 sed 如何工作,而不是执行任何有用的任务。然而,如果您是 sed 新手,那么理解它们是十分重要的。下面是第一个示例:


$ sed -e 'd' /etc/services



如果输入该命令,将得不到任何输出。那么,发生了什么?在该例中,用一个编辑命令 'd' 调用 sed。sed 打开 /etc/services 文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输出,因为 "d" 命令除去了模式缓冲区中的每一行!

在该例中,还有几件事要注意。首先,根本没有修改 /etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 -- 它不试图修改该文件。第二件要注意的事是 sed 是面向行的。'd' 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将 /etc/services 的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 'd' 命令,然后打印模式缓冲区的内容(在本例中没有内容)。我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 -- 但是,如果不使用地址,命令将应用到所有行。

第三件要注意的事是括起 'd' 命令的单引号的用法。养成使用单引号来括起 sed 命令的习惯是个好注意,这样可以禁用 shell 扩展。

另一个 sed 示例
下面是使用 sed 从输出流除去 /etc/services 文件第一行的示例:


$ sed -e '1d' /etc/services | more



如您所见,除了前面有 '1' 之外,该命令与第一个 'd' 命令十分类似。如果您猜到 '1' 指的是第一行,那您就猜对了。与第一个示例中只使用 'd' 不同的是,这一次使用的 'd' 前面有一个可选的数字地址。通过使用地址,可以告诉 sed 只对某一或某些特定行进行编辑。

地址范围
现在,让我们看一下如何指定地址范围。在本例中,sed 将删除输出的第 1 到 10 行:


$ sed -e '1,10d' /etc/services | more



当用逗号将两个地址分开时,sed 将把后面的命令应用到从第一个地址开始、到第二个地址结束的范围。在本例中,将 'd' 命令应用到第 1 到 10 行(包括这两行)。所有其它行都被忽略。

带规则表达式的地址
现在演示一个更有用的示例。假设要查看 /etc/services 文件的内容,但是对查看其中包括的注释部分不感兴趣。如您所知,可以通过以 '#' 字符开头的行在 /etc/services 文件中放置注释。为了避免注释,我们希望 sed 删除以 '#' 开始的行。以下是具体做法:


$ sed -e '/^#/d' /etc/services | more



试一下该例,看看发生了什么。您将注意到,sed 成功完成了预期任务。现在,让我们分析发生的情况。

要理解 '/^#/d' 命令,首先需要对其剖析。首先,让我们除去 'd' -- 这是我们前面所使用的同一个删除行命令。新增加的是 '/^#/' 部分,它是一种新的规则表达式地址。规则表达式地址总是由斜杠括起。它们指定一种 模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行。

因此,'/^#/' 是一个规则表达式。但是,它做些什么呢?很明显,现在该复习规则表达式了。

规则表达式复习
可以使用规则表达式来表示可能会在文本中发现的模式。您在 shell 命令行中用过 '*' 字符吗?这种用法与规则表达式类似,但并不相同。下面是可以在规则表达式中使用的特殊字符:

字符 描述
与行首匹配
与行末尾匹配
与任一个字符匹配
将与前一个字符的零或多个出现匹配
[ ] 与 [ ] 之内的所有字符匹配

感受规则表达式的最好方法可能是看几个示例。所有这些示例都将被 sed 作为合法地址接受,这些地址出现在命令的左边。下面是几个示例:

规则
表达式 描述
/./ 将与包含至少一个字符的任何行匹配
/../ 将与包含至少两个字符的任何行匹配
/^#/ 将与以 '#' 开始的任何行匹配
/^$/ 将与所有空行匹配
/}^/ 将与以 '}'(无空格)结束的任何行匹配
/} *^/ 将与以 '}' 后面跟有零或多个空格结束的任何行匹配
/[abc]/ 将与包含小写 'a'、'b' 或 'c' 的任何行匹配
/^[abc]/ 将与以 'a'、'b' 或 'c'开始的任何行匹配

在这些示例中,鼓励您尝试几个。花一些时间熟悉规则表达式,然后尝试几个自己创建的规则表达式。可以如下使用 regexp:


$ sed -e '/regexp/d' /path/to/my/test/file | more



这将导致 sed 删除任何匹配的行。然而,通过告诉 sed打印 regexp 匹配并删除不匹配的内容,而不是与之相反的方法,会更有利于熟悉规则表达式。可以用以下命令这样做:


$ sed -n -e '/regexp/p' /path/to/my/test/file | more



请注意新的 '-n' 选项,该选项告诉 sed 除非明确要求打印模式空间,否则不这样做。您还会注意到,我们用 'p' 命令替换了 'd' 命令,如您所猜想的那样,这明确要求 sed 打印模式空间。就这样,将只打印匹配部分。

有关地址的更多内容
目前为止,我们已经看到了行地址、行范围地址和 regexp 地址。但是,还有更多的可能。我们可以指定两个用逗号分开的规则表达式,sed 将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。例如,以下命令将打印从包含 "BEGIN" 的行开始,并且以包含 "END" 的行结束的文本块:


$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more



如果没发现 "BEGIN",那么将不打印数据。如果发现了 "BEGIN",但是在这之后的所有行中都没发现 "END",那么将打印所有后续行。发生这种情况是因为 sed 面向流的特性 -- 它不知道是否会出现 "END"。

C 源代码示例
如果只要打印 C 源文件中的 main() 函数,可输入:


$ sed -n -e '/main[[]]*(/,/^}/p' sourcefile.c | more



该命令有两个规则表达式 '/main[[]]*(/' 和 '/^}/',以及一个命令 'p'。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 "main" 匹配。这应该与一般 ANSI C main() 声明的开始匹配。

在这个特别的规则表达式中,出现了 '[[]]' 字符类。这只是一个特殊的关键字,它告诉 sed 与 TAB 或空格匹配。如果愿意的话,可以不输入 '[[]]',而输入 '[',然后是空格字母,然后是 -V,然后再输入制表键字母和 ']' -- Control-V 告诉 bash 要插入“真正”的制表键,而不是执行命令扩展。使用 '[[]]' 命令类(特别是在脚本中)会更清楚。

好,现在看一下第二个 regexp。'/^}' 将与任何出现在新行行首的 '}' 字符匹配。如果代码的格式很好,那么这将与 main() 函数的结束花括号匹配。如果格式不好,则不会正确匹配 -- 这是执行模式匹配任务的一件棘手之事。

因为是处于 '-n' 安静方式,所以 'p' 命令还是完成其惯有任务,即明确告诉 sed 打印该行。试着对 C 源文件运行该命令 -- 它应该输出整个 main() { } 块,包括开始的 "main()" 和结束的 '}'。

下一篇
既然已经触及了基本知识,我们将在后两篇文章中加快步伐。如果想看一些更丰富的 sed 资料,请耐心一些 -- 马上就有!同时,您可能想查看下列 sed 和规则表达式资源。


通用线程 -- sed 实例,第 2 部分
sed 是十分强大和小巧的文本流编辑器。在本文章系列的第二篇中,Daniel Robbins 为您演示如何使用 sed 来执行字符串替换、创建更大的 sed 脚本以及如何使用 sed 的附加、插入和更改行命令。
sed 是很有用(但常被遗忘)的 UNIX 流编辑器。在以批处理方式编辑文件或以有效方式创建 shell 脚本来修改现有文件方面,它是十分理想的工具。本文是前一篇介绍 sed 文章的续篇。

替换!
让我们看一下 sed 最有用的命令之一,替换命令。使用该命令,可以将特定字符串或匹配的规则表达式用另一个字符串替换。下面是该命令最基本用法的示例:

$ sed -e 's/foo/bar/' myfile.txt
上面的命令将 myfile.txt 中每行第一次出现的 'foo'(如果有的话)用字符串 'bar' 替换,然后将该文件内容输出到标准输出。请注意,我说的是每行第一次出现,尽管这通常不是您想要的。在进行字符串替换时,通常想执行全局替换。也就是说,要替换每行中的所有出现,如下所示:

$ sed -e 's/foo/bar/g' myfile.txt
在最后一个斜杠之后附加的 'g' 选项告诉 sed 执行全局替换。

关于 's///' 替换命令,还有其它几件要了解的事。首先,它是一个命令,并且只是一个命令,在所有上例中都没有指定地址。这意味着,'s///' 还可以与地址一起使用来控制要将命令应用到哪些行,如下所示:

$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt
上例将导致用短语 'entrapment' 替换所有出现的短语 'enchantment',但是只在第一到第十行(包括这两行)上这样做。

$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt
该例将用 'mountains' 替换 'hills',但是,只从空行开始,到以三个字符 'END' 开始的行结束(包括这两行)的文本块上这样做。

关于 's///' 命令的另一个妙处是 '/' 分隔符有许多替换选项。如果正在执行字符串替换,并且规则表达式或替换字符串中有许多斜杠,则可以通过在 's' 之后指定一个不同的字符来更改分隔符。例如,下例将把所有出现的 /usr/local 替换成 /usr:

$ sed -e 's:/usr/local:/usr:g' mylist.txt
在该例中,使用冒号作为分隔符。如果需要在规则表达式中指定分隔符字符,可以在它前面加入反斜杠。

规则表达式混乱
目前为止,我们只执行了简单的字符串替换。虽然这很方便,但是我们还可以匹配规则表达式。例如,以下 sed 命令将匹配从 '<' 开始、到 '>' 结束、并且在其中包含任意数量字符的短语。下例将删除该短语(用空字符串替换):

$ sed -e 's/<.*>//g' myfile.html
这是要从文件除去 HTML 标记的第一个很好的 sed 脚本尝试,但是由于规则表达式的特有规则,它不会很好地工作。原因何在?当 sed 试图在行中匹配规则表达式时,它要在行中查找最长的匹配。在我的前一篇 sed 文章中,这不成问题,因为我们使用的是 'd' 和 'p' 命令,这些命令总要删除或打印整行。但是,在使用 's///' 命令时,确实有很大不同,因为规则表达式匹配的整个部分将被目标字符串替换,或者,在本例中,被删除。这意味着,上例将把下行:

<b>This</b> is what <b>I</b> meant.
变成:

meant.
我们要的不是这个,而是:

This is what I meant.
幸运的是,有一种简便方法来纠正该问题。我们不输入“'<' 字符后面跟有一些字符并以 '>' 字符结束”的规则表达式,而只需输入一个“'<' 字符后面跟有任意数量非 '>' 字符并以 '>' 字符结束”的规则表达式。这将与最短、而不是最长的可能性匹配。新命令如下:

$ sed -e 's/<[^>]*>//g' myfile.html
在上例中,'[^>]' 指定“非 '>'”字符,其后的 '*' 完成该表达式以表示“零或多个非 '>' 字符”。对几个 html 文件测试该命令,将它们管道输出到 "more",然后仔细查看其结果。

更多字符匹配
'[ ]' 规则表达式语法还有一些附加选项。要指定字符范围,只要字符不在第一个或最后一个位置,就可以使用 '-',如下所示:

'[a-x]*'
这将匹配零或多个全部为 'a'、'b'、'c'...'v'、'w'、'x' 的字符。另外,可以使用 '[]' 字符类来匹配空格。以下是可用字符类的相当完整的列表:

字符类 描述
[] 字母数字 [a-z A-Z 0-9]
[] 字母 [a-z A-Z]
[] 空格或制表键
[] 任何控制字符
[] 数字 [0-9]
[] 任何可视字符(无空格)
[] 小写 [a-z]
[] 非控制字符
[] 标点字符
[] 空格
[] 大写 [A-Z]
[] 十六进制数字 [0-9 a-f A-F]

尽可能使用字符类是很有利的,因为它们可以更好地适应非英语 locale(包括某些必需的重音字符等等).

高级替换功能
我们已经看到如何执行简单甚至有些复杂的直接替换,但是 sed 还可以做更多的事。实际上可以引用匹配规则表达式的部分或全部,并使用这些部分来构造替换字符串。作为示例,假设您正在回复一条消息。下例将在每一行前面加上短语 "ralph said: ":

$ sed -e 's/.*/ralph said: &/' origmsg.txt
输出如下:

ralph said: Hiya Jim, ralph said: ralph said:
I sure like this sed stuff! ralph said:
该例的替换字符串中使用了 '&' 字符,该字符告诉 sed 插入整个匹配的规则表达式。因此,可以将与 '.*' 匹配的任何内容(行中的零或多个字符的最大组或整行)插入到替换字符串中的任何位置,甚至多次插入。这非常好,但 sed 甚至更强大。

那些极好的带反斜杠的圆括号
's///' 命令甚至比 '&' 更好,它允许我们在规则表达式中定义区域,然后可以在替换字符串中引用这些特定区域。作为示例,假设有一个包含以下文本的文件:

foo bar oni eeny meeny miny larry curly moe jimmy the weasel
现在假设要编写一个 sed 脚本,该脚本将把 "eeny meeny miny" 替换成 "Victor eeny-meeny Von miny" 等等。要这样做,首先要编写一个由空格分隔并与三个字符串匹配的规则表达式。

'.* .* .*'
现在,将在其中每个感兴趣的区域两边插入带反斜杠的圆括号来定义区域:

'\(.*\) \(.*\) \(.*\)'
除了要定义三个可在替换字符串中引用的逻辑区域以外,该规则表达式的工作原理将与第一个规则表达式相同。下面是最终脚本:

$ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' myfile.txt
如您所见,通过输入 '\x'(其中,x 是从 1 开始的区域号)来引用每个由圆括号定界的区域。输入如下:

Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel
随着对 sed 越来越熟悉,您可以花最小力气来进行相当强大的文本处理。您可能想如何使用熟悉的脚本语言来处理这种问题 -- 能用一行代码轻易实现这样的解决方案吗?

组合使用
在开始创建更复杂的 sed 脚本时,需要有输入多个命令的能力。有几种方法这样做。首先,可以在命令之间使用分号。例如,以下命令系列使用 '=' 命令和 'p' 命令,'=' 命令告诉 sed 打印行号,'p' 命令明确告诉 sed 打印该行(因为处于 '-n' 模式)。

$ sed -n -e '=;p' myfile.txt
无论什么时候指定了两个或更多命令,都按顺序将每个命令应用到文件的每一行。在上例中,首先将 '=' 命令应用到第 1 行,然后应用 'p' 命令。接着,sed 继续处理第 2 行,并重复该过程。虽然分号很方便,但是在某些场合下,它不能正常工作。另一种替换方法是使用两个 -e 选项来指定两个不同的命令:

$ sed -n -e '=' -e 'p' myfile.txt
然而,在使用更为复杂的附加和插入命令时,甚至多个 '-e' 选项也不能帮我们的忙。对于复杂的多行脚本,最好的方法是将命令放入一个单独的文件中。然后,用 -f 选项引用该脚本文件:

$ sed -n -f mycommands.sed myfile.txt
这种方法虽然可能不太方便,但总是管用。

一个地址的多个命令
有时,可能要指定应用到一个地址的多个命令。这在执行许多 's///' 以变换源文件中的字和语法时特别方便。要对一个地址执行多个命令,可在文件中输入 sed 命令,然后使用 '{ }' 字符将这些命令分组,如下所示:

1,20{ s/[Ll]inux/GNU\/Linux/g s/samba/Samba/g s/posix/POSIX/g }
上例将把三个替换命令应用到第 1 行到第 20 行(包括这两行)。还可以使用规则表达式地址或者二者的组合:

1,/^END/{         s/[Ll]inux/GNU\/Linux/g         s/samba/Samba/g         s/posix/POSIX/g p }
该例将把 '{ }' 之间的所有命令应用到从第 1 行开始,到以字母 "END" 开始的行结束(如果在源文件中没发现 "END",则到文件结束)的所有行。

附加、插入和更改行
既然在单独的文件中编写 sed 脚本,我们可以利用附加、插入和更改行命令。这些命令将在当前行之后插入一行,在当前行之前插入一行,或者替换模式空间中的当前行。它们也可以用来将多行插入到输出。插入行命令用法如下:

i\ This line will be inserted before each line
如果不为该命令指定地址,那么它将应用到每一行,并产生如下的输出:

This line will be inserted before each line line 1 here
This line will be inserted before each line line 2 here
This line will be inserted before each line line 3 here
This line will be inserted before each line line 4 here
如果要在当前行之前插入多行,可以通过在前一行之后附加一个反斜杠来添加附加行,如下所示:

i\ insert this line\ and this one\ and this one\ and, uh, this one too.
附加命令的用法与之类似,但是它将把一行或多行插入到模式空间中的当前行之后。其用法如下:

a\ insert this line after each line.  Thanks!
另一方面,“更改行”命令将实际替换模式空间中的当前行,其用法如下:

c\ You're history, original line! Muhahaha!
因为附加、插入和更改行命令需要在多行输入,所以将把它们输入到一个文本 sed 脚本中,然后通过使用 '-f' 选项告诉 sed 执行它们。使用其它方法将命令传递给 sed 会出现问题。

下一篇
在下一篇、也是本 sed 系列的最后一篇文章中,我将为您演示许多使用 sed 来完成不同类型任务的极佳实例。我将不仅为您显示脚本做些什么,还显示为什么那样做。完成之后,您将掌握更多有关如何在不同项目中使用 sed 的极佳知识。到时候见!



通用线程 -- sed 实例,第 3 部分
在这篇 sed 系列的总结性文章中,Daniel Robbins 带您体验 sed 的真正力量。在介绍完几个重要的 sed 脚本之后,他将通过将一个 Quicken .QIF 文件转换成可读文本格式来演示一些基本 sed 脚本的编写。该转换脚本不仅实用,而且还是展现 sed 脚本编写能力的极佳示例。

强健的 sed
在第二篇 sed 文章中,我提供了一些示例来演示 sed 的工作原理,但是它们当中很少有示例能实际做特别有用的事。在这篇 sed 系列的最后文章中,我要改变那种方式,并使用 sed 来做实际的事。我将为您显示几个示例,它们不仅演示 sed 的能力,而且还做一些真正巧妙(和方便)的事。例如,在本文的后半部,将为您演示如何设计一个 sed 脚本来将 .QIF 文件从 Intuit 的 Quicken 金融程序转换成具有良好格式的文本文件。在那样做之前,我们将看一下不怎么复杂但却很有用的 sed 脚本。

文本转换
第一个实际脚本将 UNIX 风格的文本转换成 DOS/Windows 格式。您可能知道,基于 DOS/Windows 的文本文件在每一行末尾有一个 CR(回车)和 LF(换行),而 UNIX 文本只有一个换行。有时可能需要将某些 UNIX 文本移至 Windows 系统,该脚本将为您执行必需的格式转换。

$ sed -e 's/$/\r/' myunix.txt > mydos.txt



在该脚本中,'$' 规则表达式将与行的末尾匹配,而 '\r' 告诉 sed 在其之前插入一个回车。在换行之前插入回车,立即,每一行就以 CR/LF 结束。请注意,仅当使用 GNU sed 3.02.80 或以后的版本时,才会用 CR 替换 '\r'。如果还没有安装 GNU sed 3.02.80,请在我的第一篇 sed 文章中查看如何这样做的说明。

我已记不清有多少次在下载一些示例脚本或 C 代码之后,却发现它是 DOS/Windows 格式。虽然很多程序不在乎 DOS/Windows 格式的 CR/LF 文本文件,但是有几个程序却在乎 -- 最著名的是 bash,只要一遇到回车,它就会出问题。以下 sed 调用将把 DOS/Windows 格式的文本转换成可信赖的 UNIX 格式:

$ sed -e 's/.$//' mydos.txt > myunix.txt



该脚本的工作原理很简单:替代规则表达式与一行的最末字符匹配,而该字符恰好就是回车。我们用空字符替换它,从而将其从输出中彻底删除。如果使用该脚本并注意到已经删除了输出中每行的最末字符,那么,您就指定了已经是 UNIX 格式的文本文件。也就没必要那样做了!

反转行
下面是另一个方便的小脚本。与大多数 Linux 发行版中包括的 "tac" 命令一样,该脚本将反转文件中行的次序。"tac" 这个名称可能会给人以误导,因为 "tac" 不反转行中字符的位置(左和右),而是反转文件中行的位置(上和下)。用 "tac" 处理以下文件:

foo bar oni



....将产生以下输出:

oni bar foo



可以用以下 sed 脚本达到相同目的:

$ sed -e '1!G;h;$!d' forward.txt > backward.txt



如果登录到恰巧没有 "tac" 命令的 FreeBSD 系统,将发现该 sed 脚本很有用。虽然方便,但最好还是知道该脚本为什么那样做。让我们对它进行讨论。

反转解释
首先,该脚本包含三个由分号隔开的单独 sed 命令:'1!G'、'h' 和 '$!d'。现在,需要好好理解用于第一个和第三个命令的地址。如果第一个命令是 '1G',则 'G' 命令将只应用第一行。然而,还有一个 '!' 字符 -- 该 '!' 字符忽略该地址,即,'G' 命令将应用到除第一行之外的所有行。'$!d' 命令与之类似。如果命令是 '$d',则将只把 'd' 命令应用到文件中的最后一行('$' 地址是指定最后一行的简单方式)。然而,有了 '!' 之后,'$!d' 将把 'd' 命令应用到除最后一行之外的所有行。现在,我们所要理解的是这些命令本身做什么。

当对上面的文本文件执行反转脚本时,首先执行的命令是 'h'。该命令告诉 sed 将模式空间(保存正在处理的当前行的缓冲区)的内容复制到保留空间(临时缓冲区)。然后,执行 'd' 命令,该命令从模式空间中删除 "foo",以便在对这一行执行完所有命令之后不打印它。

现在,第二行。在将 "bar" 读入模式空间之后,执行 'G' 命令,该命令将保留空间的内容 ("foo\n" 附加到模式空间 ("bar\n",使模式空间的内容为 "bar\n\foo\n"。'h' 命令将该内容放回保留空间保护起来,然后,'d' 从模式空间删除该行,以便不打印它。

对于最后的 "oni" 行,除了不删除模式空间的内容(由于 'd' 之前的 '$!')以及将模式空间的内容(三行)打印到标准输出之外,重复同样的步骤。

现在,要用 sed 执行一些强大的数据转换。

sed QIF 魔法
过去几个星期,我一直想买一份 Quicken 来结算我的银行帐户。Quicken 是一个非常好的金融程序,当然会成功地完成这项工作。但是,经过考虑之后,我觉得自己可以轻易编写某个软件来结算我的支票簿。我想,毕竟,我是个软件开发人员!

我开发了一个很好的小型支票簿结算程序(使用 awk),它通过分析包含我的所有交易的文本文件的语法来计算余额。略微调整之后,我将其改进,以便可以象 Quicken 那样跟踪不同的贷款和借款类别。但是,我还要添加一个特性。最近,我将帐户转移到一家有联机 Web 帐户界面的银行。有一天,我注意到,这家银行的 Web 站点允许以 Quicken 的 .QIF 格式下载我的帐户信息。我马上觉得,如果可以将该信息转换成文本格式,那就太棒了。

两种格式的故事
在查看 QIF 格式之前,先看一下我的 checkbook.txt 格式:

28 Aug 2000     food    -       -       Y     Supermarket             30.94 25 Aug 2000     watr    -       103     Y     Check 103               52.86



在我的文件中,所有字段都由一个或多个制表符分开,每个交易占据一行。日期之后的下一个字段列出支出类型(如果是收入项,则为 "-")。第三个字段列出收入类型(如果是支出项,则为 "-")。然后,是一个支票号字段(如果为空,则还是 "-"),一个交易完成字段("Y" 或 "N"),一个注释和一个美元金额字段。现在,让我们看一下 QIF 格式。当用文本查看器查看下载的 QIF 文件时,它看起来如下:

!Type:Bank D08/28/2000 T-8.15 N PCHECKCARD SUPERMARKET ^ D08/28/2000 T-8.25 N PCHECKCARD PUNJAB RESTAURANT ^ D08/28/2000 T-17.17 N PCHECKCARD SUPERMARKET



浏览过文件之后,不难猜出其格式 -- 忽略第一行,其余的格式如下:

D<数据>  
T<交易量>  
N<支票号>  
P<描述>  
^   (这是字段分隔符)



开始处理
在处理象这样重要的 sed 项目时,不要气馁 -- sed 允许您将数据逐渐修改成最终形式。在进行当中,可以继续细化 sed 脚本,直到输出与预期的完全一样为止。无需在试第一次时就保证其完全正确。

要开始,首先创建一个名为 "qiftrans.sed" 的文件,然后开始修改数据:

1d /^^/d s/[[]]//g



第一个 '1d' 命令删除第一行,第二个命令从输出除去那些讨厌的 '^' 字符。最后一行除去文件中可能存在的任何控制字符。既然在处理外来文件格式,我想消除在中途遇到任何控制字符的风险。到目前为止,一切顺利。现在,要向该基本脚本中添加一些处理功能:

1d /^^/d s/[[]]//g /^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/         s/^02/Feb/
s/^03/Mar/         s/^04/Apr/
s/^05/May/         s/^06/Jun/
s/^07/Jul/         s/^08/Aug/
s/^09/Sep/         s/^10/Oct/
s/^11/Nov/         s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:  }



首先,添加一个 '/^D/' 地址,以便 sed 只在遇到 QIF 数据字段的第一个字符 'D' 时才开始处理。当 sed 将这样一行读入其模式空间时,将按顺序执行花括号中的所有命令。

花括号中的第一个命令将把如下行:

D08/28/2000



变换成:

08/28/2000OUTYINNY



当然,现在的格式还不完美,但没关系。我们将在进行过程中逐渐细化模式空间的内容。后面 12 行的最后效果是将数据变换成三个字母的格式,最后一行从数据中除去三个斜杠。最后得到这一行:

Aug 28 2000OUTYINNY



OUTY 和 INNY 字段是占位符,以后将被替换。现在还不能确定它们,因为如果美元金额为负,将把 OUTY 和 INNY 设置成 "misc" 和 "-",但是,如果美元金额为正,将分别把它们更改成 "-" 和 "inco"。既然还没有读入美元金额,所以,需要暂时使用占位符。

细化
现在进一步细化:

1d  /^^/d s/[[]]//g  /^D/ {
s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/          s/^02/Feb/
        s/^03/Mar/          s/^04/Apr/
s/^05/May/          s/^06/Jun/
s/^07/Jul/          s/^08/Aug/
s/^09/Sep/          s/^10/Oct/
s/^11/Nov/          s/^12/Dec/
s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
N          N          N         
s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
s/NUMNUM/-/          s/NUM\([0-9]*\)NUM/\1/
s/\([0-9]\),/\1/  }



后七行有些复杂,所以将详细讨论它们。首先,连续使用三个 'N' 命令。'N' 命令告诉 sed 将下一行读入输入中,然后将其附加到当前模式空间。这三个 'N' 命令导致将下三行附加到当前模式空间缓冲区,现在这一行看起来如下:

28 Aug 2000OUTYINNY\nT-8.15\nN\nPCHECKCARD SUPERMARKET



sed 的模式空间变得很难看 -- 需要除去额外的新行,并执行某些附加的格式化。要这样做,将使用替代命令。要匹配的模式为:

'\nT.*\nN.*\nP.*'



这将与后面依次跟有 'T'、零或多个字符、新行、'N'、任何数量的字符、新行、'P'、以及任何数量字符的新行匹配。呀!这个规则表达式将与刚刚附加到模式空间的三行的全部内容匹配。但我们要重新格式化该区域,而不是整个替换它。美元金额、支票号(如果有的话)和描述需要出现在替换字符串中。要这样做,我们用带有反斜杠的圆括号括起那些“感兴趣部分”,以便可以在替换字符串中引用它们(使用 '\1'、'\2\ 和 '\3' 来告诉 sed 将它们插入到何处)。以下是最后的命令:

s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/  



该命令将我们的行变换成:

28 Aug 2000  OUTY  INNY  NUMNUM    Y   CHECKCARD SUPERMARKET AMT-8.15AMT



虽然该行正变得好一些,但是,有几件事一看就有点...啊...有趣。首先是那个愚蠢的 "NUMNUM" 字符串 -- 其目的何在?如果查看 sed 脚本的后两行,就会发现其目的,后两行将把 "NUMNUM" 替换成 "-",而把 "NUM"<number>"NUM" 替换成 <number>。如您所见,用愚蠢的标记括起支票号允许我们在该字段为空时方便地插入一个 "-"。

结束尝试
最后一行除去数字后的逗号。它把如 "3,231.00" 这样的美元金额转换成我使用的格式 "3231.00"。现在,让我们看一下最终脚本:

最终的“QIF 到文本”脚本  1d /^^/d s/[[]]//g /^D/ { s/^D\(.*\)/\1\tOUTY\tINNY\t/
s/^01/Jan/ s/^02/Feb/ s/^03/Mar/ s/^04/Apr/ s/^05/May/
s/^06/Jun/ s/^07/Jul/ s/^08/Aug/ s/^09/Sep/ s/^10/Oct/
s/^11/Nov/ s/^12/Dec/ s:^\(.*\)/\(.*\)/\(.*\):\2 \1 \3:
N N N s/\nT\(.*\)\nN\(.*\)\nP\(.*\)/NUM\2NUM\t\tY\t\t\3\tAMT\1AMT/
s/NUMNUM/-/ s/NUM\([0-9]*\)NUM/\1/ s/\([0-9]\),/\1/
/AMT-[0-9]*.[0-9]*AMT/b fixnegs
s/AMT\(.*\)AMT/\1/ s/OUTY/-/ s/INNY/inco/
b done :fixnegs s/AMT-\(.*\)AMT/\1/ s/OUTY/misc/
s/INNY/-/ :done }



附加的十一行使用替代和一些分支功能来美化输出。首先看一下这行:

        /AMT-[0-9]*.[0-9]*AMT/b fixnegs  



该行包含一个格式为 "/regexp/b label" 的分支命令。如果模式空间与规则表达式匹配,sed 将分支到 fixnegs 标号。您应该可以轻易找到该标号,它在代码中为 ":fixnegs"。如果规则表达式不匹配,则以常规方式继续处理下一个命令。

既然您理解该命令本身的工作原理,让我们看一下分支。如果看一下分支规则表达式,将看到它与后面依次跟有 '-'、任意数量的数字、一个 '.'、任意数量的数字和 'AMT' 的字符串 'AMT' 匹配。就象我确信您已猜到一样,该规则表达式专门处理负的美元金额。在这之前,用 'ATM' 括起美元金额,以便以后可以轻易找到它。因为规则表达式只与以 '-' 开始的美元金额匹配,所以,该分支只在恰巧处理借款时才发生。如果正处理贷款,应该将 OUTY 设置成 'misc',将 INNY 设置成 '-',并且应该除去贷款数量前面的负号。如果跟踪代码的流程,将看到实际情况正是这样。如果不执行分支,则用 '-' 替换 OUTY,用 'inco' 替换 INNY。完成了!现在输出行是完美的:

28 Aug 2000misc--       Y     CHECKCARD SUPERMARKET  -8.15



别犯糊涂
如您所见,只要循序渐进地解决问题,使用 sed 转换数据就没有那么难。不要试图使用一个 sed 命令或一下子解决所有问题。相反,要朝着目标逐步进行,并不断改进 sed 脚本,直到其输出正如您希望那样为止。sed 有许多功能,希望您已非常熟悉其内部工作原理并继续努力以进一步掌握它!


参考资料
有关 sed:

阅读developerWorks 上 Daniel 的其它 sed 文章:通用线程:sed 实例,第 2 部分和第 3 部分。
查看 Eric Pement 极佳的sed FAQ。
可以在ftp://ftp.gnu.org/pub/gnu/sed 找到 sed 3.02 资源。
将在alpha.gnu.org 找到很好的新的 sed 3.02.80。
另外,Eric Pement 还有一些方便的sed 单行程序,任何有抱负的 sed 高手都应该看一下。
如果想看好的老式书籍,O'Reilly 的sed & awk, 2nd Edition 将是极佳选择。
可能想阅读7th edition UNIX's sed man page(大概 1978!)。
阅读 Felix von Leitner 短小的sed tutorial。
在developerWorks 上阅读 David Mertz 的 "Text processing in Python"。

论坛徽章:
0
4 [报告]
发表于 2003-05-06 08:24 |只看该作者

Shell/sed/awk学习参考资料[转贴]

shell 1
目标:完成这一章,你能做以下事情:
·写出基本的shell程序
·通过环境变量传递参数给shell程序
·通过位置参数传递参数给shell程序
·使用特殊shell变量,*和#
·使用shift和read命令

1.1 shell编程概述·shell程序是一个包含UNIX命令的普通文件。
·这个文件的许可权限至少应该为可读和可执行。
·在shell提示符下键入文件名就可执行shell程序。
·数据可以通过三种方式传送到shell程序??
        -环境变量??
        -命令行参数??
        -用户的输入shell是一个命令解释器,它会解释你在命令提示符下输入的命令。
但是,你可能有一组想要多次执行的命令。shell提供了一种功能,让你将这组命令存放在一个文件中,然后你可以象unix系统提供的其他程序一样执行这个文件。
这个命令文件就叫做shell程序或者shell脚本。当你运行这个文件,它会象你在命令行输入这些命令一样地执行这些命令。
为了让shell读取你的shell程序并且执行,shell必须能够读取并能够执行每一行命令。因此,shell脚本的许可权限必须被设置为可读和可执行。为了让shell可以找到你的程序,你可以选择输入完全路径名,或者将这个脚本的路径放在于你的PATH环境变量指定的路径列表中。许多的用户会在他们的HOME目录下创建一个bin目录来存放他们自己开发的script,然后将$HOME/bin加入到他们的PATH环境变量中。
你可以写出非常复杂的shell脚本,因为shell支持变量,命令行参数,交互式输入,tests(判断)),branches(分支),和loops(循环)。

1.2 shell程序举例
$ cat myprog
#this is the program myprog
date
ls –F
$ myprog
  要创建一个shell程序,考虑进行以下步骤:
$ vi myprog ?????一个包含shell命令的程序。
#this is the program myprog
date
ls –F$
chmod +x myprog 增加文件的执行模式
$ myprogThu Jul 11 11:10 EDT 1994F1 f2 memo/ myprog*
首先使用一个文本编辑器来创建一个shell程序myprog。
在程序执行之前,这个文件必须被赋予可执行的权限。然后在命令提示符下输入这个程序名。如上例所示,当myprog执行的时候,一个子shell会被创建。这个子shell会从shell程序文件myprog读取输入而不是从命令行。这个shell中的每个命令的执行都会创建一个子shell。
一旦所有的命令都被执行,所有的子shell会中止,然后会返回到原始的父shell。
Shell程序中的注释:推荐在shell程序中提供注释语句来注明程序的内容。注释由一个#符号开始。Shell不会去执行任何在#之后的语句。#能够出现在命令行的任何位置。注意:你不可以给shell程序取名为test因为test是一个内部的shell命令。

1.3 传递数据给shell程序
$ color = lavender
$ cat color1
echo you are now running program: color1
echo the value of the variable color is color$
chmod +x color1
$ color1you ar now running program : color1
the value of the variable color is :
$ export color
$color1 you are now running program : color1
the value of the variable color is : lavender
传递数据给shell脚本的一种方法就是通过环境。
在上例中。本地变量color被赋值为lavender。然后创建了shell程序color1;然后更改为可执行权限;然后这个shell程序被执行。color1试图回送color变量的值。
但是,由于color是一个本地变量,属于父shell私有的,运行color1产生的子shell不能识别这个变量,因此不能打印出它的值。当color被输出到环境中,它就可以被子shell读取。同样,由于shell进程不能够更改父进程的环境,对一个子进程中的环境变量重新赋值不会影响到父进程环境中的值。如以下的shell脚本中的color2。
echo The original value of the variable color is $colorech0
This program will set the value of color to ambercolor=amberecho
The value of color is now $colorecho
When your program concludes,display the value of the color variable
观察在你设置了color的值后有什么变化。输出这个变量,然后执行
color2:
$ export color=lavender
$ echo $colorlanvender
$ color2The original value of the variable color is lavender
The program will set the value of color to amber
The value of volor is now amber
When your progam concludes, display the value of the color variable,
$ echo $colorlanvender

1.4 shell 程序的参数命令行:?
$ sh_program arg1 arg2 . . . argx???$0 ???$1?? $2 ....?
$X例子:
$ cat color3echo you are now running program:
$0echo The value of command line argument \#1 is: $1
echo The value of command line argument \#2 is : $2
$ chmod +x color3
$ color3 red green You are now running program: color3
The value of command line argument #1 is : red
The value of command line argument #2 is: green
大多数的UNIX系统命令可以接收命令行参数,这些参数通常告诉命令它将要操作的文件或目录(cp f1 f2),另外指定的参数扩展命令的能力(ls –l),或者提供文本字符串(banner hi there)命令行参数同样对shell程序有效。这在于传送信息给你的程序的时候十分方便。
通过开发一个接收命令行参数的程序,你可以传递文件或者目录命令名给你的程序处理,就像你运行UNIX系统命令一样。
你也可以定义命令行选项来让命令行使用shell程序额外的功能。
在shell程序中的命令行参数与参数在命令行的位置相关。这样的参数被称为位置参数,因为对每一个特殊变量的赋值依靠一这些参数在命令行中的位置。这些变量的变量名对应它们在命令行中的数字位置,因此这些特殊的变量名为数字0,1,2等等,一直到最后的参数被传递。
变量名的存取通过同样的方法,在名字前面加上$ 符号,因此,为了存取你的shell程序中的命令行参数,你可以应用$0,$1,$2等等。在$9以后,必须使用括号:$(10),$(11),否则,shell会将$10看成是$1后面跟一个0。$0会一直保存程序或命令的名字

1.4 shell程序的参数(继续)   
以下的shell程序会安装一个程序,这个程序作为一个命令行参数被安装到你的bin目录:首先创建程序my_install,注意目录$HOME/bin应该预先存在。
$ cat > my_install
echo $0 will install $1 to your bin directory
chmod +x $1
mv $1 $HOME/binecho Installation of $1 is complete
ctrl + d
$ chmod +x my_intalll
$ my_install color3my_install will install color3 to your bin directory
Installation of color3 is complete
$
这个例子中,一个程序指明第一个命令行参数为一个文件名,然后加上执行权限,然后移动到你当前目录下的bin目录下。记住UNIX系统的惯例是存贮程序在一个叫做bin的目录下。你也许想要在你的HOME目录下创建一个bin目录,在这个目录下你可以存储你的程序文件,记住要将你的bin目录放在PATH环境变量中,这样shell才会找到你的程序。

1.5 一些特殊shell变量- #和*  # ???命令行参数的数量* ???完全的参数字符串例子:
$ cat color4echo There are
$#?comand line argumentecho They are
$*ehco The first command line argument is $1
$ chmod +x color4
$ color4 red green yellow blueThey are 4 command line arguments
They are red green yellow blueThe first command line argument is red
$
至今为止我们看到的shell程序都不是很灵活。color3需要两个正确的参数,而my_install只需要一个。通常在你创建一个接收命令行参数的shell程序的时候,你想要用户输入一个参数的变量号码。你同时要程序执行成功,不管用户键入1个参数或是20个参数。
当处理变量参数列表的时候,特殊shell变量会提供你许多的灵活性。通过$#你可以知道有多少参数已经被输入,通过$*可以存取全部的参数列表,而不管参数的数量。
请注意参数($0)不在$*这个参数列表里。每一个命令行参数都是互相独立的,你可以通过$*集中检索这些参数,也可以通过$1,$2,$3等等来独立的检索这些参数。
1.5 一些特殊的shell变量-#和*(继续) 一个可以接收多个命令行参数的安装程序的例子:  
$ cat > my_install2
echo $0 will install $# files to your bin directory
echo The files to be installed are : $*
chmod +x $*
mv $* $HOME/bin
echo Installaton is complete
ctril + d
$ chmod +x my_install2
$ my_install2 color1 color2my_intall2 will install 2 files to your bin directory
The files to be installed are: color1,color2Intallaiton is complete
这个安装程序更加灵活。如果你有几个脚本要安装,你仅仅需要执行这个程序一次,只要输入多个名字即可。非常重要的是:如果你计划传递整个参数的字符串给一个命令,这个命令必须能够接收多个参数。在以下的脚本中,用户提供一个目录名作为一个命令行参数。程序会更改到指定的目录,显示当前的位置,并且列出内容。
$ cat list_dir cd $*
echo You are in the $(pwd) directory
echo The contents of the directory are:ls –F
$ list_dir dir1 dir2 dir3
sh: cd: bad argument count由于cd命令不能同时进入到多个目录中,这个程序会发生错误。

1.6 shift 命令 ·向左移动所有的在*中的字符串n个位置·#的数目减少n个(n的默认值是1)
语法:shift [n]例子:
$ cat color5orig_args=$*
echo There are $# command line arguments
echo They are $*
echo Shifting two arguments
shift 2
echo There are $# comand line arguments
echo They are $*echo Shifting two arguments
shift 2; final_args=$*
echo Original arguments are: $orig_args
echo Final arguments are: $final_args
shift命令会重新分配命令行参数对应位置参数,在shift n以后,所有的*中的参数会向左移动n个位置。同时#会减n。默认的n为1。Shift命令不会影响到参数0的位置。
一旦你完成一次移动,被移动出命令行的参数会丢失。如果你想在你的程序中引用这个参数,你需要在执行shift之前存贮这个参数。Shift命令被用在:·存取一组参·数的位置,例如一系列的x,y的坐标·从命令行删除命令选项,假定选项在参数之前。·例子:
$ color5 red green yellow orange blackThere are 6 command line arguments
They are red green yellow blue orange black
Shifting two argumentsThere are 4 command line arguments
They are yellow blue orange black
Shiftging two arguments
Original arguments are: red green yellow blue orange black
Final argument are : orange black$

1.7 read 命令语法:
read variable [variable......]
例子:
$ cat color6
echo This program prompts for user input
echo “please enter your favorite two colors -> \c”
read color_a color_b
echo The colors you entered are: $color_b $color_a$
chmod +x color6
$ color6
This program prompts for user input Please enter your favorite two colors -> red blueThe colors you entered are: blue red
$ color6
This program prompts for user inputPlease enter you favorite two colors -> red blue tanThe color you enterd are :blue tan red
用户使用命令行参数传递信息进程序,在命令执行之前,用户必须知道正确的语法。有一种情况,你想要在用户执行程序的时候提示他输入这些参数。
read命令就是用来在程序执行的时候收集终端键入的信息。你通常会想要使用echo命令来提供用户一个提示,让他知道程序正在等待一些输入,同时通知用户应该输入的类型。因此,每一个read命令应该在echo命令前面。
read命令会给出一个变量名的列表,这些变量会被用户在提示符下输入的词赋值。(以空格分隔)。如果read命令定义的变量比输入的词要多,剩余变量会被赋空值。如果用户输入的词要比变量多,剩余的数据会赋给列表中的最后一个变量。一旦被赋值,你就可以象其他的shell变量一样存取这些变量。注意:不要混淆位置参数和变量read。
位置参数在命令被激活时在命令行中定义read命令给变量赋值是在程序执行之中,通过对输入提示的响应而给变量赋值。 1.7 read命令(继续)以下例子提示用户输入要被安装的文件名:
$ cat > my_install3
echo $0 will install files into your bin directory
echo “Enter the names of the files -> \c”
read filenames
mv $filenames $HOME/bin
echo Instllation is complete
ctrl + d
$ chmod +x my_install13
$ my_install13my_install13 will install files into your bin directory
Enter the names of the files -> f1 f2Installaton is complete
这个安装会提示用户输入chmod和移动到$HOME/bin的文件名。这个程序给用户更多的关于应该输入数据情况的指引。而不像install2中用户必须在命令行中提供文件名。用户使用程序不需要特殊的语法。程序让用户确切地知道要输入什么。所有的输入的文件名都会被赋值给变量filenames。

1.8 另外的技术·#号开始的文档为注释部分。
·sh shell_program argumetns?shell_program 的属性可以不是可执行的。shell_program 必须是可读的。·sh –x shell_program arguments每一行在被执行前被打印出来。 在调试程序时有用处。
在shell程序中,#符号被用来提供一段注释。shell会忽略#符号后边的字符,直到一个回车符号为止。 执行一个shell程序的另外一种方法是:sh shell_program arguments这种方式激活一个子shell并且指定这个子shell为执行这个程序的命令解释器。这个程序文件不是必须为可执行的。
这种方式的用途在:你正在在一种shell下工作,同时想要执行用其他shell命令语言写的shell程序十分有用。你也可以在你的shell程序的第一行前加入#!/usr/bin/ shell_name来指定命令行解释器。因此,如果你当前正在POSIX shell下工作,但是想要执行一个C shell的脚本,你的C shell程序的第一行应该为:#!/usr/bin/csh虽然shell程序没有调试器,命令:sh –x shell_program arguments会在执行每一行时,先在屏幕上打印出shell程序的每一行。这允许你看到shell如何进行文件名产生,变量替代,和命令替代。这个选项对发现打字错误十分有帮助。

shell 2

作者:socketstrem     发表时间:2002/09/12 04:35pm
Shell编程-分支语句(1)
目标:
完成这一章,你将能够作以下事情:
描述条件选择语句中返回值的作用。
使用test命令来分析一个命令的返回值。
在shell程序中使用if和case结构。
1.返回值
shell变量“?”中保存上一个被执行命令的返回值:
0: ??命令成功地执行(真)
非零:?命令由于出现错误而被终止(假)

例子:
$ true ??????????$ false
$ echo $? ??? ???? $ echo $?
0 ???????????? 1
$ ls ???????????$ cp
$ echo $? ??????? ?Usage: cp f1 f2?
0???????????????? cp [-r] f1 ....fn d1
$ echo $??????????$echo $?
0????????????? 1
???????????? ? $echo $?
????????????? 0

所有的UNIX操作系统命令在结束的时候都要产生一个返回值。这个返回值通常被用来判断命令是正常结束(返回0)还是遇到了一些错误(返回非零值)。通过返回的非零值通常可以看出发生的是什么错误。例如,语法错误通常返回1,true命令返回的就是0,而false命令返回的是1。
大多数的shell程序中的判断语句都是通过分析这个返回值来进行流程控制的。shell中定义了一个特殊的变量“?”来保存上一个命令结束后的返回值。
你可以通过以下方式来观察前一个命令的返回值:
echo $?
当你执行一个条件判断(小于,大于,等于)的时候,返回值会指明这个条件是否为真(返回0)或者为假(返回非零)。
条件判断语句会在下几节中讲述。

2.test 命令
语法:
test expression 或者 [expression]
test命令对表达式进行评估,并且设置返回值。
表达式的值 ??返回值
true??????0
false????? 非零(通常为1)
test命令能够评估的对象有:
整数
字符串
文件
test命令被用来评估表达式并且产生返回值。它用参数组成逻辑表达式并且对表达式进行评估。test命令不会产生标准输出。你必须必须通过返回值来判断test命令的结果。如果表达式为真,返回值会为0,如果表达式为假,返回值为1。
test命令可以被单独使用,然后你能够看到返回值,但它用的最多的还是在if和while结构中,用来提供条件流程控制。
test命令的也可以用[expression]来代替。这对提高可读性有帮助,特别是在处理数字或者字符串的时候。
注意:在"["和"]"符号的周围必须要有空格。

3.test命令之数字test
语法:
[ number relation number ]???通过关系运算符来对数字进行比较
关系运算符:
-lt ???   小于
-le ??? 小于或者等于
-gt???  大于
-ge ???大于或者等于
-eq ???等于
-ne ???不等于

例子(假设X=3):
$ [ "$X" -lt 7]???$ [ "$X" -gt 7]
$ echo $? ????? $ echo $?
0 ????????? 1

test命令能被用于比较两个整数之间的数字关系。通常用[.....]语法来调用。test命令的返回值就能说明这个条件为真还是为假。

当testing一个变量的值的时候,你应该防止变量不要为空值,例如:
$ [ $XX -eq 3]

sh: test:argument expected
如果变量XX在前面没有被赋值,XX的值会是NULL。当shell执行变量替代的时候,shell会试图执行如下语句:

[ -eg 3]
而这个语句不是一个完整的test语句,并且会导致一个语法错误。解决这个问题的一个简单的方法就是在被测试的变量的周围加上引号。

[ "$XX" -eq 3]
当shell执行变量替代的时候,shell会试图执行如下语句:

["" -eq 3]
这会确保至少有一个NULL值作为一个参数提供给这个test命令使用。
注意:作为一个通用的规则,你应该在所有的$变量加上双引号来避免shell进行不正确的变量的替代。

4.test命令之字符串test
语法:
[ string1 = string2] 判断字符串是否相等
[ string1 !=string2] 判断字符串是否不等

例子;
$ X=abc ?????????$ X=abc
$ [ "$X" = "abc"]????        $ ["$X" ?!= "abc"]
$ echo $? ???????? $ echo $?
0???????????? 1

test命令也能够用来计较两个字符串是否相等。
[...] 语法通常用作字符串的比较。你已经看到在[]周围必须要有空格,同时在操作符周围也必须要有空格存在。

字符串操作包括:
string1 = string2 ????  如果string1等于string2就为真
string1 != string2 ???     如果string1不等于string2就为真
-z string???????? 如果string的长度为0就为真
-n string???????? 如果string的长度为非零就为真
string ?????????如果string的长度为非零就为真

如果变量中包含空白字符,在这里引号同样也能够保护字符串的test,,例如:
$ X="yes we will"
$ [ $X=yes] ???会导致一个语法错误
shell会解释这个语法为[yes we will = yes ]
$ [ "$x" = yes ] ?正确的语法
shell会解释这个语法为:[ "yes we will" = yes ]
在执行数字比较的时候,shell会将所有的参数当成是数字,在执行字符串比较的时候,shell会把所有的参数当成是字符串。如下例所示:

$ X=03
$ Y=3
$ [ "$X" -eq "$Y" ]??? 比较数字03和数字3
$ echo $?
0????????????为真-它们是相等的数字
$ [ "$X" = "$Y" ]????比较字符串“03”和字符串“3”
$ echo $?
1????????????为假-它们是不相同的字符串

5.test命令-文件比较
语法:
test -option filename???通过选项对文件进行test

例子:
$ test -f funfile
$ echo $?
0
$ test -d funfile
$ echo $?
1

shell提供的一个有用的test特性是可以用它来test文件的特征,例如文件类型和许可权限。例如:
$ test -f filename
如果文件存在并且是一个普通文件(不是目录或者设备文件),会返回真(0)。

test -s filename
如果文件存在并且其字节数大于0,会返回真(0)。
其它还有许多有用的文件test方式,比如:
-r file????如果文件存在并且是可读的时候为真
-w file ??? 如果文件存在并且是可写的时候为真
-x file ??? 如果文件存在并且是可执行的时候为真
-d directory ?目录存在并且是个目录的时候为真

6.test命令-其他操作符
语法:
-o ????OR
-a ????AND
\( ?\) ? GROUPING
例子:
$ [ "$ANS" = y -o "ANS' = Y ]
$ [ "$NUM = -gt 10 -a "$NUM" -lt 20 ]
$ test -s file -a -r file

注意:()前面必须要用斜杠。

使用Boolean操作符可以同时测试多个条件。

例子:
$ [ "$ANS" = y -o "$ANS" = Y ]
$ [ "$NUM" -gt 10 -a "$NUM" -lt 20 ]
$ test -s file -a -r file -a -x file
NOT操作符(!)被用作连接其他的操作符,特别是在文件test的时候用的很普遍。在!操作符和其他的操作符之间必须要有空格,例如:

test ! -d file
能够用来代替
test -f file -o -c file -o -b file ....

括号被用来对操作符进行分组,但是在shell中括号还有一个特殊的意义就是优先运算的意义。因此,括号前面必须使用\符号来忽略其原有含义。

以下的命令验证:有两个命令行参数,并且第一个命令行参数是一个-m ,并且最后一个命令行参数是一个目录或者是一个字节数大于0的文件:
[ \( $# = 2 \) -a \( "$1" = "-m" \) -a \( -d "$2" -o -s "$2" \) ]?

7.exit命令
语法:
?exit [arg]
例子:
$ cat exit_test
echo exiting program now
exit 99
$ exit_test
exiting_program now
$ echo $?
99

exit命令会结束一个shell程序的执行并且设置返回值。通常0值被用来说明正常结束,而非0值用来说明一个错误的条件。如果没有特别指明返回值,返回值将被设置为exit命令上一个命令的返回值。

shell 3
作者:socketstrem     发表时间:2002/09/12 04:36pm

shell编程-分支语句(2)
8.if语句
语法:(用于单向判断分支)
if??list A
then
??list B
fi

例子:
if
??test -s funfile
then
??echo funfile exists
fi
echo hello

if 结构提供了一种基于命令返回值的的流程控制。如果指定的命令的返回值为0,一个指定的命令列表就会被执行,如果用于判断的命令返回值为非0,指定命令列表会被忽略而不被执行。
上例中表明了if结构的一个通用的格式。每一个命令列表由一个或者多个UNIX系统的shell命令组成,每个命令之间用回车符或者分号分隔。list A中最后被执行的命令决定if语句的结果。
if结构执行的过程如下所示:
1.list A命令被执行。
2.如果list A中的最后一个命令的返回值为0(真),执行list B中的命令,然后继续执行fi以后的命令。
3.如果list A中的最后一个命令的返回值为非0(假),跳到fi并且继续执行fi以后的命令。
test命令通常被用作流程控制,它可以使用任何的UNIX命令,因为所有的UNIX命令都产生一个返回值,以下的例子可以说明:

if
??grep kingkong /etc/passwd > /dev/null
then
??echo found kingkong
fi
if结构也能在程序出错的时候提供流程控制。如下例所示:
if
??[ $# -ne 3 ]
then
??echo Incorrect syntax
??echo Usage: cmd arg1 arg2 arg3
??exit 99
fi

9.if-else 结构
语法:(用在多分支选择的情况)
if
??list A
then
??list B
else
??list C
fi
例子:
if [ "$X" -lt 10 ]
then
?echo X is less than 10
else
?echo X is not less than 10
fi

if-else结构让你能够在控制命令的返回值为0的情况下执行一系列的命令,或者在控制命令的返回值为非0的情况下执行另外一系列的命令。

这种情况下if结构的执行过程是:
1.执行list A中的命令。
2.如果在list A中最后一个命令的返回值是0(真),执行list B中的命令,然后继续执行fi以后的命令。
3.如果list A中最后一个命令的返回值为非0(假),执行list C中的命令,然后执行fi以后的命令。
注意在list C中可以包含任何的UNIX命令,其中也包括if。例如:
if
??[ "$X" -lt 10 ]
then
??echo X is less than 10
else
??if
????[ "$X" -gt 10 ]
??then
????echo X is greater than 10
??else
????echo X is equal to 10
??fi
fi
注意:每一个if必须要有一个fi来结束。

10.case结构
语法:(多路分支)
?case word in
?patterm1) list A
???????;;
?pattern2)?list B
???????;;
?patternN) ?list N
??????? ;;
?esac
例子:
case $ANS in
??yes) echo O.K
?????;;
?? no) echo no go
?????;;
esac

if-else结构也能支持多路的分支,但是当有两个或者三个分支的之后,程序会变得十分烦琐。case结构提供了实现多路分支的一种更方便的方法。分支的选择是基于顺序的对一个word和提供的参数之间的比较结果。这些比较是是严格的基于字符串的对比。当一个匹配发现的时候,对应的命令就会被执行。每个命令的列表都以两个分号结束。在完成了相关的比较之后,程序会在esac之后继续执行下去。
word典型的情况下是指向一个shell变量。
pattern的组成格式和文件名的生成原则是一致的。
以下是一些pattern允许的特殊的字符:
* ???匹配任何字符串和字符包括空字符
? ???匹配任何单个的字符。
[...] ?匹配任何一个括号出现中的字符
另外|字符的意义是OR。
注意:在这个结构中的右括号和分号是必须的。
case结构通常被用于菜单选择或者是需要对几个用户输入选项作出决定的时候。

12.shell编程-分支-总结
返回值???每一个程序的返回值 - echo $?
数字test??[ "$num1" -lt "$num2" ]
字符串test?[ $string1 = $string2 ]
文件test ? test -f filename
exit n ?? 终止程序的允许并且设置返回值
if ???????????????????case word in
???command listA???????????pattern1) command list
then?????????????????? ;;
???command listB???????????pattern2) command list
else?????????????????? ;;
???command listC???????????*) ????command list
fi ???????????????????;;
???????????????????? esac
执行那个语句基于listA中最后一条?????字符串word会与每一个pattern比较
命令的返回值

shell 4

作者:socketstrem     发表时间:2002/09/12 04:37pm
shell编程之循环语句(1)
目标:
完成这一章,你将能够作以下事情:
使用while语句在条件为真的时候重复地执行一段代码。
使用until语句重复执行一段代码直到条件为真。
使用交互性的for语句进行循环控制。

1.循环的简单介绍
目标: ????重复的执行一段命令列表。
控制; ????基于一个关键命令的返回值。
三种格式: ??while ... do ... done
??????? until ... do ... done
??????? for ... do ... done

循环语句让你可以重复执行一个命令列表,而决定是继续循环还是跳出循环是基于一个命令的返回值。test命令常常被用来控制一个循环是否继续。
与分支语句不同的是,在分支语句中开始一个分支语句的关键字和结束一个分支语句的关键字是相反的(if/fi 和case/esac),循环语句由一个关键字和一些条件开始,循环体整个用do/done来包围起来。
2.使用let来进行算术计算
语法:
let expression or (( expression ))
例子:
$ x=10??????????$ x=12
$ y=2?????????? $ let "x <10"???
$ let x=x+2 ???????$ echo $?
$ echo $x???????? 1
12????????????$ (( x > 10 ))
$ let "x = x / (y+1)" ??$ echo $?
$ echo $x ????????$ 0
4???????????? $ if ((x > 10 ))
$ (( x = x + 1 ))???? > ?then echo x greater
$ echo $x???????? > ?else echo x not greater
5???????????? fi
?????????????x greater
循环语句通常使用一个增长的数字变量来进行控制。使用let命令,可以在shell脚本中使用算术表达式。这个命令允许使用长的整数运算。在上例中,expression代表一个shell变量的算术表达式和能够被shell识别的操作符,而(( ))可以 代替let命令。shell能够识别的表达式如下所示:
操作符?????描述
-????? ??减去
!????? ??逻辑相反
* / %????? 乘,除,余数
+ - ??????加,减
<= ?>= ?<?>?关系比较
== !=????? 等于不等于
=??????? 赋值
括号能够被用作改变表达式中计算的顺序,就像在
let "x=x/(y+1)"
中一样
注意双引号被用来忽略括号的特殊含义。同样,如果你希望使用空格来分隔操作符和操作符的时候,就必须使用双引号,或者(( ))语句:
let " x = x + (y / 2)" 或者(( x= x+ (y / 2) ))
当使用逻辑和关系操作符,(!,<=,>=,<,>,++,~=),的时候,shell会返回一个代码变量,?会反映结果是真还是假,再一次说明,必须使用双引号来防止shell将大于和小于运算符当作I/O重定向。

3.while语句
重复执行循环体,直到条件为真
语法:
while??????????????
?? list A
do
?? list B
done
例子:
$ cat test_while
X=1
while (( X <= 10 ))
do
??echo hello X is $X
??let X=X+1
done
$ test_while

hello X is 1
hello X is 2
.
.
.
hello X is 10

while语句是shell提供的一种循环机制,当条件为真的时候它允许循环体中的命令(list B)继续执行。条件判断是通过list A中最后一个命令的返回值来进行。通常一个test或者let命令被用作控制循环的执行,但是任何命令都能被用来产生一个返回值。
上面的例子中使用的是test命令可以用let命令来代替,如下:

$ X=1
$ while [ $x -le 10 ]
> do
> ??echo hello X -s $X
>?? let X=X+1
> done

命令执行的过程如下:
1.list A中的命令被执行。
2.如果list A中的最后一个命令的返回值为0(真),执行list B。
3.回到第一步。
4.如果list A中的最后一个命令的返回值不为0(假),跳到下面done关键字后的第一个命令。
提醒:注意while循环会无穷执行下去,因为有一些循环的控制命令的返回值永远为真。

$ cat while_infinite
while true
do
?? echo hello
done
$ while_infinite

hello
hello
.
.
.
ctrl + c
4. while结构举例
例A??????????????例B
如果ans为yes就重复执行 ???????????当有命令行参数时重复执行

ans=yes???????????????while (($# != 0 ))
while ??????????????? do
[ "$ans" = yes ] ???????????if test -d $1
do ??????????????????then

??echo Enter a name??????????echo contents of $1;
??read name??????????????ls -F $1
??echo $name >> file.names????? fi
??echo "Continue?"????????? shift
??echo Enter yes or no??????? echo There are $# items
??read ans????????????? echo left on the cmd line
done ????????????????done?


上例是两个while语句的例子,例A提示用户输入,然后对用户的输入进行判断,来决定是否继续执行循环,例子B中,循环会对命令行中每一个参数进行判断,如果参数是一个目录,这个目录中的内容会被显示出来。如果这个参数不是一个目录,程序会跳过。注意shift命令的用法,它允许一个一个存取每一个参数。和while命令一起使用可以使这个循环非常灵活。它不关心参数的个数是1个还是100个,循环会继续执行直到所有的参数都被存取。

注意,如果你希望循环至少执行一次,就必须进行某些设置。例A中会执行循环体至少一次因为ans已经被设为yes。在例B中,如果程序的执行不带任何参数($#等于0),这个循环就一次都不会执行。


5.until语句

重复循环直到条件为真为止。

语法:??????例子:

until???????$ cat test_until
?list A????? X=1
do???????? until (( x > 10 ))
?list B????? do
done?????????echo hello X is $X
???????????let X=X+1
????????? done

????????? $ test_until
????????? hello X is 1
????????? hello X is 2
????????? hello X is 3
???????????.
???????????.
???????????.

????????? hello X is 10

until语句是shell提供的另一种循环机制,它会持续执行命令(list B)直到一个条件为真为止。同while循环类似,条件判断由list A中的最后一条命令的返回值来决定的。

命令的执行过程如下:
1.list A中的命令被执行。
2.如果list A中最后一条命令的返回值为非0(假),执行list B。
3.返回到第一步。
4.如果list A中的最后一条命令的返回值为0(真),跳到done关键字后的第一条命令。
注意:until循环的无穷执行下去,因为有些循环语句中的控制语句的返回值始终为假。

$ x=1
$ until
> [ $ x -eq 0 ]
> do
> echo hello
> done
hello
hello
hello
.
.
.

ctrl + c


6.until的例子

例A???????????????例B

重复执行循环体直到ans为no为止 ???????重复执行循环直到没有命令行参数为止

ans=yes?????????????until
until ? ????????????? do
? [ "$ans" = no ] ????????? if test -d $1
do ????????????????? then
echo Enter a name??????????? echo context of $1;
?read name ??????????????ls -F $1
?echo $name >> file.names??????fi
?echo "Continue?"??????????shift
?echo Enter yes or no????????echo There are $# items
?read ans ????????????? echo left on the cmd line.
done????????????????done

上例的结构与while语句中的例子类似,但是使用until语句来。注意在两种语句的test条件的逻辑关系是相反的。

同时请注意用户输入的敏感性由一些轻微的变化。使用while语句,只有用户的输入的字符串为“yes”时循环才会执行,继续执行循环的条件十分严格,使用until语句,循环会在用户使用与no不同的任何字符时都会执行。它对于继续进行循环的条件不太严格,你也许希望在决定那一种结构最符合你的要求的时候考虑这一特征。

预定义ans变量的值不再是必须的,因为它的值会被初始化为NULL,由于NULL不等于no,test会返回假,而循环会被执行。你仅仅需要将$ans用括号引起来,以免在test语句执行时发生语法错误。

shell 5

作者:socketstrem     发表时间:2002/09/12 04:38pm

shell编程之循环语句(2)
7.for语句

对列表的每一条目都进行一次循环过程,每完成一次循环过程就将var赋予列表中下一个条目,直到完成最后一个条目的循环为止

语法: ???????????例子:

for var in list????????$ cat test_for
do?????????????? for X in 1 2 3 4 5
???list A????????? do
done????????????? echo "2 * $X is \c"
??????????????? let X=X*2
??????????????? echo $X
??????????????? done

??????????????? $ test_for
??????????????? 2 * 1 is 2
??????????????? 2 * 2 is 4
??????????????? 2 * 3 is 6
??????????????? 2 * 4 is 8
??????????????? 2 * 5 is 10

在上例中,关键字为for,in,do和done,var代表一个shell变量的名字,这个变量的赋值会贯穿for循环的执行过程中,list是一串由空格或者tab分割开的字符串,在每一次循环执行都要将一个串赋值给var。

for循环的执行过程如下:
1.shell变量var被设置等于list中的第一个字符。
2.list A中的命令会被执行。
3.shell变量var被设置等于list中下一个字符。
4.list A中的命令被执行。
5.循环会持续执行,直到每一个list中的条目都执行过循环为止。

8.for循环的例子
例A:
$ cat example_A
for NAME in $(grep home /etc/passwd | cut -f1 -d
do
??mail $NAME < mtg.minutes
??echo mailed mtg.minutes to $NAME
done

例B
$ cat example_B
for FILE in *
do
?if
??test -d $FILE
?then
??ls -F $FILE
?fi
done

for结构是一种非常灵活的循环结构,它能够让循环贯穿任何能产生的列表。列表能很容易地用命令替代来产生,就像第一个例子中一样。使用管道和过滤器,能够从任何事产生一个列表。

如果你要求多次存取相同的列表,你也许想要将它存储到个文件中。你可以使用cat命令来为你的for循环产生列表,正如下例所示:

??$ cat students
??user1
??user2
??user3
??user4

??$ cat for_student_file_copy
??for NAME in $(cat_students)
??do
??        cp test.file /home/$NAME
??        chown $NAME /home/$NAME/test.file
??        chmod g-w,o-w /home/$NAME/test.file
??        echo done $NAME
??done

??$

存取命令行参数

你可以从命令行参数来产生list:

for i in $*?????????或者?????for i
do???????????????????? do?
?cp $i $HOME/backups????????????cp $i $HOME/backups
done ???????????????????done


9.break,continue,和exit命令
break [n] ?????中止循环过程的执行,并且跳到下一个命令。
continue [n]????停止循环过程的当前一个反复并且跳到循环中的下一个反复过程的开始部分
exit [n] ????? 停止shell程序的执行,并且将返回值设置为n。

在许多情况下,你可能需要在循环的正常中止条件满足之前放弃一个循环的执行。break和continue命令提供了一种无条件的流程控制。通常是在遇到一个错误的情况下来中止当前的循环继续执行。而exit命令用在不能从某种情况下恢复出来,而必须中止整个程序的运行的时候。

break命令会中止循环并且将控制权传递到done关键字后面的第一个命令。结果是完全跳出这个循环体而继续执行下面的命令。

continue命令有一点不同。当在程序执行过程中遇到这个命令,就会忽略本次循环中剩余的命令,而将控制权交给循环的顶部。这样,continue命令能让你仅仅中止循环过程中的一个反复但是继续从循环的顶部开始执行。

在while和until循环中,这种处理(continue)会导致在初始列表的开始部分继续执行,在for循环中,会将变量设置为列表中的下一个条目,然后继续执行循环。

exit命令停止执行当前的shell程序,并且根据提供的参数为这个shell程序设置一个返回值,如果没有提供返回值参数,当前的shell程序的返回值会被设置为在exit命令之前执行的命令的返回值。

注意:循环的流程控制在正常的情况下应当是通过设置循环开始部分的条件(while,until),或者是列表中的条目都循环完的(for),的情况来结束循环。而对循环过程进行中断操作仅仅应当在循环执行期间遇到没有规律的或者是错误的条件的时候才应当使用。


10.break和continue的例子
while  true
do
??echo "Enter file to remove: \c"
??read FILE
??if test ! -f $FILE
??then
????echo $FILE is not a regular file
????continue
??fi
??echo removing $FILE
??rm $FILE
??break
done

这个例子显示break和continue命令的一次有效的使用。这个命令的执行是在while循环的test条件为真的情况下,会始终产生一个为真的结果;这意味着这个循环会是一个无限的循环,除非循环体中的某些命令能中止循环的运行(这就是bread命令需要做的)。如果输入的文件不是一个普通文件,一个错误信息会打印,同时continue命令会提醒用户再输入一次文件名。如果这个文件是个普通的文件,它会被删除,并且break命令被用来跳出这个无穷循环。

11.shell编程之循环-总结
let expression ?????????????计算一个算术表达式
((expression)) ?????????????计算一个算术表达式
while condition is true do ...done??? while
until condition is true do ... done???until
for var in list do ... done ?????? for
break [n]????????????????break out of loop
continue [n]?????????????? 中止当前循环中的一次循环过程
exit [n]???????????????? 中止这个程序

********************************************************************
UNIX Shell Script   
--------------------------------------------------------------------------------
UNIX Shell本身是一个交谈式的命令环境,也是一个功能强大的译式程式语言(Interpreter)。一般我们称以UNIX Shell 写成的程式为Shell Script。不同的Shell语法,会有一定程度的差异。

Borne Shell(/bin/sh)是UNIX作业系统中最早存在的Shell,在所有的UNIX版本中均可发现他的存在,Borne Shell的语法适合Shell命令程式的写作。

UNIX Shell Script 的内容为UNIX指令与一些控制及回圈指令的组合。

UNIX Shell Scirpt的第一行开头为符号"#!"加上所用Shell名称,其用意为告诉kernel,这是一个可执行的程式,同时执行时的环境为所指定的shell(缺少这一行,执行时可能会遭遇到不可预期的错误)。

Shell Script是一种"Free Format"的程式,除了控制回圈须注意结构完整性外,程式本身并无特殊的格式。下面是一个Borne Shell的例子:

#!/bin/sh
echo " This is a Borne Shell Script "

(为使你的Shell Script可以执行,你必须将这个Shell Script的执行权限"x"打开 : chmod +x shell_script)

关于变数

设定变数方式为
var=value

取用变数的方法为
$var

Exmaple 1:
#!/bin/sh
woody="吴贤明 的小名"
echo $woody

(local)变数的设定,会在在同一个process中持续存在,一直到此process结束。

Shell中,变数的值可以含有space的字串,带有一个以上space的的变数,给定值的时候,必须以成对的双引号( " )或单引号(')涵盖之。

双引号( " )中若含有变数($var),会先将变数转换成其实际的值,单引号(')则会将$var当成是一个值,而不会作转换动作。

Example 2:
1. echo "woody就是 $woody"
2. echo 'woody就是 $woody '

结果:
1.woody 就是 吴贤明 的小名
2.woody 就是 $woody

Borne Shell中的内定变数
变数 变数意义
$# Number of arguments on the command line
$- Shell 的选项
$? Exit value of the last command executed ( in general, 0代表执行无误,1代表指令有误)
$$ Process number of the current process
$n Command line 中的参数,$0代表指令名称,$1代表第一个参数,$2代表第二个参数....
$* Command line 中的所有参数


Example 3:
Shell Script : var_test
#!/bin/sh
echo $#
echo $-
echo $?
echo $$
echo $0
echo $1
echo $2
echo $*

执行 var_test 1 2 3 4 5
结果? (Try it !!)

讯息列印

echo 指令可以让你一次在银幕上列印出一行字串(with Double quote‘"’ or Single quote‘'’),以下的方式可以让你一次印出一段文章:

Example 4:
#!/bin/sh
var1="变数一"
var2="变数二"
cat << SEGMENT1
Strings between the two "SEGMENT1"s will be treated as constant,and printed out from the monitor。Variables between these two SEGMENT1's will be calculated before any processing。
Yo umay try the following:
var1=$var1
var2=$var2
SEGMENT1

Another Example:

cat << \SEGMENT2 ##<--请注意这行中的符号‘\’ 这是避免变数被解释的方法,请注意义以下字串被列印
出来的结果:
var1=$var1
var2=$var2
SEGMENT2

这个程式执行的结果为:
Strings between the two "SEGMENT1"s will be treated as constant,and printed out from the monitor。Variables between these two SEGMENT1's will be calculated before any processing。
Yo umay try the following:
var1=变数一
var2=变数二
Another Example:
这是避免变数被解释的方法,请注意义以下字串被列印出来的结果:
var1=$var1
var2=$var2

read - 从银幕读入一个变数
Example 5:
#!/bin/sh
echo -e "lease input your name: \c" #(\c迫使游标不换行)
read name
echo "Hello, $name"

if 指令
Borne Shell中,if指令的用法为
if condition
then
        command(s)
[elif condition
then command(s)]
[else
        command(s)]
fi
中括号([])表示可有可无。

Example 6:
#!/bin/sh
ls -l /etc/host.conf
if [$? -eq 0] 注
then
        echo "/etc/host.conf is there !!"
else
        echo "/etc/host.conf is not there !!"
fi

注[$var -op value]可以比对$var与value ($var为数值变数)。op的值有gt (greater then),eq(equal to ),lt(less than),ge (greater than or equal to ),ne(not equal)及le(less than or qual to)。

test指令 (or [])
if控制回圈中的condition值只有真(0)与伪(none zero)两种,test指令(or [])提供了判断真与假的功能。
test可提供档名、字串与数值等真与伪的判断

档名
test -options file_name or [-option file_name]
常用option与其所代表的意义如下:
-r         True if file exists and readble
-w         True if file exists and writadble
-x         True if file exists and executadble
-f         True if file exists and is a regular file
-d         True if file exists and is a directory
-u         True if file exists and is setuid
-s         True if file exists and is greater than zero in size
字串 test -option string or [-option string]
常用option与其所代表的意义如下:
-z         True if string length is zero
-n         True if string length is non-zero
test string1 = string2 or [string1 = string2]
* True if string1 is identical to string2
test string1 != string2 or [string1 != string2]
--> True if string1 is not identical to string2

数值 test n1 -op n2 or [n1 -op n2]
常用op(运算元)与其所代表的意义如下
-eq         True if n1and n2 are equal
-ne        True if n1and n2 are not equal
-gt         True if n1 is greater than n2
-ge         True if n1 is greater than or equal tp n2
-lt         True if n1 is less than n2
-le         True if n1 is less than or equal to n2
** Both n1 & n2 are intergers

Case 指令(Conditional Swith)
case 指令的语法
case variable in
        pattern1[|pattern1a]) commands ;;
        pattern2) commands;;
        ....
        *) commands ;;
esac

Example 7:
#!/bin/sh
cat << EOF
****************************************
a.I need a banana.
b.I need an orange.
c.I need an apple.
****************************************
EOF
read choice
case $choice in
        a|A) echo "You need a banana !!" ;;
        b|B) echo "You need an orange !!" ;;
        c|C) echo "You need an apple !!" ;;
        *) echo "Bad choice, see yo next time !!";;
esac


while 指令
while 指令语法
while condition
do
commands
done
##回圈中commands会一直被重复执行直到condition 的值为伪为止。

Example 8:计算从1加到10并把结果印出
#!/bin/sh
NO=11
START=1
SUM=0
while [ $NO -gt 0 ]
do
        SUM=****expr $START + $SUM****
        START=****expr $START + 1****
        NO=****expr $NO - 1****
done
echo "The answer is $SUM"

说明:
1. Shell中把所有变数均当成字串,因此进行整数运算时,必须特别注明。内建function "expr"可以帮我们进行这样的数值计算。
2. 符号 **** 代表在变数给定的运算式中,带有指令或函数。设定变数必须先行运算,再做设定。例如
HOSTNAME=****/bin/hostname****
则$HOSTNAME的值会是指令/bin/hostname执行的结果


until 指令
until 指令语法
until condition
do
commands
done
##until指令用法与while恰巧相反;回圈中commands会一直被重复执行直到condition 的值为真。
请试著以until重写上面数字累加的例子。

 

trap - Trapping Signal

Signal 是UNIX系统中用来传递控制讯息(由一程序到另一程序)的一种机制。

一个Process收到signal之后,会优先处理(将控制权交出),处理完毕之后在行返回元程序继续执行(重新取回控制权)。

Signal为一非同步讯息,由数字所构成。不同的数字所代表不同的控制讯息及不同的处理步骤,例如signal "2" (按下Ctrl+C"会送出这个讯号)代表结束目前程序。Programmer也可以在程式撰写时,重新定义Signal。‘trap’是在bash环境下,可供程式撰写者重设SIgnal的工具。

利用shell programming撰写程式时,使用trap通常是希望能够避免使用者以不正常方式中断程式,而使程式进入一个不可知的状态,危害到系统运作,或取得不正当的存取权限。UNIX Signal种类,及所代表的意义,请参考讲义‘UNIX Process’。常用的HotKey与Signal的对应如下:
Ctrl+C 2
Ctrl+\ 3
Ctrl+Z 20

trap格式
trap 'commands; command' signal_number
当程序收到"signal_number"时,会屏除系统预设处理步骤,改以执行单引号中的指令代之。
Example:
trap 'echo "Why You Use Ctrl+C ? Don't Do That Again !!!" 1
如果你在shell script中(程式最上头)加上这行指令,则当使用者(这执行这个程式时)按下Ctrl+C(预设动作为结束程式),银幕上会印出"Why You Use Ctrl+C ? Don't Do That Again !!!",并继续程式的执行。



Example :9 以下的例子是一个交谈式的新帐号建立程式

#!/bin/sh
ID=****whoami****
if [ $ID != "root" ]
then
cat << SEGMENT1

***********************************************

你不是系统管理者!!

这个程式以交谈式方式帮你建立使用者帐号,
你必须具备SuperUser权限。

***********************************************

SEGMENT1
exit 1
fi

echo -e "\n\n请输入使用者帐号:\c"
read login
echo -e "\n请输入使用者姓名:\c"
read comment
echo -e "请输入使用者密码(你将看不到你的输入):\c"
password=****./inp****

# "inp"是Woody老师写的一只小小程式,功能在避免
# 使用者key-in文字在银幕Show出,Source请参见nmc anonymous
# ftp linux目录下的inp.c,Compile with " cc -o inp inp.c "

echo -e "请再输入一次:\c"
password1=****./inp****

if [ $password != $password1 ]
then
echo "两次输入密码不一致,请再试一次"
exit 1
fi

adduser -c $comment $login

if [ $? -eq 0 ]
then
echo "Account $login Created !"
echo "$loginpassword"|chpasswd

#chpasswd是系统指令,读入"username:password" Pair(From File),将帐号"username"的密码改成"password"

else
echo "Account Not Created !! "
fi

echo"";echo ""




Example :10 以下的例子是一个交谈式的使用者帐号密码更改程式(This Program is for root only!)

#!/bin/sh
trap "" 2 3 20
echo -e "\n\n\n"

if [ ! -x /usr/sbin/inp ]
then
        echo -e "/usr/sbin/inp Needed, Please CHECK IT OUT !!!\n\n\n"
exit 1
fi
echo -e "请输入你要更改密码的帐号: \c"
LOGNAME=****whoami****
read login
if [ $LOGNAME != "root" ]
then
        echo "You are Not Authorized to change OTHER'S Passwprd ...!"
        exit 1
fi
grep ^$login":" /etc/passwd > /dev/null
if [ $? -eq 0 ]
then
        echo -e "You New Password ....:\c"
        passwd=****/usr/sbin/inp****
        echo -e "You New Password Again....:\c"
        passwdagain=****/usr/sbin/inp****
        if [ $passwd = $passwdagain ]
        then
                echo "$login":"$passwd" | /usr/sbin/chpasswd
                if [ $? -eq 0 ]
                then
                        echo "asswdord For $login Changed !"
                else
                        echo "assword Not Changed, Please Try again !"
                fi
        else
                echo "assword Not Match, Please Try Again.."
        fi
else
        echo "User Not Exist, Please Check it OUt !"
fi
echo -e "\n\n\n"

Exercise:
1. 请将讲义Example 1~ 8亲自做一次,并确实了解其语意(Know HOW)。
2. token=super
请问下面指令会得到什么结果?你知道为何如此吗?
echo $tokenman
echo '$token'man
echo "$token"man
3. 请写一shell script
(1).提示使用者选择1.可连(telnet)至ccsun, 2.可连(telnet)至bbs, 3. 可连(telnet)至nmc.nchu.edu.tw
(2).从键盘(keyboard)读取变数‘Choice’
(3).IF Choice=1 --> telnet to ccsun
IF Choice=2 --> telnet to bbs
IF Cjoice=3 --> telent to nmc.nchu.edu.tw
Otherwise Say something to user, and terminate the program
4. 请重新改写Example 8 with command "until" !
5. 请写一个shell script:
(1).提示使用者输入username
(2).若此user存在,输出‘此使用者存在,请输入下一笔资料’
(3).若此user不存在,输出‘此使用者不存在,请重新输入’
(4).若输入为Esc键,则跳离此一程式。
提示:
(1).grep username /etc/passwd可检查该user是否存在。
(2).echo $?可以检查上一个指令是否执行成功。
(3).输入特殊符号(或Hot Key)的方式为^v + Hot-key

论坛徽章:
0
5 [报告]
发表于 2003-05-06 08:26 |只看该作者

Shell/sed/awk学习参考资料[转贴]

Sed 命令列可分成编辑指令与文件档部份。其中 , 编辑指令负责控制所有的编辑工作 ; 文件档表示所处理的档案。
sed 的编辑指令均由位址(address)与函数(function)两部份组成 , 其中 , 在执行时 , sed 利用它的位址参数来
决定编辑的对象;而用它的函数参数(解[3])编辑。
此外 , sed 编辑指令 , 除了可在命令列上执行 , 也可在档案内执行。其中差别只是在命令列上执行时 , 其前必
须加上选项 -e ; 而在档案(解[4])内时 , 则只需在其档名前加上选项 -f。另外 , sed 执行编辑指令是依照它
们在命令列上或档内的次序。

下面各节 , 将介绍执行命令列上的编辑指令 、sed 编辑指令、执行档案内的编辑指令、执行多个档案的编辑、及
执行 sed 输出控制。

2.1 执行命令列上的编辑指令
2.2 sed 编辑指令
2.3 执行档案内的编辑指令
2.4 执行多个档案的编辑
2.5 执行 sed 输出控制
2.1.执行命令列上的编辑指令
当编辑指令(参照[section 2.2])在命令列上执行时 , 其前必须加上选项 -e 。其命令格式如下 :
sed -e '编辑指令1' -e '编辑指令2' ... 文件档

其中 , 所有编辑指令都紧接在选项 -e 之後 , 并置於两个 " ' " 特殊字元间。另外 , 命令上编辑指令的执行是由
左而右。

一般编辑指令不多时 , 使用者通常直接在命令上执行它们。例如 , 删除 yel.dat 内 1 至 10 行资料 , 并将其
馀文字中的 "yellow" 字串改成 "black" 字串。此时 , 可将编辑指令直接在命令上执行 , 其命令如下 :

sed -e '1,10d' -e 's/yellow/black/g' yel.dat

在命令中 , 编辑指令 '1,10d'(解[5])执行删除 1 至 10 行资料 ; 编辑指令 's/yellow/black/g'(解[6]) ,
"yellow" 字串替换(substuite)成 "black" 字串。
2.2 sed 的编辑指令
sed 编辑指令的格式如下 :
[address1[,address2]]function[argument]

其中 , 位址参数 address1 、address2 为行数或 regular expression 字串 , 表示所执行编辑的资料行 ; 函数参
数 function[argument] 为 sed 的内定函数 , 表示执行的编辑动作。
下面两小节 , 将仔细介绍位址参数的表示法与有哪些函数参数供选择。

2.2.1 位址(address)参数的表示法
实际上 , 位址参数表示法只是将要编辑的资料行 , 用它们的行数或其中的字串来代替表示它们。下面举几个例子
说明(指令都以函数参数 d(参照[section4.2]) 为例) :
删除档内第 10 行资料 , 则指令为 10d。
删除含有 "man" 字串的资料行时 , 则指令为 /man/d。
删除档内第 10 行到第 200 行资料, 则指令为 10,200d。
删除档内第 10 行到含 "man" 字串的资料行 , 则指令为 10,/man/d。
接下来 , 以位址参数的内容与其个数两点 , 完整说明指令中位址参数的表示法(同样也以函数参数 d 为例)。
位址参数的内容:
位址为十进位数字 : 此数字表示行数。当指令执行时 , 将对符合此行数的资料执行函数参数指示的编辑动作。例如 ,
删除资料档中的第 15 行资料 , 则指令为 15d(参照[section4.2])。其馀类推 ,如删除资料档中的第 m 行资料 , 则
指令为 md 。

位址为 regular expression(参照[附录 A]):
当资料行中有符合 regular expression 所表示的字串时 , 则执行函数参数指示的编辑动作。另外 , 在
regular expression 前後必须加上 "/"。例如指令为 /t.*t/d , 表示删除所有含两 "t" 字母的资料行。其中 , "."
表示任意字元; "*" 表示其前字元可重任意次 , 它们结合 ".*" 表示两 "t" 字母间的任意字串。

位址参数的个数 : 在指令中 , 当没有位址参数时 , 表示全部资料行执行函数参数所指示的编辑动作; 当只有一位址
参数时 , 表示只有符合位址的资料行才编辑 ; 当有两个位址参数 , 如 address1,address2 时 , 表示对资料区执行
编辑 , address1 代表起始资料行 , address2 代表结束资料行。对於上述内容 , 以下面例子做具说明。

例如指令为

d

其表示删除档内所有资料行。
例如指令为

5d

其表示删除档内第五行资料。
例如指令为

1,/apple/d

其表示删除资料区 , 由档内第一行至内有 "apple" 字串的资料行。
例如指令为

/apple/,/orange/d

其表示删除资料区 , 由档内含有 "apple" 字串至含有 "orange" 字串的资料行
2.2.2 有那些函数(function)参数
下页表中介绍所有 sed 的函数参数(参照[chapter 4])的功能。
函数参数 功能
: label 建立 script file 内指令互相参考的位置。
# 建立解
{ } 集合有相同位址参数的指令。
! 不执行函数参数。
= 印出资料行数( line number )。
a\ 添加使用者输入的资料。
b label 将执行的指令跳至由 : 建立的参考位置。
c\ 以使用者输入的资料取代资料。
d 删除资料。
D 删除 pattern space 内第一个 newline 字母 \ 前的资料。
g 拷贝资料从 hold space。
G 添加资料从 hold space 至 pattern space 。
h 拷贝资料从 pattern space 至 hold space 。
H 添加资料从 pattern space 至 hold space 。
l 印出 l 资料中的 nonprinting character 用 ASCII 码。
i\ 插入添加使用者输入的资料行。
n 读入下一笔资料。
N 添加下一笔资料到 pattern space。
p 印出资料。
P 印出 pattern space 内第一个 newline 字母 \ 前的资料。
q 跳出 sed 编辑。
r 读入它档内容。
s 替换字串。
t label 先执行一替换的编辑指令 , 如果替换成功,则将编辑指令跳至 : label 处执行。
w 写资料到它档内。
x 交换 hold space 与 pattern space 内容。
y 转换(transform)字元。
虽然 , sed 只有上表所述几个拥有基本编辑功能的函数 , 但由指令中位址参数和指令与指令间的配合 , 也能使sed 完成大部份的编辑任务。
2.3 执行档案内的编辑指令
当执行的指令太多 , 在命令列上撰写起来十分混乱 , 此时 , 可将这些指令整理储存在档案
(譬如档名为 script_file )内 , 用选项 -f script_file , 则让 sed 执行 script_file 内的编辑指令。其命
令的格示如下 :
sed -f script_file 文件档

其中 , 执行 script_file 内编辑指令的顺序是由上而下。例如上一节的例子 , 其可改成如下命令:
sed -f ysb.scr yel.dat

其中 , ysb.scr 档的内容如下 :
1,10d
s/yellow/black/g

另外 , 在命令列上可混合使用选项 -e 与 -f , sed 执行指令顺序依然是由命令列的左到右, 如执行至 -f 後
档案内的指令 , 则由上而下执行。


2.4 执行多个文件档的编辑
在 sed 命令列上 , 一次可执行编辑多个文件档 , 它们跟在编辑指令之後。例如 , 替换
white.dat、red.dat、black.dat 档内的 "yellow" 字串成 "blue" , 其命令如下:
sed -e 's/yellow/blue/g' white.dat red.dat black.dat

上述命令执行时 , sed 依 white.dat、red.dat、black.dat 顺序 , 执行编辑指令 s/yellow/blue/(请参照[section 4.1] ,
进行字串的替换。

2.5.执行输出的控制
在命令列上的选项 -n (解[7]) 表示输出由编辑指令控制。由前章内容得知 , sed 会 "自动的" 将资料由
pattern space 输送到标准输出档。但藉着选项 -n , 可将 sed 这 "自动的" 的动作改成 "被动的" 由它所执行的
编辑指令(解[8])来决定结果是否输出。
由上述可知 , 选项 -n 必须与编辑指令一起配合 , 否则无法获得结果。例如 , 印出 white.dat 档内含有 "white"
字串的资料行 , 其命令如下:

sed -n -e '/white/p' white.dat

上面命令中 , 选项 -n 与编辑指令 /white/p (参照[section4.6]) 一起配合控制输出。其中 , 选项 -n 将输出控制权
移给编辑指令;/white/p 将资料行中含有 "white" 字串印出萤幕。

论坛徽章:
0
6 [报告]
发表于 2007-10-11 15:37 |只看该作者
不错

论坛徽章:
0
7 [报告]
发表于 2011-09-16 10:22 |只看该作者
这么多,要是有个目录索引就好了,想了解那部分很快就能找到!

论坛徽章:
1
狮子座
日期:2014-11-03 14:29:13
8 [报告]
发表于 2013-08-28 10:25 |只看该作者
很详尽,学习了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP