•  
  • 工具语言
  • shell脚本学习指南[四](Arnold Robbins & Nelson H.F. Beebe著)

shell脚本学习指南[四](Arnold Robbins & Nelson H.F. Beebe著)

Categories: 工具语言, 读书
Comments: No Comments
Published on: 2012 年 01 月 09 日

回忆起一件事情:之前用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 >= < <= == != ~ !~ 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(参数向量,或参数值),让命令行参数可用。给出例子说明其用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ 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选项结合性的参数是不可使用的。同样的,任何命令行程序也不可使用:

1
2
3
4
5
6
$ 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中所有的环境变量:

1
2
3
$ 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命令混写:

1
2
3
4
5
6
7
8
tmpfile = "/tmp/telephone.tmp"
comman = "sort > " tmpfile
for ( name in telephone)
    print name "\t" telephone[name] | command
close (command)
while((getline < tmpfile) > 0) 
   print
close(tmpfile)

close可以关闭打开的文件以解约可用资源。awk里也没有排序函数,以为它只需要复制功能强大的sort命令即可。

getline语句以及awk管道里的输出重定向都可与外部程序通信,system(command)函数提供的是第三种方式:其返回值是命令的退出码。所以上边的例子可以写成:

1
2
3
4
5
6
7
8
tmpfile = "/tmp/telephone.tmp"
for ( name in telephone)
    print name "\t" telephone[name] | > tmpfile
close (tmpfile)
system("sort < " tmpfile)
while((getline < tmpfile) > 0) 
   print
close(tmpfile)

对于被system()执行的命令并不需要调用close(),因为close()仅针对以I/O重定向运算符所打开的文件或管道,还有getline、print、printf。其他几个例子:
system("rm -f " tmpfile)
system("cat <这里有很多例子可供参考。

第十章文件处理

先讲了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变量 ??????????????????????????????????????????????????????TMPDIR????????trap???????????????????????</p>

<div class=View Code BASH

1
2
3
umask   077  #???????????????
TMPFILE=${TMPDIR-/tmp}/myprog." />    #产生临时性文件名
trap 'rm -f $TMPFILE' EXIT    #完成时删除临时文件

但是像/tmp/myprog. ?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????mktemp??????????????????????????OpenBSD???????X???????????????12?X??????<br />
$ TMPFILE=`mktemp /tmp/myprog.XXXXXXXXXXXX` || exit 1  </p>
<p>???????????????/dev/random?/dev/urandom??????????????????????????????????????????????????????/dev/urandom??????????????????????????????????????<br />
$ time dd count=1 ibs=1024 if=/dev/random > /dev/null #??1KB???<br />
$ time dd count=1024 ibs=1024 if=/dev/urandom > /dev/null #??1KB???<br />
???/dev/random????????????????????????????mktemp???????????????</p>

<div class=View Code BASH

1
2
$ TMPFILE=/tmp/secret.$(cat /dev/urandom | od -x | tr -d ' ' | head -n 1)
$ echo $TMPFILE

??????/dev/urandom????????????od???????????tr???????????????

???????locate?????????locate?????????????????????????????????????????????????????????????cron???????????updatedb?????locate??????????????????????????updatadb???????????????????????secure locate??slocate??????????????????????????????????

???????????type???????????????????????
$ type gcc cd ls

???????????????find???find???60???????????????????find???unix??????????????????????????????????????????????????????????????????????????
???find [ files-or-directories ] [ options ]
?????
-atime n ??n???????
-ctime n ??n????indoe???
-follow ???????
-group g ???g?????g????ID??????
-links n ????n???????
-ls ????ls -l ???
-mtime n ??n????????
-name 'pattern' ??????shell???????????????????shell???
-perm mask ?????????????????
-prune ?????????????
-size n ?????n???
-type t ????t????d?? f?? l????
-user u ????u??????u???ID??????

?find??????????????????????????????????????????????-exec???????????????????????????????

????find???????????find $HOME/. ! -user $USER ??????????????????????????????????????????$HOME/.??$HOME?????$HOME???????????????
-perm??????????????????????????????????????????????????????????????????????????????????????
-perm -002 #???????????????????
-perm -444 #?????????????
? -perm -444 #?????????????
-perm 444 ?????r--r--r--???
-perm +007 ???????????
? -perm +007 ????????????

-size??????????????????512?????????????find???????????c??????????k??kilobyte?KB????????????????????????????????????????????????????????????find $HOME/. -size +1024k ????????????????????1MB???? -size 0 ???????

-type??????????????????????d???f???l???
-follow????find??????????????????????????????file linkfile ????linkfile?broken symbolic link to *** ?????find??????????????find . -type l -follow #???????????

-links????????????????????????????????????;????????????????????????????????????????????????????????find . -links +1 ?

-atime?????-ctime inode???? ?-mtime ??????????????????????????????????????????????????????find . -mtime -7 #????????????
??????-newer filename ?????????????????????????????????????????????????touch -t date_time timestempfile???????????-newer??????????????????????

find???????????????????????????????????????-a?AND?????-o?OR????????????????????????????????
$ find . -size +0 -a -size -10 #????????5120???????
$ find . -size 0 -o -atime +365 #?????????????????
-a?-o?????????" /> ????????????????</p>
<p>???????find???????</p>

<div class=View Code BASH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#???html??????????????
$ cat $HOME/html2xhtml.sed
s/<H1>/<h1>/g
...
s:H2>:h2>:g
...
#??????html??
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???????????filesdirectories????????????????????????????crontab?????????????????????????????????????????????????????????????????????????????????

filesdirectories????GNU?find???? -fprint?????????????????????????????????????unix find????????10????

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#! /bin/sh -
# ???????????????????????????
# ???
#       filesdirectories directory
#??????????
IFS='
    '
?
PATH=/usr/local/bin:/bin:/usr/bin #??GNU find?-fprint??
export PATH
#?????????????????????
if [ $# -ne 1 ]
then
    echo "Usage: $0 directory" >&2
    exit 1
fi
#umask??????????????
umask 077  #??????
?
TMP=${TMPDIR:-/tmp}  #??????????,?????
#????????????????????????????????
#?(*.all.*)???????????????????????
TMPFILES="
    $TMP/DIRECTORIES.all." />  $TMP/DIRECTORIES.all. .tmp
    <span style=$TMP/DIRECTORIES.last01." />  $TMP/DIRECTORIES.last01. .tmp
    <span style=$TMP/DIRECTORIES.last02." />  $TMP/DIRECTORIES.last02. .tmp
    <span style=$TMP/DIRECTORIES.last07." />  $TMP/DIRECTORIES.last07. .tmp
    <span style=$TMP/DIRECTORIES.last14." />  $TMP/DIRECTORIES.last14. .tmp
    <span style=$TMP/DIRECTORIES.last31." />  $TMP/DIRECTORIES.last31. .tmp
    <span style=$TMP/FILES.all." />  $TMP/FILES.all. .tmp
    <span style=$TMP/FILES.last01." />  $TMP/FILES/last01. .tmp
    <span style=$TMP/FILES.last02." />  $TMP/FILES/last02. .tmp
    <span style=$TMP/FILES.last07." />  $TMP/FILES/last07. .tmp
    <span style=$TMP/FILES.last14." />  $TMP/FILES/last14. .tmp
    <span style=$TMP/FILES.last31." />  $TMP/FILES/last31. .tmp
    
#WD???????????????????
#??find???????????????
#?????????????????????cd????????
#??????????
#???????????????cd???????????????
#find????????????????????????????
#?????????????????????????????????
WD=$1
cd $WD || exit 1
#trap???????????????????
trap 'exit 1'           HUP INT PIPE QUIT TERM
trap 'rm -f $TMPFILES'  EXIT
#??find?????-name??????????????????????
#?-true???????????????????????-type f???
#??????????-fprint????????????????-type d
#??????????????????????
find . \
        -name DIRECTORIES.all -true \
    -o  -name 'DIRECTORIES.last[0-9][0-9]' -true \
    -o  -name FILES.all -true \
    -o  -name 'FILES.last[0-9][0-9]' -true \
    -o  -type f         -fprint $TMP/FILES.all." />  \
    -a      -mtime -31  -fprint $TMP/FILES.last31. </span> \
    <span style=-a      -mtime -14  -fprint $TMP/FILES.last14." />  \
    -a      -mtime -7   -fprint $TMP/FILES.last07. </span> \
    <span style=-a      -mtime -2   -fprint $TMP/FILES.last2." />  \
    -a      -mtime -1   -fprint $TMP/FILES.last1. </span> \
    <span style=-o  -type d         -fprint $TMP/DIRECTORIES.all." />  \
    -a      -mtime -31  -fprint $TMP/DIRECTORIES.last31. </span> \
    <span style=-a      -mtime -14  -fprint $TMP/DIRECTORIES.last14." />  \
    -a      -mtime -7   -fprint $TMP/DIRECTORIES.last07. </span> \
    <span style=-a      -mtime -2   -fprint $TMP/DIRECTORIES.last02." />  \
    -a      -mtime -1   -fprint $TMP/DIRECTORIES.last01. </span> \
<span style=#?find???????????????????????????????
#???????????????????????sed?????????
#?./???????????????????????????????
#sort?sed?????????????cmp????????????????
#?????????????????????
for i in FILES.all FILES.last31 FILES.last14 FILES.last07 \
        FILES.last02 FILES.last01 DIRECTORIES.all \
        DIRECTORIES.last31 DIRECTORIES.last14 \
        DIRECTORIES.last07 DIRECTORIES.last02 DIRECTORIES.last01
do
    sed -e "s=^[.]/=$WD/=" -e "s=^[.]$=$WD=" $TMP/$i." />  |
            LC_ALL=C sort > $TMP/$i. </span>.tmp
    <span style=cmp -s $TMP/$i." /> .tmp $i || mv $TMP/$i.$$.tmp $i
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位,因此两个不同文件最后散列出相同签名的可能性非常低。了解这个后可以写一个简单脚本来实现我们之前的目标了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#! /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
            print
            }'

程序很简单,就不弄注释了吧。可以测试一下:
$ 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)的公开密钥服务器,且该注册会自动地提供给其他的密钥服务器共享。比如:
美国 http://pgp.mit.edu/
德国 http://math-www.uni-paderborn.de/pgp/
也可另行搜索。将密钥内容存储到临时文件如”temp.key",加到密钥环中:
$ gpg --import temp.key
然后就可以成功验证签名了。

我猜你可能也喜欢:

No Comments - Leave a comment

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>


Welcome , today is 星期六, 2017 年 10 月 21 日