彻底理解 Linux 的搜索工具: grep 和 awk

grep 官方手册
awk 官方手册awk 学习资料

1. grep

grep 用于打印匹配指定模式的行。

1.1 介绍

grep 命令从输入文件中查找匹配到给定模式列表的行。发现匹配到的行后,默认情况下会复制这一行到标准输出流,也可以通过选项产生任何其他类型的输出。

grep 匹配文本时,对输入行长度没有限制(但受内存限制),并且可以匹配一行中的任意字符。如果输入文件的最后一个字节不是换行符,那么 grep 会提供一个。由于换行符也是模式列表的分隔符,因此无法在文本中匹配换行符。

1.2 调用 grep

grep [options] pattern input_file_names

选项可以有零个或多个。如果通过选项 -e pattern-f file 指定了模式(pattern),命令中的模式是不可见的。input_file_names 也可以有零个或多个。

1.2.1 命令行选项

grep 带有一组丰富的选项,分别来自 POSIX 或 GNU 扩展。长选项都是 GNU 扩展的,即使对于 POSIX 规范中的选项也是如此。

通用程序信息
  • --help:打印选项帮助信息并退出。
  • -V--version:将 grep 的版本号打印到标准输出流。
匹配控制
  • -e pattern--regexp=pattern:从命令行指定模式。如果此选项多次使用或与 -f (--file) 选项结合使用,搜索所有给定的模式。 (-e 由 POSIX 指定。)
  • -f file--file=file:从文件中获取模式,每行一个。如果此选项多次使用或与 -e--regexp)选项结合使用,搜索所有给定的模式。 空文件包含零个模式,因此不匹配任何内容。(-f 由 POSIX 指定)。
  • -i-y--ignore-case:忽略大小写,以便匹配只是大小写不同的字符。
  • -v--invert-match:对匹配到的结果取反,选择不匹配的行。(-v 由 POSIX 指定。)
  • -w--word-regexp:只选择那些包含整个单词的匹配的行。测试是匹配子字符串必须位于行首,或者以非单词组成字符开头。同样,它必须位于行尾,或者后面跟着一个非单词组成字符。单词组成字符是字母,数字和下划线。如果还指定了 -x,则此选项无效。
  • -x--line-regexp:只选择与整个行完全匹配的那些行。对于一个正则表达式模式,这就像括号中的模式,然后用’^’和’$’围绕它。 (-x 由 POSIX 指定。)
通用输出控制
  • -c``--count:抑制正常输出,相反,为每个输入文件打印一个匹配行数。使用 -v--invert-match)选项可以计算不匹配的行。
  • --color[=WHEN]``--colour[=WHEN]:将匹配(非空)字符串,匹配行,上下文行,文件名,行号,字节偏移量和分隔符(用于上下文行的字段和组)排列用转义序列包围后在终端上以彩色显示。对于粗体红色,颜色由环境变量 GREP_COLORS 定义,默认为’ms = 01; 31:mc = 01; 31:sl =:cx =:fn = 35:ln = 32:bn = 32:se = 36’ 粗体红色的匹配文本,品红色的文件名称,绿线的数字,绿色的字节偏移量,青色的分隔符和默认终端颜色。WHEN 可以是‘never’、‘always’ 或 ‘auto’。
  • -L``--files-without-match:抑制正常输出,相反,打印每个输入文件的名称,其中通常不会输出任何输出文件(from which no output would normally have been printed)。每个文件的扫描在第一次匹配时停止。
  • -l``--files-with-matches:抑制正常输出,相反,打印每个输出文件的名称,通常从哪个输出文件打印出来(from which output would normally have been printed)。 每个文件的扫描在第一场比赛时停止。
  • -m num``--max-count=num:匹配到指定次数后停止读取文件。如果输入是来自常规文件的标准输入,并且输出了 num 个匹配行,则 grep 可确保在退出前标准输入位于最后一个匹配行之后,而不管后面是否存在上下文行。这使调用进程能够恢复搜索。例如,以下 shell 脚本使用这个选项:
while grep -m 1 PATTERN
do
  echo xxxx
done < FILE

但以下可能无法正常工作,因为管道 pipe 不是常规文件:

# This probably will not work.
cat FILE |
while grep -m 1 PATTERN
do
  echo xxxx
done

当 grep 在 num 个匹配行后停止时,它输出任何后面的上下文行。由于上下文不包含匹配行,当遇到另一个匹配行时,grep 将停止。当还使用 -c--count 选项时,grep 不会输出大于 num 的计数。当使用 -v--invert-match 选项时,grep 在输出 num 不匹配行后停止。
- -o``--only-matching:仅打印匹配行的匹配(非空)部分,每个这样的部分位于单独的输出行上。输出行使用与输入相同的分隔符,如果还使用了 -z--null-data),则分隔符为空字节(请参阅其他选项)。
- -q``--quiet``--silent:不向标准输出写任何东西。如果发现任何匹配,即使检测到错误,也立即以零状态退出。另请参阅 -s--no-messages 选项。
- -s``--no-messages:抑制关于不存在或不可读文件的错误消息。

输出行的前缀控制(Output Line Prefix Control):

当要输出多个前缀字段时,顺序始终是文件名,行号和字节偏移量,无法指定顺序。
- -b``--byte-offset:在每行输出之前打印在输入文件中基于 0 的字节偏移量。如果指定了 -o--only-matching),则打印匹配部分本身的偏移量。当 grep 在 MS-DOS 或 MS-Windows 上运行时,打印的字节偏移取决于是否使用 -u--unix-byte-offsetsets)选项。
- -H``--with-filename:每次匹配时打印文件名。同时搜索多个文件时默认开启。
- -h``--no-filename:抑制输出前缀中的文件名。只搜索一个文件时默认开启。
- --label=LABEL:显示实际来自标准输入的输入作为来自文件 LABEL 的输入。这在使用 zgrep 这样的工具时特别有用,例如:

gzip -cd foo.gz | grep --label=foo -H something
  • -n``--line-number:在每个输出行的前面添加输入文件中基于 1 的行号。
  • -T``--initial-tab:确保实际行内容的第一个字符位于制表位 tab 上,以便对齐。对于将输出前缀(文件名、行号、字节偏移量)添加到实际内容的选项非常有用:-H-n-b。这也可能会预留空格来输出行号和字节偏移量,以使单个文件中的行全部从同一列开始。
  • -u``--unix-byte-offsets:只在 Windows 下有效。报告 Unix 风格的字节偏移量。该选项会导致 grep 报告字节偏移量,就像该文件是 Unix 样式的文本文件一样,即字节偏移量会忽略被删除的回车符。这将产生与在 Unix 机器上运行 grep 相同的结果。除非也使用 -b 选项,否则此选项不起作用。
  • -Z``--null:输出一个零字节(ASCII NUL 字符),而不是通常跟在文件名后面的字符。例如,’grep -lZ’在每个文件名后面输出一个零字节,而不是通常的换行符。该选项使输出清晰,即使在包含不正常字符(如换行符)的文件名中也是如此。这个选项可以和’find -print0’,’perl-0’,’sort -z’和’xargs -0’这样的命令一起使用来处理任意文件名,甚至那些包含换行符的文件名。
上下文行控制(Context Line Control):

上下文行是匹配行附近的非匹配行。只有在使用以下选项之一时才会输出它们。无论如何设置这些选项,grep 都不会多次输出任何给定的行。如果指定了 -o--only-matching)选项,则这些选项无效,并在使用时发出警告。
- -A num``--after-context=num:打印匹配行后的上下文行中的 num 行。
- -B num``--before-context=num:打印匹配行前的上下文行中的 num 行。
- -C num``-num``--context=num:打印匹配行前和匹配行后的上下文行中的 num 行。
- --group-separator=string:当使用 -A-B-C 选项时,在每组行之间打印字符串而不是 --
- --no-group-separator:当使用 -A-B-C 选项时,每组行之间不再打印字符串。

以下是关于 grep 如何选择分隔符以在前缀字段和行内容之间打印的一些要点:

  • 匹配行通常使用’:’作为前缀字段和实际行内容之间的分隔符。
  • 上下文(即非匹配行)行使用’-‘代替。
  • 当未指定上下文时,匹配行将被一个接一个地输出。
  • 指定上下文时,输入中相邻的行形成一个组,并且一个接一个地输出,而默认情况下,分隔符会出现在非相邻组之间。
  • 默认分隔符是’-‘行,它的存在和外观可以通过上述选项进行更改。
  • 每个组可能包含几个匹配行,当它们彼此足够靠近时,两个相邻的组连接并可合并成一个连续的组。
文件及目录选择(File and Directory Selection):
  • -a``--text:像处理文本一样处理二进制文件,相当于’–binary-files=text’选项。
  • --binary-files=type:如果文件的数据或元数据指示文件包含二进制数据,则假定该文件是 type 类型的。非文本字节表示二进制数据,这些输出字节是针对当前语言环境进行错误编码的输出字节(请参阅环境变量 Environment Variables),或者当没有给出 -z--null-data)选项时输入空字符(请参阅其他选项 Other Options)。
    默认情况下,type 是’binary’,并且 grep 发现输入的二进制数据不存在时会抑制输出,并抑制包含不正确编码数据的输出行。当某些输出被抑制时,grep 会跟随带有单行消息的任何输出,表示二进制文件匹配。
    如果 type 是’without-match’,当 grep 发现输入的二进制数据不存在时,它假定文件的其余部分不匹配。相当于 -I 选项。
    如果 type 是’text’,grep 会像处理文本一样处理二进制数据。相当于 -a 选项。
    当 type 是’binary’时,即使没有 -z--null-data)选项,grep 也可能将非文本字节视为行终止符。这意味着选择“binary”与“text”可以影响模式是否与文件匹配。例如,当 type 为’binary’时,模式’q$’可能会匹配紧接着为空字节的’q’,尽管当 type 为’text’时这不匹配。相反,当 type 为’binary’时,模式’.’(句点)可能不匹配空字节。
    警告:-a--binary-files=text)选项可能会输出二进制垃圾,如果输出是终端,并且终端驱动程序将其中的一部分解释为命令,则可能会产生副作用。另一方面,当阅读编码未知的文本文件时,在环境中使用 -a 或设置 LC_ALL='C' 可能会有所帮助,以便找到更多匹配,即使匹配对于直接不安全显示。

  • -D action``--devices=action:如果输入文件是设备,FIFO 或套接字,则使用 action 来处理它。如果 action 是“read”,则所有设备都被读取,就像它们是普通文件一样。如果 action 是“skip”,则设备,FIFO 和套接字将被默认跳过。默认情况下,如果设备在命令行上或者使用了 -R--deference-recursive)选项,则读取设备,如果递归地遇到设备并使用 -r--recursive)选项,则会跳过设备。该选项对通过标准输入读取的文件没有影响。

  • -d action``--directories=action:如果输入文件是目录,则使用 action 来处理。默认情况下,action 是’read’,这意味着对目录的读取跟普通文件一样(一些操作系统和文件系统不允许这样做,并且会导致 grep 打印每个目录的错误消息或者静默跳过它们)。如果 action 是“skip”,则目录会被静默跳过。如果 action 是’recurse’,则 grep 会以递归方式读取每个目录下的所有文件,遵循命令行符号链接并跳过其他符号链接,相当于 -r 选项。
  • --exclude=glob:跳过任何名称后缀匹配到模式 glob 的命令行文件,使用通配符匹配。名称后缀可以是全名,或在 / 之后和非 / 之前的部分。当递归搜索时,跳过基本名称与 glob 匹配的任何子文件,基本名称是最后一个 / 之后的部分。模式可以使用 *?[...] 作为通配符,而 \ 可以直接引用通配符或反斜线字符。
  • --exclude-from=file:跳过名称与 file 的模式相匹配的文件(使用通配符匹配,如 --exclude 下所述)。
  • --exclude-dir=glob:跳过任何名称后缀匹配模式 glob 的命令行目录。当递归搜索时,跳过其基名与 glob 匹配的任何子目录。忽略 glob 中的任何冗余结尾斜杠。
  • -I:处理二进制文件,就好像它不包含匹配数据一样; 这相当于 --binary-files=without-match 选项。
  • --include=glob:只搜索名称与 glob 匹配的文件,使用 --exclude 下所述的通配符匹配。
  • -r``--recursive:对于每个目录操作数,递归读取并处理该目录中的所有文件。遵循命令行上的符号链接,但跳过递归遇到的符号链接。注意,如果没有给出文件操作数,grep 将搜索工作目录。这与 --directories=recurse 选项相同。
  • -R``--dereference-recursive:对于每个目录操作数,按照所有符号链接递归地读取并处理该目录中的所有文件。
Other Options:
  • --line-buffered:在输出上使用行缓冲。这可能会导致性能损失。
  • -U``--binary:将文件视为二进制文件。默认情况下,在 Windows 下,grep 根据 --binary-files 选项所描述的猜测文件是文本文件还是二进制文件。如果 grep 认为该文件是一个文本文件,它将从原始文件内容中删除回车符(以使表达式的 ^$ 正常工作)。指定 -U 推翻了这种猜测,导致所有文件被读取并逐字传递给匹配机制。如果文件是每行末尾有 CR/LF 对的文本文件,则会导致一些正则表达式失败。此选项对 Windows 以外的平台没有影响。
  • -z``--null-data:将输入和输出数据视为行序列,每个数据以零字节(ASCII 的 NUL 字符)而不是换行符结尾。像 -Z--null 选项一样,该选项可以用于像 sort -z 这样的命令来处理任意文件名。

1.2.2 环境变量

grep 的行为受环境变量的影响。

GREP_OPTIONS:废弃。指定要放置在任何显式选项前面的默认选项。由于这会在编写可移植脚本时造成问题,所以在未来的 grep 发行版中将删除此功能。请改用别名或脚本。例如,如果 grep 位于目录 /usr/bin 中,则可以将 $HOME/bin 预先添加到 PATH 中,并创建包含以下内容的可执行脚本 $HOME/bin/grep

#! /bin/sh
export PATH=/usr/bin
exec grep --color=auto --devices=skip "$@"

GREP_COLOR:指定用于突出显示匹配(非空)文本的颜色。不赞成但仍支持使用 GREP_COLORS。GREP_COLORS 的’mt’,’ms’和’mc’功能优先于它。它只能指定用于突出显示任何匹配行中匹配的非空文本的颜色(当 -v 命令行选项被省略时的选定行,或者指定 -v 时的上下文行)。 默认值为’01;31’,表示终端默认背景上的粗体红色前景文字。

更多环境变量,参考 这里

1.2.3 退出状态

正常情况下,如果选择了一行,退出状态为 0,如果未选择行,则退出状态为 2,如果发生错误,退出状态为 2。但是,如果使用 -q--quiet--silent 选项并选择一行,即使发生错误,退出状态也为 0。其他 grep 实现可能会以大于 2 的状态退出。

1.3 正则表达式

参考 这里

1.4 使用

配合管道

基于上一个命令的输出信息进行 grep 匹配,而不是基于文件:

# echo "hello world" | grep "hello"
hello world

在文件中查找匹配模式的行

假设文件内容为:

123
hello
hello world
hello
hello world
666
fine
grep "hello world" /home/user/1.txt     # 匹配到一行
grep [0-9] /home/user/1.txt              # 匹配到两行

在多个文件中查找

grep "match_pattern" file_1 file_2 file_3 ...

输出包含匹配字符串的行数 -n,从 1 开始:

# grep -n "hello world" 1.txt 
3:hello world
5:hello world

统计包含匹配字符串的总行数:

# grep -c "hello world" 1.txt 
2

在多个文件中搜索并输出匹配到的文件:

grep -l "text" file1 file2 file3...

在多级目录中对文本进行递归搜索:

grep "text" . -r -n

忽略匹配样式中的字符大小写:

echo "hello world" | grep -i "HELLO"
hello

静默输出:

不会输出任何信息,如果命令运行成功返回 0,失败则返回非 0 值。一般用于条件测试。

grep -q "test" 1.txt

2. awk

awk 是一种程序设计语言,语言风格类似 C 语言,设计目的是写那种一行搞定事情的脚本,常用于文本处理的脚本。包含常用的内置函数,支持用户函数和动态正则表达式,支持数组。

awk 是一种弱类型语言,不需要提前声明就可以使用变量,变量的类型转换也是隐含的,在不同的上下文中变量可能是不同类型。awk 的字符串连结操作不需要任何操作符,只要把需要连结的串并列写在一起即可。

2.1 语法及命令

1. awk 命令调用语法

awk [options] 'script' var=value file(s)
awk -v var=value [options] 'script' file(s)
awk [options] -f scriptfile var=value file(s)

2. awk 常用选项

  • -F fs:指定输入分隔符,fs 可以是字符串或正则表达式,如 -F:
  • -v var=value:自定义用户变量,将外部变量传递给 awk
  • -f scripfile:从脚本文件中读取 awk 命令

3. awk 脚本结构

awk 'BEGIN{ print "start" } { commands } END{ print "end" }' file

awk 脚本通常由:BEGIN 语句块、能够使用模式匹配的通用语句块、END 语句块 3 部分组成,这三个部分都是可选的。脚本通常是被单引号或双引号中,所有指令用分号分隔。示例:

awk 'BEGIN{ i=0 } { i++ } END{ print i }' filename
awk "BEGIN{ i=0 } { i++ } END{ print i }" filename

4. print 及 printf 指令

awk 中的 print 指令不带参数时打印当前行,参数可以是逗号分隔的列表。awk 的 print 中双引号被当作字符串连接符使用,例如:

# echo | awk '{a="a"; b="b"; print a,b}'
a b
# echo | awk '{a="a"; b="b"; print a","b}'
a,b

printf 指令跟 C 语言使用一样的格式化字符,以”%”开始,后跟一个或几个规定字符,用来确定输出内容格式。

格式描述
%d十进制有符号整数
%u十进制无符号整数
%f浮点数
%s字符串
%c单个字符
%p指针的值
%e指数形式的浮点数
%x%X 无符号以十六进制表示的整数
%o无符号以八进制表示的整数
%g自动选择合适的表示法
[root@VM_157_18_centos ~]# awk 'BEGIN{s="hello";f=124.113;i=246;c="hello"; printf("%s, %u, %.2f, %.2g, %X, %o, %c", s, f, f, f, i, i, c);}'
hello, 124, 124.11, 1.2e+02, F6, 366, h

2.2 awk 变量

1. 内置变量

  • $n:当前记录的第 n 个字段,空白符分隔一行为多个字段。
  • $0:执行过程中当前行的文本内容,完整的行。
  • NR:Number of Record,从 1 开始计数。awk 开始执行后,按照记录分隔符读取数据的次数(默认的记录分隔符为换行符,因此就是读取行数)。多个输入文件时,处理完第一个文件后,NR 继续累加而不会清空。
  • FNR:File Number of Record,每当处理一个新文件的时候,FNR 就从 1 开始计数。
  • NF:Number of Field,当前记录中字段的个数。
  • ARGC:命令行参数的个数。
  • ARGV:包含命令行参数的数组。
  • ARGIND:命令行中当前文件的位置(从 0 开始算)。
  • CONVFMT:数字转换格式(默认值为 %.6g)。
  • ENVIRON:环境变量关联数组。
  • ERRNO:最后一个系统错误的描述。
  • FIELDWIDTHS:字段宽度列表(用空格键分隔)。
  • FILENAME:当前输入文件的名。
  • FS:Field Separator,字段分隔符(默认是任何空白字符)。
  • IGNORECASE:如果为真,则进行忽略大小写的匹配。
  • OFMT:数字的输出格式(默认值是 %.6g)。
  • OFS:输出字段分隔符(默认值是一个空格)。
  • ORS:输出记录分隔符(默认值是一个换行符)。
  • RS:Record Separator,记录分隔符(默认是一个换行符)。
  • RSTART:由 match 函数所匹配的字符串的第一个位置。
  • RLENGTH 由 match 函数所匹配的字符串的长度。
  • SUBSEP 数组下标分隔符(默认值是 34)。

变量示例:

  • NR 及 NF 的用法:
# echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | awk '{print "Line No:"NR", No of fields:"NF, "$0="$0, "$1="$1, "$2="$2, "$3="$3}' 
Line No:1, No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3
Line No:2, No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5
Line No:3, No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7
  • 使用 print $NF 可以打印一行中的最后一个字段,使用 $(NF-1) 打印倒数第二个字段,以此类推:
[root@VM_157_18_centos test]# echo -e "abc def\n123 456\nabc ggg" | awk '{print $(NF-1)}'
abc
123
abc
[root@VM_157_18_centos test]# echo -e "abc def\n123 456\nabc ggg" | awk '{print $(NF)}'
def
456
ggg
  • 打印完整行、行的第一个和最后一个字段:
[root@VM_157_18_centos test]# echo -e "abc def\n123 456\nabc ggg" | awk '{print $0, $1, $(NF)}'
abc def abc def
123 456 123 456
abc ggg abc ggg
  • 统计行数(使用 END 语句块):
[root@VM_157_18_centos test]# echo -e "abc def\n123 456\nabc ggg" | awk 'END{print NR}'
3

2. 传入外部变量到 awk

两种方式向 awk 传入变量:通过 -v 选项每次指定一个变量,或在 awk 的命令行参数中的语句块之后传入空格分隔的变量。

[root@VM_157_18_centos test]# echo | awk -v v1=100 -v v2=200 '{print v1, v2}'
100 200
[root@VM_157_18_centos test]# echo | awk '{print v1, v2}' v1=100 v2=200
100 200

2.3 awk 模式和操作

awk 脚本是由模式和操作组成。

2.3.1 awk 可以使用的模式有四种:

1. 正则表达式:/regular expression/

语法:

awk '/reg/{commands} {commands}'

工作流程:

示例:

# echo -e "abc \n123\n666" | awk '/[0-9]/{a=$0}{printf  a} '
123666
# echo -e "abc \n123\n666" | awk '/[0-9]/{print $0} '
123
666
# echo -e "abc \n123\n666" | awk '/[0-9]/{print $1} '
123
666
2. 关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试。

可以判断行内的某个字段是否满足条件,满足则打印数据。支持的运算符有:==!=><>=<=

[root@VM_120_242_centos test]# echo -e 'abc ok\n1234\nxxx ok' | awk '$2=="ok"'
abc ok
xxx ok
3. 模式匹配表达式:用运算符 ~(匹配)和 ~!(不匹配)。

模式匹配时,可以将行内的某个字段使用正则表达式匹配。~ 表示开始模式匹配且打印匹配成功的行。

[root@VM_120_242_centos test]# echo -e 'abc ok\n1234\nxxx ok' | awk '$2 ~ /ok/'
abc ok
xxx ok
4. BEGIN 语句块、pattern 语句块、END 语句块

语法:

awk 'BEGIN{ commands } { commands } END{ commands }' file

工作流程:

  1. 执行 BEGIN{ commands } 语句块中的语句,BEGIN 关键字可以省略。
  2. 从文件或标准输入(stdin)读取一行后执行 { commands } 语句块,然后读下一行并再次执行 { commands } 语句块,直到最后一行。
  3. 当读至文件或输入流末尾时,执行 END{ commands } 语句块,END 关键字可以省略。

BEGIN 语句块用于只需执行一次的初始化等操作,比如变量初始化、打印输出表格的表头等。

END 语句块用于只需执行一次的清理操作,比如文件读取完毕后打印所有行的分析结果。

示例,其中 echo 输出的内容通过 -e 选项配合 \n 实现换行:

# echo -e "abd \n 1234" | awk 'BEGIN {print "Start"}  {print} END {print "END"}'
Start
abd 
 1234
END
# echo -e "abd \n 1234" | awk '{print "Start"}  {print} {print "END"}'
Start
abd 
 1234
END

2.3.2 操作

操作位于大括号内,由一个或多个命令、函数、表达式组成,用换行符分号分隔。模式匹配成功后,执行后面的操作。例如上面的 awk '/[0-9]/{a=$0}{printf a} ' 中的 {a=$0}{printf a} 就是操作。

2.4 输入输出

2.4.1 getline

从输入中每次获取一行输入。可以通过 while 循环,读完所有行。

1. expression | getline [var]

将管道前命令输出的结果作为 getline 的输入,每次读取一行。其中管道前的命令需要用双引号,例如 "cat 1.txt | getline var"。如果后面跟有 var,则将读取的内容保存到 var 变量中,否则会重新设置 $0 和 NF。

示例:

[root@VM_157_18_centos test]# cat 1.txt 
123
hello
hello world
hello
hello world
666
fine
[root@VM_157_18_centos test]# awk 'BEGIN{while("cat 1.txt" | getline var) print var}'
123
hello
hello world
hello
hello world
666
fine
[root@VM_157_18_centos test]# awk 'BEGIN{while("cat 1.txt" | getline ) print $0, NF}'
123 1
hello 1
hello world 2
hello 1
hello world 2
666 1
fine 1
2. getline [var]

从处理的文件中读取输入。同样,如果没有 var,则会设置 $0,并且这时候会更新 NF, NR 和 FNR:

[root@VM_157_18_centos test]# awk '{while(getline var) print var}' 1.txt 
hello
hello world
hello
hello world
666
fine

2.4.2 close

close 函数可以用于关闭已经打开的文件或者管道,很少会用到。

上面例子中 getline 函数的第一种形式用到管道,我们可以用 close 函数把这个管道关闭 close("cat statement.txt")

2.4.3 system

执行外部命令,例如:

[root@VM_157_18_centos test]# awk 'BEGIN{system("uname -a")}'
Linux VM_157_18_centos 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

2.5 字符串

awk 的字符串连结操作不需要任何操作符,只要把需要连结的串并列写在一起即可。

[root@VM_157_18_centos test]# echo | awk '{T=123;print T;T=T+"abc";print T}'
123
123
[root@VM_157_18_centos test]# echo | awk '{T=123;print T;T=T"abc";print T}'
123
123abc
[root@VM_157_18_centos ~]# awk 'BEGIN{a="aaa";b="666";print a"   "b}'
aaa   666

字符串函数

定义
函数解释
length [(String)]返回 String 参数指定的字符串的长度(字符形式)。如果未给出 String 参数,则返回整个记录 $0 的长度。
blength [(String)]返回 String 参数指定的字符串的长度(以字节为单位)。如果未给出 String 参数,则返回整个记录 $0 的长度。
substr( String, M, [ N ] )返回具有 N 参数指定的字符数量的子串。子串从 String 参数指定的字符串取得,其字符以 M 参数指定的位置开始。M 参数指定为将 String 参数中的第一个字符作为编号 1。如果未指定 N 参数,则子串到 String 的末尾。
index( String1, String2 )在由 String1 参数指定的字符串(其中有出现 String2 指定的参数)中,返回位置,从 1 开始编号。如果 String2 参数不在 String1 参数中出现,则返回 0(零)。
tolower( String )字符串中每个大写字符将更改为小写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。
toupper( String )字符串中每个小写字符将更改为大写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。
split( String, A, [Ere] )将 String 参数指定的参数分割为数组元素 A[1], A[2], …, A[n],并返回 n 变量的值。此分隔可以通过 Ere 参数指定的扩展正则表达式进行,或用当前字段分隔符(FS 特殊变量)来进行(如果没有给出 Ere 参数)。除非上下文指明特定的元素还应具有一个数字值,否则 A 数组中的元素用字符串值来创建。
match( String, Ere )在 String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回位置(字符形式),从 1 开始编号,或如果 Ere 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一)。
gsub( Ere, Repl, [ In ] )除了正则表达式所有具体值被替代这点,它和 sub 函数完全一样地执行。
sub( Ere, Repl, [ In ] )用 Repl 参数指定的字符串替换 In 参数指定的字符串中的由 Ere 参数指定的扩展正则表达式的第一个具体值。sub 函数返回替换的数量。出现在 Repl 参数指定的字符串中的 &(和符号)由 In 参数指定的与 Ere 参数的指定的扩展正则表达式匹配的字符串替换。如果未指定 In 参数,缺省值是整个记录($0 记录变量)。
示例
[root@VM_157_18_centos ~]# awk 'BEGIN{info="hello world from awk,你好世界!";print length(info);}'
21
[root@VM_157_18_centos ~]# awk 'BEGIN{info="hello world from awk!";print substr(info,4,10);}'
lo world f
[root@VM_157_18_centos ~]# awk 'BEGIN{info="hello world from awk!";print index(info,"awk");}'
18
[root@VM_157_18_centos ~]# awk 'BEGIN{info="hello 666 world from 777 awk!";print match(info, /[0-9]/);}'
7
[root@VM_157_18_centos ~]# awk 'BEGIN{info="hello 666 world from 777 awk!";gsub(/[0-9]+/,"!",info);print info}'
hello ! world from ! awk!

2.6 数组

处理文本经常使用数组。数组索引(下标)可以是数字和字符串在awk中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。数组元素用 0 或空字符串来初始化。

awk 中的数组下标从 1 开始,这与 C 的数组不一样。

awk 中的数组是默认是无序的关联数组。通过 for…in 循环得到是无序数组。对于数字下标的数组,如果需要得到有序数组,需要通过下标获得。例如:

[root@VM_157_18_centos test]# awk 'BEGIN{
 a["a"]=123;
 a[1]="abc";
 a["z"]=666;
 a["b"]=222;
 for (k in a) {
  print k, a[k];
 }
 len = length(a);
 for (i = 0; i < len; i++) {
  print i, a[i];
 }
}'
z 666
a 123
b 222
1 abc
0 
1 abc
2 
3

数组定义

数字做数组索引:

arr[1] = "a"
arr[2] = "123"

字符串做数组索引:

arr["a"] = "a"
arr["b"] = "123"

使用中 print arr[1] 会打印出 a;使用 print arr["b"] 会得到 123。

用 for 循环读数组的值

{ for(item in array) {print array[item]}; }       #输出的顺序是随机的
{ for(i=1;i<=len;i++) {print array[i]}; }         #Len是数组的长度

数组内置函数

数组长度 length

length 函数是 awk 内置函数,用于求数组长度。

[root@VM_157_18_centos test]# awk 'BEGIN{
 a[1]=123;
 a[2]="abc";
 print length(a);
}'
2
判断键值存在以及删除键值
[root@VM_157_18_centos ~]# awk 'BEGIN{
 a["a"]=123;
 a[1]="abc";
 a["z"]=666;
 a["b"]=222;
 if ("z" in a) {
    print "z in a"
 }
 if ("kkk" in a) {
    print "kkk in a"
 }
}'
z in a

二维数组

awk 中的二维数组用法跟 C 语言类似

  • 通过 array[i, j] 这样的形式访问。
  • 通过 if ( (i, j) in arr ) 判断元素是否存在,下标必须放置在圆括号中。
  • 通过 for ( key in arr ) 遍历数组。
[root@VM_157_18_centos ~]# awk 'BEGIN{awk 'BEGIN{
 for (i = 0; i < 4; i++) {
  for (j = 0; j < 4; j++) {
   arr[i, j] = i * j;
   print i"*"j, "=", i * j;
  }
 }
}'
0*0 = 0
0*1 = 0
...
3*2 = 6
3*3 = 9

2.7 内置函数

时间函数

函数说明
mktime( YYYY MM dd HH MM ss[ DST])生成时间
strftime([format [, timestamp]])格式化时间输出,将时间戳转为时间字符串格式,见下表
systime()时间戳,从 1970 年 1 月 1 日开始到当前时间的秒数

strftime日期和时间格式说明符:

格式描述
%a星期几的缩写(Sun)
%A星期几的完整写法(Sunday)
%b月名的缩写(Oct)
%B月名的完整写法(October)
%c本地日期和时间
%d十进制日期
%D日期 08/20/99
%e日期,如果只有一位会补上一个空格
%H用十进制表示24小时格式的小时
%I用十进制表示12小时格式的小时
%j从1月1日起一年中的第几天
%m十进制表示的月份
%M十进制表示的分钟
%p12小时表示法(AM/PM)
%S十进制表示的秒
%U十进制表示的一年中的第几个星期(星期天作为一个星期的开始)
%w十进制表示的星期几(星期天是0)
%W十进制表示的一年中的第几个星期(星期一作为一个星期的开始)
%x重新设置本地日期(08/20/99)
%X重新设置本地时间(12:00:00)
%y两位数字表示的年(99)
%Y当前月份
%Z时区(PDT)
%%百分号(%)

示例:

[root@VM_157_18_centos ~]# awk 'BEGIN{tstamp=mktime("2018 04 20 16 28 59");print strftime("%c",tstamp);}'
Fri 20 Apr 2018 04:28:59 PM CST

[root@VM_157_18_centos ~]# awk 'BEGIN{print systime()}'
1524212859

[root@VM_157_18_centos ~]# awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S", systime())}'
2018-04-20 16:31:14

算术函数

格式描述
atan2( y, x )返回 y/x 的反正切。
cos( x )返回 x 的余弦;x 是弧度。
sin( x )返回 x 的正弦;x 是弧度。
exp( x )返回 x 幂函数。
log( x )返回 x 的自然对数。
sqrt( x )返回 x 平方根。
int( x )返回 x 的截断至整数的值。
rand( )返回任意数字 n,其中 0 <= n < 1。
srand( [expr] )将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。

获取随机数示例:

[root@VM_157_18_centos ~]# awk 'BEGIN{srand(); print int(100*rand());}'
66
[root@VM_157_18_centos ~]# awk 'BEGIN{srand(); print int(100*rand());}'
29
[root@VM_157_18_centos ~]# awk 'BEGIN{srand(); print int(100*rand());}'
76

2.8 流程控制语句

awk 的流程控制语句跟 C 语言中的类似。

条件判断语句 if else

if (expresion)
  {...}
else if(expresion)
  {...}
else
  {...}

示例:

[root@VM_157_18_centos test]# awk 'BEGIN{
    score=60;
    if (socre > 60) {
        print "good";
    } else if (score <= 60) {
        print "danger";
    } else {
        print "hehe";
    }      
}'
danger

循环语句

for 循环

awk 中支持 C 风格的 for 循环:

for (init; ;expression) {
    ...
}

awk 也支持 for in 循环遍历数组:

for (var in arr) {
    ...
}

示例一:

[root@VM_157_18_centos test]# awk 'BEGIN{
 for (i = 5; i > 0; i--) {
  print i;
 }
}'
5
4
3
2
1

示例二:

[root@VM_157_18_centos test]# awk 'BEGIN{
 arr[1] = "a";
 arr[2]="123";
 for (v in arr) {
  print v, arr[v];
 }
}'
1 a
2 123
while 循环
while(expression) {
    ...
}
next 跳过后面的语句,continue 结束本次循环并开始下次循环

awk 就像是 C 语言中的 for 循环,循环执行次数就是文件或输入流的行数。next 指令类似 continue,跳过本次循环,开始下一次循环。下面示例跳过包含数字的行:

[root@VM_157_18_centos test]# echo -e 'abc\n123\nddd\n333\n666' | awk '/[0-9]/{next}{print NR, $0}'
1 abc
3 ddd
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值