Blog

跟踪数据字段和记录

Content #

如果想在gawk脚本中跟踪数据字段和记录,那么变量FNR、NF和NR用起来非常方便。有时你并不知道记录中到底有多少个数据字段。NF变量可以让你在不知道具体位置的情况下引用记录中的最后一个数据字段:

$ gawk 'BEGIN{FS=":"; OFS=":"} {print $1,$NF}' /etc/passwd
root:/bin/bash
daemon:/usr/sbin/nologin
bin:/usr/sbin/nologin
sys:/usr/sbin/nologin
sync:/bin/sync
games:/usr/sbin/nologin
man:/usr/sbin/nologin
...
rich:/bin/bash
$

NF变量含有数据文件中最后一个字段的编号。可以在NF变量之前加上美元符号,将其用作字段变量。

FNR变量包含当前数据文件中已处理过的记录数,NR变量则包含已处理过的记录总数。下面通过几个例子来了解一下这种差别:

$ gawk 'BEGIN{FS=","}{print $1,"FNR="FNR}' data1 data1
data11 FNR=1
data21 FNR=2
data31 FNR=3
data11 FNR=1
data21 FNR=2
data31 FNR=3
$

在这个例子中,gawk脚本在命令行指定了两个输入文件(同一个输入文件指定了两次)。该脚本会打印第一个字段的值和FNR变量的当前值。注意,当gawk脚本处理第二个数据文件时,FNR的值会被重置为1。

现在加上NR变量,看看会输出什么:

$ gawk '
> BEGIN {FS=","}
> {print $1,"FNR="FNR,"NR="NR}
> END{print "There were",NR,"records processed"}' data1 data1
data11 FNR=1 NR=1
data21 FNR=2 NR=2
data31 FNR=3 NR=3
data11 FNR=1 NR=4
data21 FNR=2 NR=5
data31 FNR=3 NR=6
There were 6 records processed
$

在gawk处理第二个数据文件时,FNR变量的值被重置了,而NR变量则继续计数。

...

ENVIRON变量

Content #

ENVIRON变量使用关联数组来提取shell环境变量。关联数组用文本(而非数值)作为数组索引。

数组索引中的文本是shell环境变量名,对应的数组元素值是shell环境变量的值。来看一个例子:

$ gawk '
> BEGIN{
> print ENVIRON["HOME"]
> print ENVIRON["PATH"]
> }'
/home/rich
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/
games:/usr/local/games:/snap/bin
$

From #

Linux命令行与shell脚本编程大全

ARGC和ARGV变量

Content #

变量ARGC和ARGV允许从shell中获取命令行参数的总数及其值。有点儿麻烦的地方在于gawk并不会将程序脚本视为命令行参数的一部分:

$ gawk ‘BEGIN{print ARGC,ARGV[1]}’ data1 2 data1 $ ARGC变量表明命令行上有两个参数。这包括gawk命令和data1参数(记住,程序脚本并不算参数)。ARGV数组从索引0开始,代表的是命令。第一个数组值是 gawk命令后的第一个命令行参数。

提示:跟shell变量不同,在脚本中引用gawk变量时,变量名前不用加美元符号。

注意,在引用特定字段的时候,一定要加上美元符号,比如,$1引用的是第一个字段。考虑下列代码的输出:

$ gawk 'BEGIN {FS=",";x=3} {print x, $x}' data1

From #

Linux命令行与shell脚本编程大全

占据多行的记录的处理方法

Content #

变量RS和ORS定义了gawk对数据流中记录的处理方式。在默认情况下,gawk会将 RS和ORS设置为换行符。默认的RS值表明,输入数据流中的每行文本就是一条记录。

有时,你会在数据流中碰到占据多行的记录,典型的例子是在包含地址和电话号码的数据中,地址和电话号码各占一行: Ima Test 123 Main Street Chicago, IL 60601 (312)555-1234 如果用默认的FS变量值和RS变量值来读取这组数据,gawk就会把每一行作为一条单独的记录来读取,并将其中的空格作为字段分隔符。这可不是你希望看到的。

要解决这个问题,需要把FS变量设置成换行符。这就表明数据流中的每一行都是一个单独的字段,行内的所有数据都属于同一个数据字段。但是,让人头疼的是不知道新记录从何处开始。

为此,可以把RS变量设置成空字符串,然后在数据记录之间留一个空行。gawk会把每一个空行都视为记录分隔符。下面的例子采用了这种方法: $ cat data2 Ima Test 123 Main Street Chicago, IL 60601 (312)555-1234

Frank Tester 456 Oak Street Indianapolis, IN 46201 (317)555-9876

Haley Example 4231 Elm Street Detroit, MI 48201 (313)555-4938 $ gawk ‘BEGIN{FS="\n"; RS=""} {print $1,$4}’ data2 Ima Test (312)555-1234 Frank Tester (317)555-9876 Haley Example (313)555-4938 $ gawk现在将文件中的每一行都视为一个字段,将空行作为记录分隔符。

From #

Linux命令行与shell脚本编程大全

...

a,i命令与c命令的区别

Content #

sed的a和i命令与c命令格式上相似,但对pattern space的影响却很不相同,主要不同在哪里?

  1. a和i命令只能作用于单行地址,c命令可作用于多行。
  2. c命令会清空pattern space(与d命令相同),在c命令后的命令不再有效。a和 i命令则不会改变pattern space的内容,新增加的内容只会输出,不会对sed 其他命令生产任何影响。

From #

保留空间(hold space)

Content #

模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时保存着待检查的文本。还有另一块称作保留空间(hold space)的缓冲区。当你在处理模式空间中的某些行时,可以用保留空间临时保存部分行。

与保留空间相关的命令有5个: h - 将模式空间复制到保留空间(holds) H - 将模式空间附加到保留空间 g - 将保留空间复制到模式空间(gets) G - 将保留空间附加到模式空间 x - 交换模式空间和保留空间的内容

From #

Linux命令行与shell脚本编程大全

删除跨行的注释

Content #

现有emp_comment.txt文件内容如下: John Doe CEO #Chief Executive Officer# Jason Smith IT Manager #Information Technology Officer# Raj Reddy Sysadmin #System Administrator# Anand Ram Developer #Senior Programmer# Jane Miller Sales Manager #Sales Manager#

要用sed删除所有的两个#符号之间的注释,用"N,P,D"模式实现。

sed -e '/#/{N;/#.*#/{s/#.*#//;P;D}}' emp_comment.txt

删除.H1开头的行之下的空行

Content #

现有如下文本: .H1 “On Egypt”

Napoleon, pointing to the Pyramids, said to his troops: “Soldiers, forty centuries have their eyes upon you.”

现在要删除.H1开头的行之下的空行,但不能删除文档中其它部分的空行,用sed 来处理,该写什么样的脚本?

/^\.H1/{
n
/^$/d
}

打印出名字及头衔(employee实例)

Content #

现有emp.txt文件内容如下: John Doe CEO Jason Smith IT Manager Raj Reddy Sysadmin Anand Ram Developer Jane Miller Sales Manager

1.打印出所有"Manager"的名字及头衔,要求名字与头衔之间以":“分隔。

  • 使用H,x,h命令(bash/001_sed_Hxh.txt)
#!/bin/sed -nf
/Manager/!h
/Manager/{H;x;s/\n/:/;p}
  • 使用G,x,h命令
#!/bin/sed -nf
/Manager/!h
/Manager/{x;G;s/\n/:/;p}
  1. 打印出所有人的名字及头衔

用N命令来实现

sed -e '{N;s/\n/:/}' emp.txt
  1. 打印出所有"Manager"的名字
#!/bin/sed -nf
/Manager/!h
/Manager/{x;p}

或者:

#!/bin/sed -nf
x;n
/Manager/{x;p}

或者:

#!/bin/sed -nf
/Manager/!h
/Manager/{g;p}

或者:

sed -n '{N;/Manager/P}' emp.txt
  1. 打印出如下格式的内容,注意"Manager"前面带有”*“号

John Doe:CEO *Jason Smith:IT Manager Raj Reddy:Sysadmin Anand Ram:Developer *Jane Miller:Sales Manager

...

删除结尾的空行

Content #

就跟打印数据流的结尾一样,删除数据流结尾的空行也得花点儿心思:利用循环来实现。

sed '{
:start
/^\n*$/{$d; N; b start }
}'

乍一看可能有点儿怪,在正常的脚本花括号内还有花括号。但这可以在整个命令脚本中将部分命令分组。命令分组会被应用于指定的地址模式。该地址模式能够匹配只含一个换行符的行。如果找到了这样的行,而且还是最后一行,删除命令就会将它删除。如果不是最后一行,那么N命令会将下一行附加到它后面,然后分支命令会跳到循环起始位置重新开始。

下面是脚本的实际运行效果:

$ cat data10.txt
Line one.
Line two.



$
$ sed '{
> :start
> /^\n*$/{$d; N; b start}
> }' data10.txt
Line one.
Line two.
$

From #

Linux命令行与shell脚本编程大全