shell脚本学习指南[四](Arnold Robbins & Nelson H.F. Beebe著)
回忆起一件事情:之前用linux寻找中文输入法的时候,在百度输入了fcitx,然后结果上边有个,您要找的是不是: 讽刺腾讯 。本来一直记不住这个输入法名字,不过以后哥就记住这个输入法的名字是怎么拼了,感谢百度。
第九章awk的惊人表现
awk的调用可以定义变量、提供程序并且指定输入文件,语法:
awk [ -F fs ] [ -v var=value ... ] 'program' [ -- ] [ var=value ... ] [file(s) ]
awk [ -F fs ] [ -v var=value ... ] -f programfile [ -- ] [ var=value ... ] [ file(s) ]
短程序通常直接在命令行上提供,而比较长的程序则委托-f选项指定,可以重复使用此选项。如果命令行未指定文件名,则awk会从标准输入读取。 -- 是特殊选项,指出awk本身已经没有更进一步的命令行选项。任何接下来的选项都可被你的程序使用。
-F选项是用来重新定义默认字段分隔字符,且一般惯例将它作为第一个命令选项。紧接-F选项后的fs参数是一个正则表达式或是被提供作为下一个参数。字段分隔字符也可以设置使用内建变量FS所指定。如:
awk -F '\t' '{ ... }' files FS="[\f\v]" files
上边例子-F选项设置的值,应用到第一个文件组,而由FS指定的值,则应用到第二个组。初始化的-v选项必须放在命令行上直接给定的任何程序之前,他们会在程序启动前生效。在一命令行程序之后-v选项会被解释为一个文件名。在命令行上其他地方的初始化会在处理参数时完成,并且会带上文件名,如:
awk '{...}' Pass=1 *.tex Pass=2 *.tex
处理文件的列表两次,第一次Pass设为1,第二次为2。使用字符串值进行初始化无须用引号框起来,除非shell要求这样的引用以保护特殊字符或空白。
特殊文件名-(连字符)表示标准输入。大部分现代的awk实现(不包括POSIX)都认定特殊名称/dev/stdin为标准输入,即使主机操作系统不支持该文件名。同样:/dev/stderr与/dev/stdout可用于awk程序内,分别表示标准错误输出与标准输出。
一般awk命令模式或操作可省略一个,如果模式省略,则每条输入都被操作;如果操作省略,则默认操作为输出匹配模式的记录。虽然模式多半是数字或字符串表达式,不过awk以保留自BEGIN与END提供两种特殊模式。
与BEGIN关联的操作只会执行一次,在任何命令行文件或一般命令行赋值被处理之前,但是在任何开头的-v选项指定已完成之后。它大部分是用来处理程序所需要的任何特殊初始化工作。END操作也是只执行一次。用于所有输出数据已被处理完之后。BEGIN和END模式可以是任意顺序,可以存在awk程序内任何位置。当指定多个BEGIN或END模式,则他们将按照在awk程序里的顺序执行。
awk提供了标量与数组两种变量以保存数据、数字与字符串表达式,还提供了一些语句类型以处理数据:赋值、注释、条件、函数、输入、循环及输出。awk表达式许多功能与c语言相似。awk里注释是从#开始到行尾。跨行语句需要在结尾处加上反斜杠。
awk里的字符串常数是以引号定界,字符串可包含任何8bit的字符除了控制字符NUL以外。因为NUL在底层实现语言(C)里,扮演的是一个字符串中断字符的角色。awk字符串长度视内存而定。反斜杠转义序列允许非打印字符的表示。
awk提供了许多内建函数,可以在字符串上执行,之后再详细说,这会说两个length(string)返回string内的字符数。字符串的比较用的是传统的关系运算符:==、!=、<、<=、>、>=。比较不同长度的字符串,且其中一个字符串为另一个的初始子字符串时,较短的定义为小于较长的那个。在shell里字符串连接可以直接进行,不需要连接符号。
awk功能强大的地方大多来自于它对正则表达式的支持。有两个运算符:~(匹配)与!~(不匹配)让awk更容易使用正则表达式:"ABC" ~ "^[A-Z]+$"结果为真,正则表达式常量可以用引号或斜杠加以定界:/^[A-Z]+$/。注意如果有字面意义的符号,需要反斜杠来转义。
awk里的数字,都以双精度浮点值表示,如1/32 写成0.03125、3.125e-2等,awk里没有提供字符串转数字的函数,不过想做到也很简单,只要加个零到字符串里,如:s = "123" , n = 0 + s 。这样123便赋值给n了。一般"+123ABC"转化为123,而"ABC123"与""都转化为0。即使awk里所有的数值运算都是在浮点算术内完成,整数值还是可以表示的,只要值不太大,这个值限定在53位,即2^53即9千万亿的样子。awk的数值运算符没有位运算符,多一个指数运算符(^ 或 ** 或 **=,但是避免使用**和*=,它不是POSIX awk的一部分)它是右结合性的,且与赋值运算符是仅有的右结合性运算符。比如a^b^c^d运算顺序是a^(b^(c^d))。awk里的取余运算测试了 5 % 3 是2 ; 5 % -3 是2; -5 % 3 是-2; -5 % -3是-2;发现取余的结果取决于被取余的数的正负。还有一个内建函数:
int(x) 对x取整
rand 取 0到1之间的随机数
srand(x) 设置x为rand的新输入值
cos(x) 给出x的余弦值
sin(x) 给出x的正弦值
atan2(x,y) 给出y/x的正切值
exp(x) 给出e的x次幂
log(x) 给出x的常用对数值(基为e)
sqrt(x) 给出x的正平方根值
exit(x) 结束awk程序,若有x值,则返回x,否则返回0.
index(s,t) 返回t在s中的第一个开始位置,如t不是s的子串,则返回0]
length(x) 求x的长度(字符个数)
substr(s,x,y) 在字符串s中取得从x个字符开始的长度为y的子字符串.
awk内置字符串函数
gsub(r,s) 在整个$0中用s替代r
gsub(r,s,t) 在整个t中用s替代r
index(s,t) 返回s中字符串t的第一位置
length(s) 返回s长度
match(s,r) 测试s是否包含匹配r的字符串
split(s,a,fs) 在fs上将s分成序列a
sprint(fmt,exp) 返回经fmt格式化后的exp
sub(r,s) 用$0中最左边最长的子串代替s
substr(s,p) 返回字符串s中从p开始的后缀部分
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分
awk提供许多内建变量,都是大写名称,时常用到的几个有:
FILENAME 当前输入文件的名称
FNR 当前输入文件的记录数
FS 字段分隔字符(正则表达式)(默认为:" ")
NF 当前记录的字段数
NR 在工作中的记录数
OFS 输出字段分隔字符(默认为:" ")
ORS 输出记录分隔字符(默认为:"\n")
RS 输入记录分隔字符(仅用于gawk与mawk里的正则表达式)(默认为:"\n")
awk允许的测试:
x==y x等于y?
x!=y x不等于y?
x>y x大于y?
x>=y x大于或等于y?
x x<=y x小于或等于y?
x~re x匹配正则表达式re?
x!~re x不匹配正则表达式re?
awk的操作符
= 、+=、 -=、 *= 、/= 、 %=
|| && > >= < <= == != ~ !~
xy (字符串连结,'x''y'变成"xy")
+ - * / % ++ --
awk没有提供位操作符,但是提供了相关的函数:
and(v1, v2) Return the bitwise AND of the values provided by v1 and v2.
compl(val) Return the bitwise complement of val.
lshift(val, count) Return the value of val, shifted left by count bits.
or(v1, v2) Return the bitwise OR of the values provided by v1 and v2.
rshift(val, count) Return the value of val, shifted right by count bits.
xor(v1, v2) Return the bitwise XOR of the values provided by v1 and v2.
awk的数组变量允许数组名称之后,以方括号将任意数字或字符串表达式括起来作为索引。以任意值为索引的数组称之为关联数组。awk将应用于数组中,允许查找插入和删除等操作,在一定时间内完成,与存储多少项目无关。(说了这么多其实就是hash数组)。delete array[index]会从数组中删除元素。delete array删除整个数组。awk数组还可以这么用:
print maildrop[53, "Oak Lane", "T4Q 7XV"]
print maildrop["53" SUBSEP "Oak Lane" SUBSEP "T4Q 7XV"]
print maildrop["53\034Oak Lane", "T4Q 7XV"]
print maildrop["53\034Oak Lane\034T4Q 7XV"]
以上输出结果都是一样的。内建变量SUBSEP默认值是\034,可以更改它。如果稍后更改了SUBSEP的值,将会使已经存储数据的索引失效,所以SUBSEP其实应该在每个程序只设置一次,在BEGIN操作里。
awk对于命令行的自动化处理,意味着awk程序几乎不需要关心他们自己。awk通过内建变量ARGC(参数计数)与ARGV(参数向量,或参数值),让命令行参数可用。给出例子说明其用法:
$ cat >showargs.awk
BEGIN{
print "ARGC = ",ARGC
for ( k = 0 ; k < ARGC ; k++)
print "ARGV[" k "] = [" ARGV[k] "]"
}
$ awk -v One=1 -v Two=2 -f showargs.awk Three=3 file1 Four=4 file2 file3
ARGC = 6
ARGV[0] = [awk]
ARGV[1] = [Three=3]
ARGV[2] = [file1]
ARGV[3] = [Four=4]
ARGV[4] = [file2]
ARGV[5] = [file3]
正如C/C++中,参数存储在数组项目0、1....、ARGC-1中,第0个项目是awk程序本身的名称。不过与-f 和 -v选项结合性的参数是不可使用的。同样的,任何命令行程序也不可使用:
$ awk 'BEGIN{for(k=0;k<ARGC;k++)
print "ARGV["k"] = ["ARGV[k]"]"}' a b c
ARGV[0] = [awk]
ARGV[1] = [a]
ARGV[2] = [b]
ARGV[3] = [c][/c][/c]
是否需要显示在程序名称里的目录路径,则看实际情况而定。awk程序可修改ARGC和ARGV,注意保持俩个的一致性。
awk一见到参数含有程序内容或是特殊--选项时,它会立即停止将参数解释为选项。任何接下来的看起来像是选项的参数,都必须由你的程序处理,并接着从ARGV中被删除或设置为空字符串。
awk提供访问内建数组ENVIRON中所有的环境变量:
$ awk 'BEGIN{ print ENVIRON["HOME"]; print ENVIRON["USER"]}'
/home/administrator
administrator
ENVIRON数组并无特别之处,可以随意修改删除。然而,POSIX要求子进程继承awk启动时生效的环境,而我们也发现,在现行实现下,并无法将对于ENVIRON数组的变更传递给子进程或者内建函数。特别地,这是指你无法通过对EVNIRON["LC_ALL"]的更改控制字符串函数,例如tolower(),在特定locale下的行为模式。因此你应将ENVIRON看成一个只读数组。如果要控制子进程的locale,则可通过在命令行字符串里设置适合的环境变量达成。如:
system("env LC_ALL=es_Es sort infile > outfile")#以Spanish的locale排序文件。
system()函数稍后说明。
模式与操作构成awk程序的核心。模式为真则进行操作。一般模式是正则表达式,就会被拿来与整个输入记录进行匹配,比如:
NF == 0 #选定空记录
NF > 3 #选定拥有三个字段以上的记录
NR < 5 #选定第一到第四条记录
$1 ~ /jones/ #选定字段1中有jones的记录
/[xX][mM][lL]/ #忽略大小写选定含xml的记录
awk在匹配功能上,还可以使用范围表达式,以逗点隔开的两个表达式。比如:
(FNR == 3) , (FNR == 10) #选定每个输入文件按里记录3到10
/<[Hh][Tt][Mm][Ll]>/ , /<\/[Hh][Tt][Mm][Ll]>/ #选定html文件里的主体
在BEGIN操作里,FILENAME、FNR、NF与NR初始都未定义;引用到他们时,会返回null。
通过模式的匹配,就要把为真记录的传给操作。给出一些实例:
#unix单词计数程序wc:
awk '{ C += length($0) + 1 ; W += NF } END { print NR, W, C}'
注意:模式/操作组并不需要以换行字符分隔,一般换行是为了阅读方便。我们也可以使用BEGIN{ C = W =0} 来初始化,但是awk具有默认的初始化保证。
#将原始数据值及他们的对数打印为单栏数据文件:
awk ' { print $1 , log($1) }' file(s)
#要从文本文件里随机打印5%行左右的样本:
awk 'rand() < 0.05 ' file(s)
#以空白分隔字段的表格中,报告第n栏的和:
awk -v COLUMN=n '{ sum += $COLUMN } END { print sum } ' file(s)
#产生字段n栏的平均值
awk -v COLUMN=n '{ sum += $COLUMN } END { print sum / NR } ' file(s)
#统计文件最后一个字段的总数
awk '{ sum += $NF; print $0 , sum }' file(s)
#三种查找文件内文本的方式:
egrep 'pattern|pattern' file(s)
awk '/pattern|pattern/' file(s)
awk '/pattern|pattern/ { print FILENAME ":" FNR ":" $0 }' file(s)
#仅查找100-150行 的匹配信息
sed -n -e 100,150p -s file(s) | egrep 'pattern'
awk '(100<=FNR)&&(FNR<=150)&& /pattern/ { print FILENAME":"FNR":"$0}' file(s)
#要在四栏表格里调换二三栏,假设制表符分隔:
awk -F'\t' -v OFS='\t' '{ print $1,$3,$2,$4}' old > new
awk 'BEGIN {FS=OFS='\t' } {print $1,$3,$2,$4 }' old > new
awk -F'\t' '{ print $1 "\t"$3"\t"$2"\t"$4} ' old > new
#将格栏分隔符由制表符替换成&:
sed -e 's/\t/\&/g' file(s)
awk 'BEGIN { FS="\t"; OFS="&" } {$1 = $1; print }' file(s)
#删除排序后的重复行:
sort file(s) | uniq
sort file(s) | awk 'Last != $0 { print } {Last = $0} '
#将回车字符/换行符的行终结,一致转换为以换行字符为行终结:
sed -e 's/\r$//' file(s)
sed -e 's/^M$//' file(s)
mawk 'BEGIN { RS="\r\n" } { print } ' file(s)
#找出长度超过72个字符的行:
egrep -n '^.{73,}' file(s)
awk 'length($0) > 72 { print FILENAME":"FNR":"$0}' file(s)
awk支持语句的连续执行。支持条件语句,if else 类似C语言,支持循环 while(){} 或do{} while()或for( ; ; ){] 类似c语言。还有一个for(key in array) { } 。
如 awk 'BEGIN { for( x=0; x<=1;x+=0.05) print x}' 。虽然很多类似C,但是注意awk中是缺乏逗点运算符的。循环同样可以使用break和continue 。
awk直接处理命令行上标明的输入文件,一般不用用户自己打开与处理文件,但是也可以通过awk的getline语句做这些事情。用法:
getline 从当前输入文件读取下一条记录存入$0,并更新NF、NR、FNR
getline var 从当前输入文件中,读取下一条记录存入var并更新NR、FNR
getline < file 从fle中读取下一条记录,存入$0,并更新NF
getline var < file 从file读取下条记录存入var
cmd | getline 从外部命令cmd读取下条记录存入$0,并更新NF
cmd | getline var 从外部命令读取下条记录,存入var
如果像确保来自控制终端的输入则:getline var < "/dev/tty"
在awk里可以通过管道与外部的shell命令混写:
tmpfile = "/tmp/telephone.tmp"
comman = "sort > " tmpfile
for ( name in telephone)
print name "\t" telephone[name] | command
close (command)
while((getline < tmpfile) > 0)
close(tmpfile)
close可以关闭打开的文件以解约可用资源。awk里也没有排序函数,以为它只需要复制功能强大的sort命令即可。
getline语句以及awk管道里的输出重定向都可与外部程序通信,system(command)函数提供的是第三种方式:其返回值是命令的退出码。所以上边的例子可以写成:
tmpfile = "/tmp/telephone.tmp"
for ( name in telephone)
print name "\t" telephone[name] | > tmpfile
close (tmpfile)
system("sort < " tmpfile)
while((getline < tmpfile) > 0)
close(tmpfile)
对于被system()执行的命令并不需要调用close(),因为close()仅针对以I/O重定向运算符所打开的文件或管道,还有getline、print、printf。其他几个例子:
system("rm -f " tmpfile)
system("cat < 由于每次调用system()都会起始一个全新的shell,因此没有简单方式可以在分开的system()调用内的命令之间传递数据,除非通过中间文件。
就到目前这里,awk足够编写任何数据处理程序了。对于大型程序,不利于维护和查看,所以awk提供函数,就像c一样,awk也可选择性的返回标量值。函数可以定义在程序顶层的任何位置:成对的模式/操作组之前、之间、之后。在单一文件的程序里,惯例是将所有函数放在成对的模式/操作码之后,且让他们依字母顺序排列,这样会读起来方便。定义如下:
function name(arg1,arg2....){ statement(s) ; return expression ;}
局部的变量会覆盖全局的同名变量。
awk里其他的内建函数:
子字符串提取substr(string,start,len),下标从1开始。
字母大小写转换tolower(string),toupper(string)。无法处理罕见字母和重音字母。
字符查找index(string,find),返回起始位置,找不到给0.
字符串匹配match(string,regexp),匹配则返回string的索引,并且会更新全局变量RSTART和RLENGTH,获取匹配方法:substr(string, RSTART,RLENGTH)。
字符串替换sub(regexp,replacement,target)和gsub(regexp,replacement,target)。前者将target与正则表达式进行匹配,将最左边最长的匹配部分替换为字符串。
gsub()的运行类似,不过它会替换所有匹配的字符串。两种函数都返回替换的数目。如果省略第三个参数,则默认值为当前的记录$0。两个函数里replacement里的字符&都会被替换为target中与regexp匹配的文本。使用\&可关闭这一功能,而且请记得如果你要在引号字符串里使用它时,以双斜杠转义它。如gsub(/[aeiouyAEIOUY]/,"&&")令所有当前$0里的元音字母乘以两倍,而gsub(/[aeiouyAEIOUY]/,"\\&\\&")则是将所有元音字母替换为一对&符号。
字符串分割:awk针对$0自动提供了方便的分割为$1 $2 .... $NF,也可以函数来做:split(string,array,regexp)将string切割为片段,并存储到array里。如果regexp省略,则默认内建字段分隔符为FS。函数返回array里的元素数量。填写分割符的时候留意默认字段分隔符" "与"[ ]"的差异:前者会忽略前置与结尾的空白,并于运行时将空白视为一个单独空格,后者则正好匹配一个空格,对绝大多数文本处理而言,第一种模式已经满足功能上的需求了。
字符串格式化sprintf(format,expression1,expression2,...) ,它会返回已格式化的字符串作为其函数值。printf()的运行方式也是这样,只不过它会在标准输出或重定向的文件上显示格式化后的字符串,而不是返回其函数值。这俩函数类似shell里的printf,但是还有些许差异,使用的时候注意一下。
数值函数:
atan2(y,x) 返回y/x的反正切
exp(x) 返回x的指数,ex
int(x),log(x),cos(x),sin(x),sqrt(x),
rand() 返回0<=r<1
srand(x) 设置虚拟随机产生器的种子为x,并返回正确的种子。如果省略x,则使用当前时间(以秒计)。如果srand()未被调用,则awk每次执行都会从默认种子开始。
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 下标分隔符 "34"
以上基本上把所有awk的内容详细讲完了,十分的强大,网上搜了些别的关于awk的讲解,没发现有哪篇讲解像这本书里这么全的。
上边例子给出的比较少,这里有很多例子可供参考。
第十章文件处理
先讲了ls命令,应该很熟了,再罗列一下主要选项吧:
-1 数字1,强制单栏输出,默认的以适合窗口宽度输出
-a 显示所有文件
-d 显示与目录相关信息,而非他们包含的文件的信息
-F 使用特殊结尾字符,标记特定的文件类型。试了一下路径加了斜杠,可执行文件加了*号。别的没怎么试。
-g 仅适用于组:省略所有者名称
-i 列出inode编号
-L 紧连着符号性连接,列出他们指向的文件。
-l 小写L,显示详细信息。
-r 倒置默认排序
-R 递归列出下沿进入每个目录
-S 按照由大到小的文件大小计数排序,仅GNU版本支持。
-s 以块(与系统有关)为单位,列出文件的大小。
-t 按照最后修改时间排序
--full-time 显示完整的时间戳
说明一下长信息显示的时候的内容:
drwxrwxr-x 2 administrator administrator 1024 1月 5 10:43 bin
第一个字母 - 表示一般文件 d表示目录 l表示符号连接
接下来的9个字符,每三个是一组,报告所有组的权限,r表示可读,w表示可写,x表示可执行。前三个是拥有者选前,中间三个是用户所在组的权限,最后三个是其他人的权限。
第二栏包含连接计数。第三四栏表示所有者和所属组。第五栏是字节单位大小。最后是时间和文件(夹)名。
书中给了一个命令od 说显示真是的文件名,ls | od -a -b ,尝试了一下,完全看不懂输出内容。貌似是以nl(八进制012)做分隔符,然后罗列处来文件名的样子。如果文件名有汉字,显示会是一些符号。各种不懂。
书中用一节说使用touch更新修改时间,并说有时时间戳是有意义的,但内容则否。常见例子是用于锁定文件,以指出程序已在执行中,不应该启动第二个实例。另一用途则为记录文件的时间戳,供日后与其他文件对照用。touch默认(-m) 操作会改变文件的最后修改时间,也可以使用-a选项改变文件的最后访问时间。也可以搭配-t选项修改时间,方式是加上[[CC]YY]MMDDHHMM[.SS]形式的参数,世纪、公元年和秒数是可选项,例如:
$ touch -t 201201010000.00 date #建立一个文件设定时间戳
touch还提供-r选项,复制参照文件的时间戳。
以日期来看,unix时间戳是从零开始,由1970/1/1/ 00:00:00 UTC算起。
然后又用一节介绍了一下临时文件/tmp 。一般要解决自己程序生成的临时文件,共享的目录或同一程序的多个执行实例可能造成临时文件命名冲突,一般使用的都是进程ID,可以在shell变量
umask 077 #???????????????
TMPFILE=${TMPDIR-/tmp}/myprog." />You can't use 'macro parameter character #' in math mode #产生临时性文件名
trap 'rm -f $TMPFILE' EXIT #完成时删除临时文件
但是像/tmp/myprog.
$ cat $HOME/html2xhtml.sed
s/<H1>/<h1>/g
...
s:H2>:h2>:g
...
cd top level web site directory
find . -name '*.html' -o -name '*.htm' -type f |
while read file
do
echo $file
mv $file $file.save
sed -f $HOME/html2xhtml.sed < $file.save > $file
done
书中说了一小节寻找问题文件,意思是文件名里有特殊字符,可以实用find -print0 来解析,但是没搞明白说这些是干嘛用的。
然后介绍了一个执行命令xargs,是为了处理给脚本传参过长的问题,不如有时候我们会写寻找字符串的命令如下:
$ grep POSIX_OPEN_MAX /dev/null $(find /usr/include -type f | sort )
我们在后边一堆文件中寻找 POSIX_OPEN_MAX这样的一个字符串。如果后边find出来的文件很少,那很好,这条命令就会顺利执行,但是如果过长会给出提示:****:Argument list too long. 这样子。我们可以通过getconf ARG_MAX来查看你的系统允许的最大值是多少。上边这条命令有一个文件是空文件/dev/null,这是为了防止find没找到任何文件使grep进入从标准输入获取信息的空等状态,也为了使grep命令有多个文件参数而使结果可以显示文件名和出现的行数。
我们可以解决这样的一个参数过长的问题通过开始提到的xargs命令,如:
$ find /usr/include -type f | xargs grep POSIX_OPEN_MAX /dev/null
这里xargs如果未取得输入文件名,则会默认终止。GNU的xargs支持--null选项:可处理GNU find的-print0选项所产生的NUL结尾的文件名列表。xargs将每个这样的文件名作为一个完整参数,传递给它执行的命令,而没有shell(错误)解释问题或换行符号混淆的危险,然后是交给其后的命令处理它的参数。另外xargs的选项可以控制哪些参数需要被替换,还可以限制传递的参数个数等。
如果了解文件系统的空间信息,我们可以通过find和ls命令配合awk程序协助就可办到,比如:
$ find -ls | awk '{sum +=$7} END {printf("Total: %.0f bytes\n",sum)}'
但并不好用,编码长不说还不知道可用空间。有两个好用的命令来解决这一需求:df和du。
df(disk free)提供单行摘要,一行显示一个加载的文件系统的已使用和可实用空间。显示单位具体看相应版本。可以实用-k强制实用kilobytes单位。还有一个选项-l 仅显示本地文件系统,排除网络加载的文件系统。还有-i选项,提供访问inode使用量。GNU的df还提供-h(human-readable)选项,方便阅读。还可以提供一个或多个文件系统名称或加载点来限制输出项目:$ df -lk /dev/sda6 /var 。
du会摘要文件系统的可用空间,但是不会告诉你某个特定的目录树需要多少空间,这是du(disk usage)的工作。不同系统可能有所不同,-k控制单位,-s显示摘要。
GNU版本提供-h,同df。du可以解决的一个常见问题是:找出哪个用户用掉最多的系统空间:$ du -s -k /home/users/* | sort -k1nr | less
假设用户目录全部放在/home/users下。
关于比较文件好用的两个命令cmp和diff。cmp直接后边跟两个文件参数即可,如果不同输出结果会指出第一个不同处的位置,相同没有任何输出。-s可以抑制输出,可以通过$?来查看离开状态码,非零表示不同。diff惯例是将旧文件作为第一个参数,不同的行会以前置左尖括号的方式,对应到左边文件,而前置右尖括号则指的是右边的文件。还有一个扩展是diff3,比较3个文件。
有时候需要修复不同的地方,patch命令提供了十分方便的做法:
$ echo test 1 > test.1
$ echo test 2 > test.2
$ diff -c test.[12] > test.dif
$ patch < test.dif
此时你查看test.1会发现里边的内容已经变为test 2了。patch会尽可能套用不同之处,然后报告失败的部分,由用户自行处理。虽然patch也可以处理一般的diff输出,但是常规都是处理diff -c选项的信息。
如果有你怀疑有很多文件有相同的内容,实用cmp或diff就十分麻烦。这时可以实用file checksum(文件校验和),取得近似线性的性能完成这一繁琐的工作。有很多工具可以提供,如:sum、cksum、checksum,消息摘要工具md5与md5sum,安全性三列(secure-hash)算法工具sha、sha1sum、sha256以及sha384。可惜的是:sum的实例在各个平台都不想同,使得他们的输出无法跨越不同unix版本进行文件校验和的比较。一般的会这样:
$ md5sum /bin/l?
57e35260b8e017f4696b8559ed4f32f2 /bin/ln
0f68a291df1a708e130e0f407a67e9ca /bin/ls
输出结果有32个十六进制数,等同128位,因此两个不同文件最后散列出相同签名的可能性非常低。了解这个后可以写一个简单脚本来实现我们之前的目标了。
#! /bin/sh -
# 根据他们的MD5校验和,显示某种程度上内容机会一直的文件名
#
# 语法:
# show-indentical-files files
IFS='
'
PATH=/usr/local/bin:/usr/bin:/bin
export PATH
md5sum "$@" /dev/null 2> /dev/null |
awk '{
count[$1]++
if( count[$1] ==1 ) first[$1]=$0
if( count[$1] ==2 ) print first[$1]
if( count[$1] >1 ) print $0
}' |
sort | awk '{
if ( last != $1 ) print ""
last = $1
}'
程序很简单,就不弄注释了吧。可以测试一下:
$ show-indentical-files /bin/*
发现好多命令都很能装啊,其实内容都一样的 - -!。
这里说一下数字签名验证,很有用。
软件发布的时候,一般会包含分发文件的校验和,这可以让你方便得知所下载的文件是否与原始文件匹配。不过单独的校验和不能提供验证(verification)工作:如果校验和被记录在你下载软件里的另一个文件中,则攻击者可以恶意的修改软件,然后只需要相应的修改校验和即可。
这个问题的解决方案是公钥加密(public-key cryptography)。在这种机制下,数据的安全保障来自两个相关密钥的存在:一个私密密钥,只有所有者知悉,以及一个公开密钥,任何人都可得知。两个密钥的其中一个用以加密,另一个则用于解密。公开密钥加密的安全性,依赖已知的公开密钥及可被该密钥解密的文本,以提供一条没有实际用途的信息但可被用来回复私密密钥。这一发明最大的突破是解决了一直以来密码学上极为严重的问题:在需要彼此沟通的对象之间,如何安全的交换加密密钥。
私密密钥与公开密钥是如何使用和运作的呢?假设Alice想对一个公开文件签名,她可以使用她的私密密钥(private key)为文件加密。之后Bob再使用Alice的公开密钥(public key)将签名后的文件解密,这么一来即可确信该文件为Alice所签名,而Alice无须泄漏其私密密钥,就能让文件得到信任。
如果Alice想传送一份只有Bob能读的信给他,她应以Bob的公开密钥为信件加密,之后Bob再使用它的私密密钥将信件解密。只要Bob妥善保管其私密密钥,Alice便可确信只有Bob能读取她的信件。
对整个信息加密其实是没有必要的:相对的,如果只有文件的校验和加密,它就等于有数字签名(digital signature)了。如果信息本身是公开的,这种方法便相当有用,不过还需要有方法验证它的真实性。要完整说明公开密钥加密机制,需要整本书才行,可参考《安全性与密码学》。
计算机越来越容易受到攻击,下载文件或软件要很注意安全。一般软件存档文件都并入了文件校验和信息的数字签名,如果不确定下载的东西是否安全,可以验证它。举例:
$ ls -l coreutils-5.0.tar*
-rw-rw-r-- 1 jones devel 6020616 Apr 2 2003 coreutils-5.0.tar.gz
-rw-rw-r-- 1 jones devel 65 Apr 2 2003 coreutils-5.0.tar.gz.sig
$gpg coreutils-5.0.tar.gz.sig #尝试验证此签名
gpg: Signature made Wed Apr 2 14:26:58 2003 MST using DSA key ID D333CBA1
gpg: Can't check signature: public key not found
验证失败,是因为我们还未将签名者的公开密钥加入gpg密钥环。我们可以在签名作者的个人网站找到公开密钥或者通过email询问。然而幸好使用数字签名的人多半会将他们的公开密钥注册到第三方(thrid-party)的公开密钥服务器,且该注册会自动地提供给其他的密钥服务器共享。
将密钥内容存储到临时文件如”temp.key",加到密钥环中:
$ gpg --import temp.key
然后就可以成功验证签名了。
上一篇:linux shell 根据进程名获取pid的实现方法
栏 目:Shell
下一篇:shell中的for循环用法详解
本文标题:shell脚本学习指南[四](Arnold Robbins & Nelson H.F. Beebe著)
本文地址:http://www.codeinn.net/misctech/6942.html