常用的过滤程序: grep、tail、sort、wc、ed
grep、 egrep、fgrep
tr、dd、uniq
sed(stream editor)、awk(作者的名字)都是grep派生的程序。
执行格式:$prog 模式行为 文件名
1. grep系列
$ grep 模式 文件名
选项: -n -v -y
模式中的元字符:
元字符 ^ 、$ 分别代表行首和行尾。
[a-z]: 匹配任何小写字母。
[^0-9]:匹配任何非数字字符。
. :匹配任意字符。
* : X*,[a-zA-Z]*, 匹配表示其前面的字符重复任意次。 .*, .*x 区别与shell的元字符*.
^[^:]*:表示行首任何非:的字符。
(): 聚合多个字符。
(a | b):选择关系。
+: x+ 表示一个或多个x。
?: x?表示零个或一个x。
包含aeiou五个元音一次且按字母序的单词模式:
^[^aeiou]*a[^aeiou]*e[^aeiou]*i[^aeiou]*o[^aeiou]*u[^aeiou]*$. 包含aeiou次序的单词。alphvowels、monotonic单调的。
字母序单调的单词模式:^a?b?c?d?e?f?g?h?i?j?k?l?m?n?o?p?q?r?s?t?u?v?w?x?y?z?$
wzb56@ubuntu:~$ ls -l | grep '^.......rw' #匹配当前目录中other用户能都写的文件。
wzb56@ubuntu:~$ ls -l | grep '^d' #匹配当前目录中的所有目录文件。
fgrep : 可以搜索许多文字串。不解释元字符串,并行检索大量的单词,类似于文件检索。
egrep: 解释真正的正则表达式。
格式: fgrep/egrep -f包含模式的文件名
grep与egrep正则表达式
c: 非特殊字符。
\c: 将元字符转义。
^ : 行首。
$grep '^[^:]*::' /etc/passwd #查找没有口令的用户
fgrep : 可以搜索许多文字串。不解释元字符串,并行检索大量的单词,类似于文件检索。
egrep: 解释真正的正则表达式。
格式: fgrep/egrep -f包含模式的文件名
grep与egrep正则表达式
c: 非特殊字符。
\c: 将元字符转义。
^ : 行首。
$ : 行尾。
. : 任意单字符。
[...]: 字符类 ... 中任意单个字符。
[^...]: 不在字符类中的任意单个字符。
\n: 与第n个\(..\)所匹配的字符。只在grep中使用。
r*: r出现任意次。
r+: r出现一次或多次。(egrep)
r?: r出现零次或一次。(egrep)
r1r2: r2紧跟r1。
r1 | r2 : r1 or r2 。(egrep)
\(r\): 带括号的正则表达式r,(r)。 (egrep)
(r): 正则表达式r, (egrep)。
练习: grep 搜索回文串。
二、其他过滤程序:
1. sort命令:
sort: 按照ascii表顺序逐行对其输入排序。
选项:
-f : 不区分大小写。
-d:字典序。
-n:数值。
-r: 对任意排序结构进行逆序。
+m:跳过开始m行。(linux下不能实现)
-o: 输出文件名。
-u:unique,删除排序区的重复行,重复行仅保留1行。
2. uniq命令:
uniq:对相邻的重复行,仅保留一个。可以删除多个空行,无论其输入是否排序。
选项:
-d: 打印重复的行。
-u:打印唯一的行。
-c: 求每行输出的次数。
3. comm命令:
comm: 文件比较程序。
对于给定的两个已排序文件f1与f2中,
comm打印输出分为3列:第一列仅出现在f1中的行,第二列仅出现在f2中的行,第三列公共的行,即都出现的行。
wzb56@ubuntu:~$ comm --help
Usage: comm [OPTION]... FILE1 FILE2
Compare sorted files FILE1 and FILE2 line by line.
With no options, produce three-column output. Column one contains
lines unique to FILE1, column two contains lines unique to FILE2,
and column three contains lines common to both files.
-1 suppress lines unique to FILE1
-2 suppress lines unique to FILE2
-3 suppress lines that appear in both files
--help display this help and exit
--version output version information and exit
Report bugs to <[email protected]>.
4. tr命令:
tr:对输入的字符进行翻译,其最常用的用途是大、小写转换。
wzb56@ubuntu:~$ tr --help
Usage: tr [OPTION]... SET1 [SET2]
Translate, squeeze, and/or delete characters from standard input,
writing to standard output.
-c, -C, --complement first complement SET1
-d, --delete delete characters in SET1, do not translate
-s, --squeeze-repeats replace each input sequence of a repeated character
that is listed in SET1 with a single occurrence
of that character
-t, --truncate-set1 first truncate SET1 to length of SET2
--help display this help and exit
--version output version information and exit
SETs are specified as strings of characters. Most represent themselves.
Interpreted sequences are:
\NNN character with octal value NNN (1 to 3 octal digits)
\\ backslash
\a audible BEL
\b backspace
\f form feed
\n new line
\r return
\t horizontal tab
\v vertical tab
CHAR1-CHAR2 all characters from CHAR1 to CHAR2 in ascending order
[CHAR*] in SET2, copies of CHAR until length of SET1
[CHAR*REPEAT] REPEAT copies of CHAR, REPEAT octal if starting with 0
[:alnum:] all letters and digits
[:alpha:] all letters
[:blank:] all horizontal whitespace
[:cntrl:] all control characters
[:digit:] all digits
[:graph:] all printable characters, not including space
[:lower:] all lower case letters
[:print:] all printable characters, including space
[:punct:] all punctuation characters
[:space:] all horizontal or vertical whitespace
[:upper:] all upper case letters
[:xdigit:] all hexadecimal digits
[=CHAR=] all characters which are equivalent to CHAR
Translation occurs if -d is not given and both SET1 and SET2 appear.
-t may be used only when translating. SET2 is extended to length of
SET1 by repeating its last character as necessary. Excess characters
of SET2 are ignored. Only [:lower:] and [:upper:] are guaranteed to
expand in ascending order; used in SET2 while translating, they may
only be used in pairs to specify case conversion. -s uses SET1 if not
translating nor deleting; else squeezing uses SET2 and occurs after
translation or deletion.
wzb56@ubuntu:~$ tr a-z A-Z < list.c # 小写转化为大写
wzb56@ubuntu:~$ tr A-Z a-z < list.c #大写转化为小写
5. dd命令:
dd: 常被用于处理原始的、未格式化的数据,无论什么数据源。
组合的过滤程序:
cat $* |
tr -sc A-Za-z '\012' | # 非字符转化为换行符
sort |
uniq -c |
sort -n|
tail
三、流编辑器:sed(stream editor)由ed派生而来。
sed的基本思想:
$sed '一系列ed命令' 文件名
从输入文件中一次一行地读取,按顺序将列表中的命令应用到每一行,并将其编辑过的行写道标准输出。
eg: $sed 's/unix/linux/g' 文件名 > output.file
$sed '...' file > file是个错误的命令。一定要使用临时文件或另一个程序。
wzb56@ubuntu:~/sh$ du -a test*
4 test.txt
4 test1.txt
wzb56@ubuntu:~/sh$ du -a test* | sed 's/.*\t//'
test.txt
test1.txt
wzb56@ubuntu:~/sh$ who am i
wzb56 pts/11 Oct 8 08:40 (211.87.235.109)
sed命令汇总:
a \: 将行添加到输出直至不以 \ 为终结符的行。
b 标号: 转到命令:标号。
c \: 对随后的文本如同a命令那样组行修改。
d: 删除行,读下一个输入行。
i \: 在下一个输出前插入下面的文本。
l : 按行列表使所有的非打印字符可见。
p : 打印行。
q: 退出。
r file: 读文件,拷贝内容到输出。
s/oldString/newString/f 用newString替换oldString,若f=g,置换所有;
若f=p,打印;若f=w 文件,写入文件。
t 标号: 测试:若对当前行进行了替换,转至标号。
w 文件:将行写到文件。
y/string1/string2/ : 用string2中的每个字符替换串1中的每个字符(不支持范围值)
= : 打印当前的输入行号。
! : 对命令取反。
: : 为b和t设置标号。
{ : 将{}之间的一组命令视为一组命令。
四、模式扫描与处理语言awk
awk 操作细节更多的是基于c程序设计语言,而不是文本编辑程序,用法也同与sed。
$ awk '程序' 文件名
但是程序不同:
模式 { action}
awk一次一行地读文件名中的输入,依次将每行与每个模式相比较,对每个与行匹配的模式,执行器相对应的操作。 和sed一样,awk不改变其输入文件。awk可以使用egrep一样的正则表达式,实现egrep一样的功能:打印与正则表达式匹配的每一行。
$awk '/正则表达式/{print}' 文件名:将与正则表达式匹配的行打印。
$awk '/正则表达式/' 文件名: 将与正则表达式匹配的行打印。
$awk '{print}' 文件名 :将文件打印。
$awk -f 命令文件 文件 : 将命令文件中命令传给awk,对文件操作。
wzb56@ubuntu:~/sh$ awk '/wzb/{print}' test
wzb56
wzb56@ubuntu:~/sh$
1. awk字段:awk自动将每个输入行分成字段, 即有空格或Tab分隔的非空格字符串。
awk称这些字段为 $1、$2、$3、.... 、$NF, 其中NF为一变量,其值被设置为字段的个数。
(注意:NF 与$NF两者之间的区别,NF指字段的个数,$NF是指行中的最后一个字段,不同于shell,在awk中只有字段是以$开始,变量是不加次修饰的)。
eg:去除du -a产生的文件大小
$du -a | awk '{print "}'
$who | awk '{print $1, $5}'
$who | awk '{print $5, $1}' | sort
awk 通常假设使用空白符(任意数量的空格或tab)分隔字段,但是分隔符可以用任意字符代替,
一种方法是用-F命令选项指定,
eg:为了打印 /etc/passwd 中的第一个字段的用户名可以用:
$awk -F: '{print $1}' /etc/passwd
wzb56@ubuntu:~/sh$ awk -F: '{print $1}' /etc/passwd
wzb56@ubuntu:~/sh$ du -a
4 ./at.sh
4 ./test1.txt
4 ./filter.sh
4 ./test.txt
4 ./test
24 .
wzb56@ubuntu:~/sh$ du -a |awk '{print $1}'
./at.sh
./test1.txt
./filter.sh
./test.txt
./test
.
wzb56@ubuntu:~/sh$
2. 打印:
除了字段的个数外,awk还记载了其他有意义的数量。内嵌变量NR是当前输入的“记录”或行的序数。
为了把行的序数添加到一输入流数据中, 可以使用:
$awk '{ print NR, $0}' #NR表示行计数,$0是未加修改的整个输入行。
wzb56@ubuntu:~/sh$ awk -F: '{print NR, $0, NF}' /etc/passwd
wzb56@ubuntu:~/sh$ awk -F: '{printf "#%4d,%s,%d\n", NR, $0, NF}' /etc/passwd
printf 的打印格式的控制和C语言中printf格式控制几乎一致:
#%4d指定字段中十进制数NR为四个字符宽度, %s表示字符串$0; \n表示换行,这是因为prinf不自动打印任何空格或换行。awk中的prinf语句与C语言中的printf类似。
$ awk '{printf "\t%s\n", $0}' $* #在行首插入tab符。
3. 模式
假设查找/etc/passwd 中没有口令的用户。加密口令位于第二个字段,因此程序使用如下模式:
$awk -F: '$2 =="" ' /etc/passwd #使用了相等测试符 == 测试字段2
相等操作模式的其他写法:
$2 == "" #第二个字段为空
$2 ~ /^$/ #第二个字段与空串匹配
$2 !~ /./ #第二个字段不与任何字符串匹配
length($2) == 0 #第二个字段的长度为0.
其中,符号~表示与正则表达式匹配, !~的意义是“不匹配”。正则表达式包括在/正则表达式/之内。
length是awk的内部函数,他得到字符串的长度。 可以在模式!,使其取反,如!($2 == "").
awk 通常用于简单的数据验证任务。
使用%操作符进行取余操作。
命令模式:
NF%2 !=0 #若字段数为奇数则打印。
length($0) >72 #若长度足够长则打印。
若输出过长:
length($0) > 72 { print "Line", NR, " too long: ", substr($0, 1, 60) }
#substr(s, m, n)产生s的子串。eg:打印date中小时和分钟
wzb56@ubuntu:~/sh$ date
Sat Oct 8 19:01:27 CST 2011
wzb56@ubuntu:~/sh$ date | awk '{print substr($4,1,5)}'
19:02
wzb56@ubuntu:~/sh$
4. BEGIN与END模式
awk提供了两个特殊的模式,BEGIN和END. BEGIN在读入第一个输入行之前执行,可以使用BEGIN 模式初始化变量,打印标题头,或通过指定变量FS设置字段分隔符。
wzb56@ubuntu:~/sh$ awk 'END {print NR} ' /etc/passwd
33
wzb56@ubuntu:~/sh$ awk 'END {print NR} $0 ' /etc/passwd
5. 算术运算与变量
awk不仅能进行文本处理,还能对输入的数据进行计算,可以很容易的实现计数、累计总和和求平均数等功能。
awk的常用功能求各个字段的总和。
eg:
{s = s + $1 }
END { print s }
由于行的个数可以由 NR中得到,因此,可以求平均值:
END { print s/NR }
其中,s是使用BEGIN模式初始化定义的变量。
awk提供了和C一样的算术操作 :
{ s+=$i }
END { print s}
对输入的求和,功能类似 wc:
{
nc += length($0) + 1 #number of chars, 1 for \n
nw += NF #number of words
}
END { print NR, nw, nc}
#计算输入的行数,单词数,字符数。
统计pr打印产生的页数:
$ wc $* | awk '!/total$/ { n += int( ( $1 + 55) \56) } END {print n} '
$ pr $* | awk 'END {print NR\56 }'
wzb56@ubuntu:~/sh$ pr test | awk 'END {print NR/66}'
2
wzb56@ubuntu:~/sh$ wc test | awk 'BEGIN {n=0} !/total/ { n += int(($i+55)/56)} END {print n}' 2
wzb56@ubuntu:~/sh$
wzb56@ubuntu:~/sh$ wc -l test
576 test
wzb56@ubuntu:~/sh$ pr test | awk 'END {print NR/66}'
11
wzb56@ubuntu:~/sh$ wc test | awk 'BEGIN {n=0} !/total/ { n += int(($i+55)/56)} END {print n}'
11
wzb56@ubuntu:~/sh$
awk 中的变量既可以是数字字符串也可以是字符字符串。取决与上下文。
awk的内部变量:
FILENAME: 当前输入文件的名。
FS: 字段分隔符(缺省为空白符:空格或tab符)
NF:输入记录中的字段个数。
NR:输入记录数或行数。
OFMT:数字的输出格式(缺省为%g 参见printf(3))。
OFS:输出字段的分隔符(缺省为空格)。
ORS:输出记录的分隔符(缺省为换行\n)。
RS: 输入记录分隔符(缺省为换行\n)。
awk操作符的优先级(升序)
= 、+=、 -= 、*=、 /= 、%= :赋值操作。
|| :逻辑或。
&& : 逻辑与。
! : 表达式求反。
>、 >=、 < 、<=、 ==、 !=、 ~、 !~ :关系操作,~ 匹配操作符。
nothing :字符串连接。
+、 - : 加、减
*、 /、 % : 乘、除、取余
++ 、--: 自加、自减
6. 控制流
awk '
FILENAME != prefile { #new file
NR = 1 #reset line number
prefile = FILENAME
}
NF >0 {
if($1 ==lastword)
printf " kesrc="mailto:wzb56@ubuntu:~/sh$2>wzb56@ubuntu:~/sh$
awk 中的变量既可以是数字字符串也可以是字符字符串。取决与上下文。
double的shell脚本:
#!/bin/sh
awk '
FILENAME != prefile { #new file
NR = 1 #reset line number
prefile = FILENAME
}
NF >0 {
if($1 ==lastword)
printf "double %s, file%s, line %d\n", $1, FILENAME, NR
for(i=2; i<=NF; i++)
if($i == $(i-1))
printf "double %s, file %s, line %d\n", $i, FILENAME, NR
if(NF >0)
lastword = $NF
}' $*
内部变量FILENAME表示当前输入的文件名。
由于NR从开始输入时,就开始计数, 因此,文件名改变时,将其复位。
awk中的if语句与c语言中的语句相同。
awk中的for语句与c语言中的语句相同。
awk中的while语句与c语言中的while语句相同。
break,continue都与c语言中的break、continue一致。
next语句:促使从awk程序起始处重新开始读入下一个输入行和匹配模式。
exit语句:则立即转向END模式。
7. 数组
awk中的数组,利用数组将输入行的收集到数组元素中,然后以行数为索引,逆向输出:
backwards.sh
#!/bin/sh
awk '{ line[NR]=$0 } END{ for(i=NR; i>0; i--) print i, line[i] }' $*
(注意格式:awk '模式{action}' $* 最后$* 与‘之间只能有一个空格,不能有换行否则出现错误!)
有可能造成内存溢出!
为了不去读中间的数据而直接读文件的尾部, tail充分利用了seeking文件系统操作。 tail -r与上述脚本的作用相同。
对标准输入的处理:将输入行分成若干个字段,可以是使用内部函数spllit:
n = split(s,array,separator):将字符串s分割成有separator分割的n个部分放在array的1到n个元素中。若无separator,则使用FS。
awk中的内嵌函数:
cos(表达式) :余弦。
sin(表达式): 正弦。
exp(表达式):自然指数。
log(表达式):自然对数。
int(表达式):表达式的整数部分,向零截取。
getline():读入下一行:若文件结束,返回零;否则,返回1。
index(string1,string2):查找字string1中string2的位置,若不存在,则返回0。
length(string):返回字符串的长度。
n=split(string,array,separator):返回n,将string按separator分隔为n部分,分别放在 array[1]...array[n]中。
sprintf(格式,...):格式化 ...依据格式说明。
substring(字符串,m,n):起始于m的字符串的长度为n子串。
8. 关联数组
在awk中任意数值都可以作为下标。
(1)、一组数据,第一个字段对应多组值,把第一个字段对应的第二个字段的数据加和。
{ sum[$1] +=$2 }
END { for (name in sum ) print name, sum[name] }
说明:这种加和算法,相当于哈希(hash);for语句使用了变形的for语句:
for(数组中的变量)
语句
对求和的结果进行排序:
$ awk '....' | sort +1nr
关联数组的存储是用散列方法实现的,以确保对任何元素的存取都使用一样的时间,且时间不依赖于数组中元素的个数,(至少中等规模的数组是这样)。
(2)、用关联存储对于诸如求输入中所有单词的出现次数,处理效率很高:
wordfreq.sh
awk ' { for( i=1; i<=NF; i++) num[$i]++ }
END { for (word in num) print word, num[word] }' $*
思考,哈希存储的设计。
$sed 's/[\t\s]*//g' $* | sort | uniq -c | sort -nr
9. 字符串
sed和awk都可用于处理诸如选取单个字段这样的小工作,但自由awk可用于真正需要编程的各种工作。
在fold中使用sed来讲tab转换为空格,使得awk的字符计数保持正确。
fold.sh
wzb56@ubuntu:~/sh$ cat fold.sh
#!/bin/sh
sed 's/\t/ /g' $* | #convert tabs to 8 spaces
awk '
BEGIN {
N=80; #fold at column 80;
for( i=1; i<=N; i++) #make a string of blanks
blanks = blanks" "
}
{
if( ( n=length($0) ) <=N )
print $0
else {
for(i =1; n>N; n -=N) {
printf "%s\\\n", substr($0, i, N)
i +=N;
}
printf "%s%s\n", substr(blanks, 1, N-n), substr($0, i)
}
}'
wzb56@ubuntu:~/sh$
在awk中没有明显的字符串连接操作符,当字符串相邻时,就被连接。
10. 与shell的交互作用
(1)程序field n打印输出每一行的的第n个字段:
eg: $who | field 1 #打印登录名
00:field.sh
wzb56@ubuntu:~/sh$ cat field.sh
#!/bin/sh
awk '{print $'$1'}'
01:field.sh
wzb56@ubuntu:~/sh$ cat field.sh
#!/bin/sh
awk "{print \$$1}"
wzb56@ubuntu:~/sh$ who | sh field.sh 1
lw_987
bytexu
berryfl
zFish
mxdu
Batcher
witas
zte_zxr10
(2) addup n: 将第n个字段求和。
wzb56@ubuntu:~/sh$ cat addup.sh
#!/bin/sh
awk '{ s += $'$1'}
END { print s}'
wzb56@ubuntu:~/sh$ cat -n who.txt | sh addup.sh 1
300
wzb56@ubuntu:~/sh$
(3)对n列中的每列分别单独求和,让后再将各列之和相加:
wzb56@ubuntu:~/sh$ cat add.sh
#!/bin/sh
awk 'BEGIN { n='$1'}
{
for(i=1; i<=n; i++)
sum[i] += $i
}
END {
for( i=1; i<=n; i++) {
printf "%6g ", sum[i]
total += sum[i]
}
printf "; total = %6g\n", total
}'
wzb56@ubuntu:~/sh$ sh add.sh 2
1 1
2
3 3
6 4 ; total = 10
11.基于awk的日历服务
at
atq
atrm
12. 附注:
awk是一种十分广泛的语言。printf 的输出重定向到文件和管道。print,printf后更>、>> | 等用法。
五、好的文件与过滤程序。
unix下,将一个大问题分成若干个可由过滤程序解决的子问题,而这些过滤程序被联合成一个管道。
这中使用工具的方法被认为是unix程序设计环境的核心。
unix程序产生的输出,可以作为其他程序的输入,其格式能为其他输入程序所理解。可过滤的文件包括:文本文件,文件头、文件尾或空行。每一行都是一个有意义的对象--文件名、单词,对运行过程的描述。--因此像wc和grep这样的程序可计算有意义项的总和,或者按名查找。
过滤程序的设计思想:每个都是将对参数文件的处理结果写到标准输出,若是没有给出输入参数则使用标准输入。 参数只用于指定输入,而不指定输出。因此,一个命令的输出总是可以送往管道 ( time 除外,其输出为stderr 2)。可选参数位于任意文件名前。最后,错误信息总是写到标准输出上,因此,不会送到管道中。
(没有消息是最好的消息)
13.有关文献
1. pattern matching in string:模式匹配算法的详细说明。1979
egrep的作者Al, Aho。
sed由Lee McMahon设计与实现,它以ed为基础。
awk由Al Aho、Peter Weinberger 和Brain Kernighan设计和实现的,它远不止于一个流编辑器设计,而是以作者的名字命名的一种语言,也表明了其缺乏想象力。
2. AWK--a pattern scannning and processing language" 论述了其设计。
awk来源于多个方面,但肯定来源有SNOBOL4、sed、Marc Rochkind设计的有效性语言、语言工具yacc和lex、当然还有从C语言中窃取的一些好的思想。awk的语法结构像C而不是C,一些语法结构没了,其他的只有微小的差别。这中相似性也是产生问题的根源。
3.the fat file system FFG: a database system consisting of primitives,论述了用shell和awk建立数据库系统。