Bash: 三大日志文本处理命令grep、sed及awk

原作者:呼伦贝尔
http://lq2419.blog.51cto.com/1365130/1238880
重排版:AT

grep、sed和awk都是文本处理工具,虽然都是文本处理工具单却都有各自的优缺点,一种文本处理命令是不能被另一个完全替换的,否则也不会出现三个文本处理命令了。只不过,相比较而言,sed和awk功能更强大而已,且已独立成一种语言来介绍。

命令简介

  • grep

    文本过滤器,如果仅仅是过滤文本,可使用grep,其效率要比其他的高很多;
  • sed

    Stream EDitor,流编辑器,默认只处理模式空间,不处理原数据,如果你处理的数据是针对行进行处理的,可以使用sed;
  • awk

    报告生成器,格式化以后显示。如果对处理的数据需要生成报告之类的信息,或者你处理的数据是按列进行处理的,最好使用awk。

grep命令使用

  • grep是一个最初用于Unix操作系统的命令行工具。

    在给出文件列表或标准输入后,grep会对匹配一个或多个正则表达式的文本进行搜索,并只输出匹配(或者不匹配)的行或文本。Unix的grep家族包括grep、egrep和fgrep。

  • egrep和fgrep的命令只跟grep有很小不同。

    egrep是grep的扩展,支持更多的re元字符,
    fgrep就是fixed grep或fast grep,
    它们把所有的字母都看作单词,也就是说,正则表达式中的元字符表示回其自身的字面意义,不再特殊。

  • linux使用GNU版本的grep。

    它功能更强,可以通过-E、-F命令行选项来使用egrep和fgrep的功能。

  • grep的工作方式是这样的,

    它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到屏幕,不影响原文件内容。
    grep可用于shell脚本,因为grep通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。我们利用这些返回值就可进行一些自动化的文本处理工作。
    grep格式说明

    grep:根据模式搜索文本,并将符合模式的文本行显示出来。   
    Pattern:文本字符和正则表达式的元字符组合而成匹配条件
    
    使用格式:
    grep [options] PATTERN [FILE...]
        -i:忽略大小写    
        --color:匹配到字符用其他颜色显示出来,默认是红色
        -v:显示没有被模式匹配到的行
        -o:只显示被模式匹配到的字符串,不显示行
        -E:使用扩展正则表达式
        -A n:表示显示该行及其后n行
        -B n:表示显示该行及其前n行
        -C n:表示显示该行及其前后各n行
    
    正则表达式:REGular EXPression,REGEXP
    
    元字符:
    .:匹配任意单个字符
    []:匹配指定范围内的任意单个字符
    [^]:匹配指定范围外的任意单个字符
    
    `字符集和:[:digit:],[:lower:],[:upper:],[:punct:],[:space:],[:alpha:],[:alnum:]`
    对应上边:数字,小写字母,大写字母,标点符号,空白字符,所有字母,所有数字和字母
    
    匹配次数(贪婪模式,即尽可能长的匹配):
    *:匹配其前面的字符任意次 
       如:编辑文件abc,输入这些字符
       a,b,ab,aab,acb,adb,amnb,
       使用grep匹配a*b,命令及显示效果如下所示:
        grep1.jpg
        从上图可以看到,虽然像acb、adb之类的字符串也可以显示出来,
        但从显示的颜色可以看到,匹配的仅仅是字符b,
        虽然整个字符串中也有字符a,
        但是此时a并没有与b一起紧挨着出现,
        所以仅匹配到字符b;
        而像ab、aab等字符串,全部显示,
        此时a紧挨着b出现,符合条件,所以可以显示。
    
    .*:任意长度的任意字符
        如:匹配.b和.*b,看二者有什么区别,命令和显示效果如下:
        grep3.jpg
    
    \?:匹配其前面的字符1次或0次
        如:继续上边的例子,现在匹配a\?b,显示效果如下所示:
    grep2.jpg
    
    \{m,n\}:匹配其前面的字符至少m次,至多n次
       \{1,\}:至少一次
       \{0,3\}:最多三次
    
    位置锚定:
    ^:锚定行首,此字符后面的任意内容必须出现在行首    
    $:锚定行尾,此字符前面的任意内容必须出现在行尾
    ^$:空白行
    \<或\b:锚定词首,其后面的任意字符必须作为单词首部出现
    \>或\b:锚定词尾,其后边的任意字符必须作为单词尾部出现
    
        如:\<root\>:在整个文件中,把root作为整个单词出现,词首词尾都不行,如:mroot、rooter均将不匹配
    举几个例子来说明下上边的位置锚定。
    grep5.jpg
    
    分组:
    \(\)
       如: \(ab\)*:ab整体作为匹配字符,且匹配任意次
              \(ab\)\{1,\}:ab整体作为匹配字符,且匹配至少一次
              \(ab\):ab整体作为匹配字符
    
        后向引用
        \1:匹配第一个左括号以及与之对应的右括号所包括的所有内容
        \2:匹配第二个左括号以及与之对应的右括号所包括的所有内容
        \3:匹配第三个左括号以及与之对应的右括号所包括的所有内容
        ......
    

grep命令

  [root@www ~]# grep '\(l..e\).*\1' test1    #匹配前边第一个左括号的以及与之对应的右括号的内容
  He love his lover.
  She like her liker.
  [root@www ~]# grep 'l..e' test1          #匹配指定的字符,中间可为任意两个字符
  He love his lover.
  She like her liker.
  He love his liker.
  She like her lover.
  He like her.
  She love he.

grep练习

  1. 显示/proc/meminfo文件中以不区分大小的s开头的行;
    grep -i '^s' /proc/meminfo
    grep '^[sS]' /proc/meminfo
    #[]表示匹配指定范围内的单个字符,因此也可实现不区分大小写

  2. 显示/etc/passwd中以nologin结尾的行;
    grep 'nologin$' /etc/passwd
    扩展一:取出默认shell为/sbin/nologin的用户列表
    grep '/sbin/nologin' /etc/passwd | cut -d: -f1
    或者
    grep '/sbin/nologin' /etc/passwd | awk -F: '{print $1}'
    或者直接使用awk
    awk -F: '$7 ~ /nologin/{print $1}' /etc/passwd
    扩展二:取出默认shell为bash,且其用户ID号最小的用户的用户名
    grep 'bash$' /etc/passwd | sort -n -t: -k3 | head -1 | cut -d: -f1
    或者
    awk -F: '$7 ~ /bash/{print $3,$1}' /etc/passwd | sort -n | head -1 | awk '{print $2}'

  3. 显示/etc/inittab中以#开头,且后面跟一个或多个空白字符,而后又跟了任意非空白字符的行;
    grep '^#[[:space:]]\{1,\}[^[:space:]]' /etc/inittab

  4. 显示/etc/inittab中包含了:一个数字:(即两个冒号中间一个数字)的行;
    grep ':[0-9]:' /etc/inittab

  5. 显示/boot/grub/grub.conf文件中以一个或多个空白字符开头的行;
    grep '^[[:space:]]\{1,\}' /boot/grub/grub.conf

  6. 显示/etc/inittab文件中以一个数字开头并以一个与开头数字相同的数字结尾的行;
    grep '\(^[0-9]\).*\1$' /etc/inittab
    #在RHEL5.8以前的版本中可查看到效果

  7. 找出某文件中的,1位数,或2位数;
    grep '<[[:digit:]][[:digit:]]?>' /etc/inittab
    或者
    grep '<[0-9]{1,2}>' /etc/inittab

  8. 查找当前系统上名字为student(必须出现在行首)的用户的帐号的相关信息, 文件为/etc/passwd
    grep '^student:' /etc/passwd
    扩展:若存在该用户,找出该用户的ID号:
    grep '^student:' /etc/passwd | cut -d: -f3 或者# id -u student
    思考题:用多种方法找出本地的IP地址,这里给出三种方法,如果你还有其他方法可以一起分享下:
    ifconfig eth0|grep -oE '([0-9]{1,3}.?){4}'|head -n
    ifconfig eth0|awk -F: '/inet addr/{split($2,a," ");print a[1];exit}'
    #这里使用了awk的内置函数,如果不懂可在看完awk的介绍后再来做此题
    ifconfig |grep "inet addr"|grep -v "127.0.0.1" |awk -F: '{print $2}' |awk '{print $1}'


sed命令使用

  • sed(意为流编辑器,源自英语“stream editor”的缩写)
    是Unix常见的命令行程序。
    sed 用来把文档或字符串里面的文字经过一系列编辑命令转换为另一种格式输出。
    sed 通常用来匹配一个或多个正则表达式的文本进行处理。
    sed是一种在线编辑器,它一次处理一行内容。
    处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。
    Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

sed使用格式:

sed 'AddressCommand' file ...
    -n:静默模式,不再显示模式空间中的内容
    -i:直接修改原文件
    -e SCRIPT -e SCRIPT:添加脚本到将要执行的命令中,可以同时执行多个脚本
    -f /PATH/TO/SED_SCORIPT:添加脚本文件中的内容到将要执行的命令中
         #sed -f /path/to/script file
    -r:表示使用扩展正则表达式

Address:
1、StartLine,EndLine:开始行,结束行
   如:1,100:表示从第1行到第100行
    $;最后一行
2、/RegExp/:扩展正则表达式
   如:/^root/
3、/pattern1/,/pattern2/:表示第一次被pattern1匹配到的行开始,至第一次被pattern2匹配到的行结束,这中间的所有行
4、LIneNumber:指定的行
5、StartLIne,+N:从StartLine开始,向后的N行

Command:
    d:删除符合条件的行;
    p:显示符合条件的行,在不使用-n选项时被匹配到的行会显示两遍,因为sed处理时会把处理的信息输出
    a \string:在指定的行后面追加新行,内容为“string”,
        显示两行或多行,在每行后加\n进行换行
    i \string:在指定的行前面添加新行,内容为string
    r file:将指定的文件的内容添加至符合条件的文件中
    w file:将地址指定范围内的行另存至指定的文件中
    s/pattern/string/修饰符:查找并替换,默认只替换每行中第一次被模式匹配到的字符串
        加修饰符
         g:全局替换,如:s/pattern/string/g
         i:忽略字符大小写,如:s/pattern/string/i
    s///,s###,s@@@都可以,当所使用的分割符号与内容中显示的相同时,需使用转义字符转义    
        \(\),\1,\2:成组匹配,\1表示匹配第一个‘(’,\2表示匹配第二个‘(’
    &:引用模式匹配到的整个串
介绍了那么多,现在从我们的系统中复制个文件,作为我们的测试文件,这里,我们复制/etc/inittab文件,并稍加修改,删掉几行,然后作为测试文件使用。
  1. cp /etc/inittab ./
    #复制该文件到当前目录下,这里当前处于root家目录下。
    vim inittab
    #编辑该文件,修改此文件,内容如下所示,其内容也可参看下边的图片

    # inittab is only used by upstart for the default runlevel.
    #
              #此处是一空行,没有任何字符,就光空行添加代码时会被删掉,所以这里加上备注,说明是一空交行,下面相同
    # Individual runlevels are started by /etc/init/rc.conf
    #
               #此处是一空行,没有任何字符。该文件内容也看参见下边的图片
    # Default runlevel. The runlevels used are:
    #   0 - halt (Do NOT set initdefault to this)
    #   1 - Single user mode
    #   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
    #   3 - Full multiuser mode
    #   4 - unused
    #   5 - X11
    #   6 - reboot (Do NOT set initdefault to this)
    #
    id:3:initdefault:
    

用复制并修过的inittab文件作为测试文件,举几个例子:

  1. [root@www ~]# sed '1,3d' inittab
    #表示删除文件的第一到第三行
  2. [root@www ~]# sed '3,$d' inittab
    #表示删除模式空间中的第三到最后一行
  3. [root@www ~]# sed '/run/d' inittab
    #表示删除被run匹配到的行
  4. [root@www ~]# sed '/^#/d' inittab
    #表示删除文件中以#开头的行
  5. [root@www ~]# sed -n '/^\//p' inittab
    #显示以/开头的行,因为没有这样的行,故不显示任何信息
  6. [root@www ~]# sed '/^#/p' inittab
    #显示以#开头的行,可以看到被匹配到的行,均显示了两遍,这是因为sed处理时会把处理的信息输出
  7. [root@www ~]# sed -n '/^#/p' inittab
    #显示以#开头的行,使用-n选项表示仅显示匹配到的行

为了后边的演示,创建文件test,添加如下内容:

Welcome to my linux!
This is my world.
How are you?
  1. [root@www ~]# sed '2r ./test' inittab
    #表示将test文件中的内容添加到inittab文件中,且从第二行往后开始添加
  2. [root@www ~]# sed '1,2r ./test' inittab
    #表示将test文件中的内容添加到inittab文件中,且分别 添加在第一和第二行后边
  3. [root@www ~]# sed 's/linux/LINUX/g' test
    #查找该文件中的字符串,然后替换为指定的字符串
  4. [root@www ~]# sed 's/y.u/&r/g' test
    #查找指定的字符串,并将其替换为在其后加上r,分隔符采用/
  5. [root@www ~]# sed 's@y.u@&r@g' test
    #意义同上,分隔符采用@
  6. [root@www ~]# sed 's#y.u#&r#g' test
    #意义同上,分隔符采用#

sed练习;

  1. 删除/etc/grub.conf文件中行首的空白符;
    sed -r 's/^[[:space:]]+//' /etc/grub.conf
  2. 替换/etc/inittab文件中“id:3:initdefault:”一行中的数字为5;
    sed 's/\(id:\)[0-9]\(:initdefault:\)/\15\2/g' /etc/inittab
  3. 删除/etc/inittab文件中的空白行;
    sed '/^$/d' /etc/inittab
  4. 删除/etc/inittab文件中开头的#号;
    sed 's/^#//g' /etc/inittab
  5. 删除某文件中开头的#号及其后面的空白字符,但要求#号后面必须有空白符;
    sed 's/^#[[:space:]]\{1,\}//g' /etc/inittab
    sed -r 's/^#[[:space:]]+//g' /etc/inittab
  6. 删除某文件中以空白字符后面跟#类的行中的开头的空白字符及#
    sed -r 's/^[[:space:]]+#//g' /etc/inittab
  7. 取出一个文件路径的目录名称;
    echo "/etc/rc.d/abc/edu/" | sed -r 's@^(/.*/)[^/]+/?@\1@g'
    #因sed支持扩展正则表达式,在扩展正则表达式中,+表示匹配其前面的字符至少1次
  8. 取出一个文件路径的最后一个文件名;
    echo "/etc/rc.d/abc/edu/" | sed -r 's@^/.*/([^/]+)/?@\1@g'

awk命令使用

  • awk是一种优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一。
  • 这种编程及数据操作语言(其名称得自于它的创始人Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母)的最大功能取决于一个人所拥有的知识。
  • AWK提供了极其强大的功能:可以进行正则表达式的匹配,样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。
  • 它具备了一个完整的语言所应具有的几乎所有精美特性。实际上AWK的确拥有自己的语言:AWK程序设计语言,三位创建者已将它正式定义为“样式扫描和处理语言”。
  • 它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
  • 最简单地说,AWK是一种用于处理文本的编程语言工具。我们现在使用最多的是gawk,gawk是AWK的GNU版本。

awk使用格式:

# awk [options] 'script' file1, file2, ...
# awk [options] 'PATTERN { action }' file1, file2, ...

awk的输出:print和printf

一、print

print的使用格式:
print item1, item2, ...
要点:

  1. 各项目之间使用逗号隔开,而输出时则以空白字符分隔;
  2. 输出的item可以为字符串或数值、当前记录的字段(如$1)、变量或awk的表达式;数值会先转换为字符串,而后再输出;
  3. print命令后面的item可以省略,此时其功能相当于print $0, 因此,如果想输出空白行,则需要使用print "";

例子:

1 awk 'BEGIN { print "line one\nline two\nline three" }'
2 awk -F: '{ print $1, $7 }' /etc/passwd
#等价于:awk -v FS=: '{print $1,$7}' /etc/passwd
awk2.jpg

二、awk变量

  1. awk内置变量之记录变量:
   FS: field separator,字段分隔符,默认是空白字符;
   RS: Record separator,记录分隔符,默认是换行符;
   OFS: Output Filed Separator,输出字段分隔符
   ORS:Output Row Separator,输出行分隔符

一起来看一个示例:

vim test.txt #编辑该文件,添加如下两行信息作为示例使用

welcome to redhat linux.
how are you?
  1. [root@www ~]# awk 'BEGIN{OFS="#"} {print $1,$2}' test.txt
    #指定输出时的分隔符

  2. [root@www ~]# awk 'BEGIN{OFS="#"} {print $1,"hello",$2}' test.txt
    #指定输出时的分隔符,并添加显示的内容

  3. [root@www ~]# awk -v FS=: -v OFS=# '{print $1,$7}' /etc/passwd
    #以:为字段分隔符,以#号为输出分隔符,显示该文件的第一及第七字段的值,这里仅贴出部分显示内容

    root#/bin/bash
    bin#/sbin/nologin
    daemon#/sbin/nologin
    adm#/sbin/nologin
    lp#/sbin/nologin
    sync#/bin/sync
    shutdown#/sbin/shutdown
    halt#/sbin/halt
    
  1. awk内置变量之数据变量:
NR: The number of input records,awk命令所处理的记录数;如果有多个文件,这个数目会把处理的多个文件中行统一计数;
NF:Number of Field,当前记录的字段个数,有时可用来表示最后一个字段
FNR: 与NR不同的是,FNR用于记录正处理的行是当前这一文件中被总共处理的行数;
ARGV: 数组,保存命令行本身这个字符串,
#如awk '{print $0}' a.txt b.txt这个命令中,ARGV[0]保存awk,ARGV[1]保存a.txt;
ARGC: awk命令的参数的个数;
FILENAME: awk命令所处理的文件的名称;
ENVIRON:当前shell环境变量及其值的关联数组;
#awk 'BEGIN{print ENVIRON["PATH"]}'
#awk '{print $NF}' test.txt
  1. 用户自定义变量

gawk允许用户自定义自己的变量以便在程序代码中使用,变量名命名规则与大多数编程语言相同,只能使用字母、数字和下划线,且不能以数字开头。gawk变量名称区分字符大小写。

  1. 在脚本中赋值变量
    在gawk中给变量赋值使用赋值语句进行,例如:
    [root@www ~]# awk 'BEGIN{var="variable testing";print var}'
    #给变量赋值,并输出变量的值,下边是显示效果
    variable testing
  2. 在命令行中使用赋值变量
    gawk命令也可以在“脚本”外为变量赋值,并在脚本中进行引用。
    例如,上述的例子还可以改写为:
    [root@www ~]# awk -v var="variable testing" 'BEGIN{print var}'

    与上述的例子一样,显示效果如下

    variable testing

三、printf

printf命令的使用格式:
printf format, item1, item2, ...

要点:

  1. 其与print命令的最大不同是,printf需要指定格式;
  2. format用于指定后面的每个item的输出格式;
  3. printf语句不会自动打印换行,需要显式使用\n换行。
format格式的指示符都以%开头,后跟一个字符;如下:
%c: 显示字符的ASCII码;
%d, %i:十进制整数;
%e, %E:科学计数法显示数值;
%f: 显示浮点数;
%g, %G: 以科学计数法的格式或浮点数的格式显示数值;
%s: 显示字符串;
%u: 无符号整数;
%%: 显示%自身;

修饰符:
N: 显示宽度;
-: 左对齐;
+:显示数值符号;

例子:

  • awk -F: '{printf "%-15s%i\n",$1,$3}' /etc/passwd
    #使用printf显示该文件中的第一列和第三列,要求第一列左对齐且占用15个字符宽度,第二列显示十进制整数。

四、输出重定向

使用格式:
print items > output-file
print items >> output-file
print items | command
特殊文件描述符:
/dev/stdin:标准输入
/dev/sdtout: 标准输出
/dev/stderr: 错误输出
/dev/fd/N: 某特定文件描述符,
#如/dev/stdin就相当于/dev/fd/0;

例子:

  • awk -F: '{printf "%-15s %i\n",$1,$3 > "/dev/stderr" }' /etc/passwd
    #显示效果与上述例子一样,只不过这里是重定向到错误输出,然后显示

六、awk的操作符:

算术操作符:
-x:负值
+x:转换为数值;
x^y:x的y次方
x**y: x的y次方
x*y:乘法
x/y:除法
x+y:加法
x-y:减法
x%y:取余
  • 字符串操作符:
    只有一个,而且不用写出来,用于实现字符串连接;
    [root@www ~]# awk 'BEGIN{print "A" "B"}'
    #连接A和B两个字符,使其成为一个字符串,显示效果如下所示:
    AB

  • 赋值操作符:

    =
    +=
    -=
    *=
    /=
    %=
    ^=
    **=
    ++
    --
    
  • 需要注意的是,如果某模式为=号,此时使用/=/可能会有语法错误,应以/[=]/替代;

  • 布尔值
    awk中,任何非0值或非空字符串都为真,反之就为假;

  • 比较操作符:

x < y     True if x is less than y.
x <= y true if x is less than or equal to y.> y     True if x is greater than y.
x >= y     True if x is greater than or equal to y.
x == y     True if x is equal to y.
x != y     True if x is not equal to y.
x ~ y     True if the string x matches the regexp denoted by y. 
# 如果字符串x被y表示的正则表达式匹配,则为真;
x !~ y     True if the string x does not match the regexp denoted by y. 
# 如果字符串x不被y表示的正则表达式匹配,则为真;
subscript in array       True if the array array has an element with the subscript subscript.
  • [root@www ~]# awk -F: '$1 ~ /^root/{print $1,$3,$4,$NF}' /etc/passwd
    #显示该文件中以root开头的行的第一列、第三列、第四列和最后一列,显示效果如下所示:
    root 0 0 /bin/bash

  • [root@www ~]# awk -F: '$3>=400{printf "%-15s%-10i%s\n",$1,$3,$NF}' /etc/passwd
    #显示UID大于400的行的第一列、第三列和最后一列

  • 表达式间的逻辑关系符:

&&
||
  • 条件表达式:
    selector?if-true-exp:if-false-exp

  • 函数调用:
    function_name (para1,para2)

awk的模式:

awk 'program' input-file1 input-file2 ...
其中的program为:
pattern { action }
pattern { action }
...
  • 常见的pattern类型:
  1. Regexp: 正则表达式,
    格式为/regular expression/
    [root@www ~]# awk -F: '/bash/{print $0}' /etc/passwd
    #在/etc/passwd中查找有bash的行,并显示
root:x:0:0:root:/root:/bin/bash
nginx:x:496:493::/home/nginx:/bin/bash
mysql:x:495:492::/home/mysql:/bin/bash
wpuser:x:494:491::/home/wpuser:/bin/bash
  1. expresssion: 表达式,
    其值非0或为非空字符时满足条件,如:
    $1 ~ /foo/ 或 $1 == "lq2419"
    用运算符~(匹配)和~!(不匹配)。
    awk -F: '$3>=500{printf "%-15s%s\n",$1,$3}' /etc/passwd
    awk -F: '$1 ~ /root/{print $1,$3}' /etc/passwd
    等价于:awk -F: '$1=="root"{print $1,$3}' /etc/passwd

  2. Ranges: 指定的匹配范围,
    格式为pat1,pat2

  3. BEGIN/END:特殊模式,
    仅在awk命令执行前运行一次或结束前运行一次
    [root@www ~]# awk -F: 'BEGIN{print "Username UID"}$3>=400{printf "%-15s%s\n",$1,$3}' /etc/passwd
    #上式表示对于该文件,在开始比较前先输出username和UID作为题头,
    然后,输出UID大于400的行的第一列和第三列

Username       UID
rtkit          499
pulse          498
saslauth       497
nfsnobody      65534
nginx          496
mysql          495
wpuser         494

现在我想输出/etc/passwd文件中的第一列,那么,有多少种方法可以实现呢?

有三中方式均可以实现,命令及显示效果如下:

  • [root@www ~]# awk -v FS=: '{print $1}' /etc/passwd
    #使用-v选项和FS指定分隔符,然后显示
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
uucp
  • [root@www ~]# awk -F: '{print $1}' /etc/passwd
    #直接使用-F选项指定分隔符,然后显示
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
uucp
  • [root@www ~]# awk 'BEGIN{FS=":"}{print $1}' /etc/passwd
    #使用BEGIN,在运行前指定分隔符,然后显示
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
uucp
  1. Empty(空模式):匹配任意输入行;
  • 常见的Action
  1. Expressions:
  2. Control statements
  3. Compound statements
  4. Input statements
  5. Output statements
  • /正则表达式/:使用通配符的扩展集。
  • 关系表达式:
    可以用下面运算符表中的关系运算符进行操作,可以是字符串或数字的比较,如$2>$1选择第二个字段比第一个字段长的行。
  • 模式匹配表达式:
    模式,模式:指定一个行的范围。该语法不能包括BEGIN和END模式。
    BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
    END:让用户在最后一条输入记录被读取之后发生的动作。

八 控制语句:

  1. if-else
    语法:if (condition) {then-body} else {[ else-body ]}

例子:

  • [root@www ~]# awk -F: '{if ($1=="root") print $1, "Admin"; else print $1, "Common User"}' /etc/passwd
    #判断如果第一个字段是root,则显示是admin,否则显示是common user,显示结果如下所示:
root Admin
bin Common User
daemon Common User
adm Common User
lp Common User
sync Common User
shutdown Common User
halt Common User
  • [root@www ~]# awk -F: '{if ($1=="root") printf "%-15s%s\n", $1,"Admin"; else printf "%-15s%s\n", $1, "Common User"}' /etc/passwd
    #显示效果同上,只不过这里使用printf,显示定义显示的格式
root             Admin
bin              Common User
daemon           Common User
adm              Common User
lp               Common User
sync             Common User
shutdown         Common User
halt             Common User
mail             Common User
uucp             Common User
  • [root@www ~]# awk -F: -v sum=0 '{if ($3>=400) sum++}{if ($3>=400) printf "%-15s%i\n",$1,$3}END{print "Sum = "sum;}' /etc/passwd
    #定义变量sum=0,然后判断如果UID大于400,让sum自加,并且如果UID大于等于400的显示其用户名和UID,结束前输出sum的值
rtkit             499
pulse             498
saslauth          497
nfsnobody         65534
nginx             496
mysql             495
wpuser            494
Sum = 7
  1. while
    语法: while (condition){statement1; statment2; ...}
    如:
  • [root@www ~]# awk -F: -v i=1 '{while (i<=7) {print $i;i++}}' /etc/passwd
    #使用-v选项显示定义变量i=1,当i小于等于7时循环结束,然后,输出第一个字段到第七个字段,当i为7时,正好输出的是第一行的7个字段的值
root
x
0
0
root
/root
/bin/bash
  • [root@www ~]# awk -F: '{i=1;while (i<=3) {print $i;i++}}' /etc/passwd
    #定义变量i,先判断,当i小于等于3时,输出第一到第三个字段的值,所处理的数据时该文件中的每一行数据,而不仅仅是第一行。这里需要注意与上述命令的区别
root
x
0
bin
x
1
daemon
x
2
adm
x
3
lp
x
4
  1. do-while
    语法: do {statement1, statement2, ...} while (condition)
  • [root@www ~]# awk -F: '{i=1;do {print $i;i++}while(i<=3)}' /etc/passwd
    #意义同上,显示效果虽与上述相同,但与while不同的是,do-while是先执行一趟,然后再判断是否满足条件,也就是说不管条件是否满足,都会先执行一趟;而while中如果条件不满足,则一趟也不会执行
  1. for
    语法: for ( variable assignment; condition; iteration process) { statement1, statement2, ...}
  • [root@www ~]# awk -F: '{for(i=1;i<=3;i++) print $i}' /etc/passwd
    #使用for循环,输出各行的前三个字段
  • for循环还可以用来遍历数组元素:
    语法: for (i in array) {statement1, statement2, ...}
  • [root@www ~]# awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd
    #匹配最后一个字段不空的行,把最后一段当做数组下标,输出各下标的值,及各下标对应的个数,各下标的个数保存在数组中
         /bin/sync:1
         /bin/bash:4
     /sbin/nologin:29
        /sbin/halt:1
    /sbin/shutdown:1
  1. case
    语法:switch (expression) { case VALUE or /REGEXP/: statement1, statement2,... default: statement1, ...}

  2. break 和 continue
    常用于循环或case语句中

  3. next
    提前结束对本行文本的处理,并接着处理下一行;例如,下面的命令将显示其ID号为奇数的用户:

  • [root@www ~]# awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd
    #UID号对2取余,如果为0,直接处理下一行,否则,输出用户名和UID号,显示效果如下所示:
bin 1
adm 3
sync 5
halt 7
operator 11
gopher 13
nobody 99
dbus 81
usbmuxd 113
vcsa 69
rtkit 499
saslauth 497
postfix 89
abrt 173
rpcuser 29
mysql 495

九 awk中使用数组

  1. 数组
    array[index-expression]
    index-expression可以使用任意字符串;
    需要注意的是,如果某数据组元素事先不存在,那么在引用其时,awk会自动创建此元素并初始化为空串;
    因此,要判断某数据组中是否存在某元素,需要使用index in array的方式。
    要遍历数组中的每一个元素,需要使用如下的特殊结构:
    for (var in array) { statement1, ... }
    其中,var用于引用数组下标,而不是元素值;

例子:
1|netstat -ant | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
--|--
2|每出现一被/^tcp/模式匹配到的行,数组S[$NF]就加1,NF为当前匹配到的行的最后一个字段,此处用其值做为数组S的元素索引

  • [root@www ~]# awk '{IP[$1]++}END{for (A in IP) printf "%-20s%s\n",A,IP[A]}' /var/log/nginx/access.log
    #用法与上一个例子相同,用于统计某日志文件中IP地的访问量
172.16.32.30         3
172.16.32.50         31596
172.16.32.52         408
192.168.0.239        1886
172.16.32.0          1010
  • [root@www ~]# awk 'BEGIN{A["m"]="hello";A["n"]="world";print A["m"],A["n"]}'
    #开始前对数组赋值,然后输出数组的值

  • [root@www ~]# awk 'BEGIN{A["m"]="hello";A["n"]="world";for (B in A) print A[B]}'
    #在开始前对数组赋值,然后使用for循环,把B当做下标,依次输出数组中的值

  1. 删除数组变量
    从关系数组中删除数组索引需要使用delete命令。使用格式为:
    delete array[index]

十、awk的内置函数

  • split(string, array [, fieldsep [, seps ] ])
    功能:将string表示的字符串以fieldsep为分隔符进行分隔,并将分隔后的结果保存至array为名的数组中;数组下标为从0开始的序列;
  • netstat -ant | awk '/:22\>/{split($5,clients,":");IP[clients[1]]++}END{for(i in IP){print IP[i],i}}' | sort -rn | head -50
length([string])
功能:返回string字符串中字符的个数;
substr(string, start [, length])
功能:取string字符串中的子串,从start开始,取length个;start从1开始计数;
system(command)
功能:执行系统command并将结果返回至awk命令
systime()
功能:取系统当前时间
tolower(s)
功能:将s中的所有字母转为小写
toupper(s)
功能:将s中的所有字母转为大写

十一、用户自定义函数

自定义函数使用function关键字。格式如下:

function F_NAME([variable]){
    statements
}

函数还可以使用return语句返回值,格式为“return value”。

在最后,介绍个面试中常被问到的关于awk的一个问题,顺便给出几个好玩的awk命令。有时候面试的时候,会被问到,使用awk编写脚本打印出乘法口诀,牛人当然觉得这小菜一碟,但是,像我们这些菜鸟,可就该抓耳挠腮了,本人经过上网搜索,加上自己尝试,写出了该脚本。先附上脚本及打印效果,以供参考。

  1. 乘法口诀
    vim cfkj.sh #编辑该文件,输入如下内容:
#!/bin/awk -f
BEGIN{
    for (i=1;i<=9;i++){ for (m="1;m<=i;m++){" printf m"*"i"="m*i" " } print < pre>
  1. 打印图案
    接触过C语言的都知道,我们在刚开始学C语言的时候,有时候为了让你能够学下去,特别是在谭浩强版的C程序设计丛书中,会有很多课后习题,让你用C语言实现打印一些图案等,甚至是一些有意思的像编程实现使各行、各列以及对角线之和都相等之类的图标等等。那如果现在让你用awk来实现,打印一个图案,又该如何实现呢?比方说,使用awk打印如下图案。
         *
        ***
      *****
     *******

对于这个我们又该如何实现呢?其实,可使用如下命令实现打印此图案。
awk 'BEGIN {for(i=1;i<=4;i++) {for(j=1;j<=10-i;j++) {printf " ";}for(j=1;j<=(2*i-1);j++) {printf "*";}printf "\n";}}'

3、使用数字打印中心对称图形
这个就有点难度了。本人也没有想出来,这个是一个朋友给我的,觉得好玩,就拿出来跟大家分享一下。这里直接给出命令及显示效果了,就不吊各位胃口了。
echo 15|awk '{x=8;for(i=1;i<$0;i++){for(j=1;j<=3*($0-i)-(x>0?x:0);j++)printf" ";for(k=i;k>=1;k--)printf"%d ",k;for(l=2;l<=i;l++)printf"%d ",l;printf"\n";x--};\ for(i=1;i<=$0;i++){for(j=1;j<=(i<=$0-10+1?3*(i-1):3*(i-1)+2-(i-$0%10-10*int(($0-10)/10)));j++)printf" ";for(k=$0-i+1;k>=1;k--)printf"%d ",k;for(l=2;l<=$0-i+1;l++)printf"%d ",l;printf"\n"}}'