unix环境编程笔记: 第四章: 文件过滤程序

 

常用的过滤程序: 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建立数据库系统。

 

 

你可能感兴趣的:(编程,unix,正则表达式,String,ubuntu,character)