gawk RS 仅在行首与 ^

gawk RS only at beginning of line with ^

假设我有多行记录,= 作为记录分隔符,但前提是 = 是一行的开头:

$ cat file
record 1, field 1
record 1, field 2 with a = in it
record 1, field 3
= record 2, field 1
record 2, field 2 also with a = in it
record 2, field 3
= final record 3, field 1
record 3, field 2

我想将与此类似的文件分成由 ^=[ \t] 分隔的记录和由 \n 分隔的字段。

我试过了:

$ gawk -v RS="^=[ \t]" -v FS="\n" '{printf "%s\n--- NF=%s, NR=%s ---\n", [=11=], NF, FNR}' file

但这会导致:

record 1, field 1
record 1, field 2 with a = in it
record 1, field 3
= record 2, field 1
record 2, field 2 also with a = in it
record 2, field 3
= final record 3, field 1
record 3, field 2

--- NF=9, NR=1 ---

^ 不能像我期望的那样作为行的开头。

我知道我能做到:

$ gawk -v RS="\n=[ \t]" -v FS="\n" '{printf "%s\nNF=%s, NR=%s\n", [=13=], NF, FNR}'

但这感觉就像 Unix / Windows 行分隔符的问题。它还有一个额外的 \n 附加到最终记录

我可以使用 sed^=[ \t] 替换为额外的 \n 然后在段落模式中使用 gawk

$ sed 's/^=[ \t]/\
/' file | gawk -v RS="" -v FS="\n" '{printf "%s\n--- NF=%s, NR=%s ---\n", [=14=], NF, FNR}'
record 1, field 1
record 1, field 2 with a = in it
record 1, field 3
--- NF=3, NR=1 ---
record 2, field 1
record 2, field 2 also with a = in it
record 2, field 3
--- NF=3, NR=2 ---
final record 3, field 1
record 3, field 2
--- NF=2, NR=3 ---

正是我要找的东西。

问题:有没有办法在 RS 中使用 ^ 在 gawk 中用多行记录指示 'start of the line',这样我就不必通过 sed ?我想我正在寻找 gawk.

中 PCRE 正则表达式中 m 标志的等价物

我不知道这是否有所不同,但我发现在 BEGIN 子句中执行此操作稍微容易一些:

awk 'BEGIN {RS = "\n= "; FS = "\n"} {printf "%s\n--- NF=%s, NR=%s ---\n", [=10=], NF, FNR}' records

这给出了结果:

record 1, field 1
record 1, field 2 with a = in it
record 1, field 3
--- NF=3, NR=1 ---
record 2, field 1
record 2, field 2 also with a = in it
record 2, field 3
--- NF=3, NR=2 ---
final record 3, field 1
record 3, field 2

--- NF=3, NR=3 ---

不需要解释,因为它实际上什么也没做,只是稍微重新表述了您已经做过的事情。看起来怎么样?

据我所知,^ 的问题在于本身没有 "lines"。有记载。我可能是错的,但我认为 "start of line" 概念与这种情况无关。 "Start of field" 将是,或 "start of record",尽管后者只是类似于:

[=12=] ~ /^chars/

但是,我对 awk 这部分的内部工作原理知之甚少,所以我欢迎对它的教育。

你可以通过检查最后一个字段来避免最后一条记录额外的新行

$ awk -F'\n' -v RS='\n=[ \t]' -v OFS='\n' '{NF-=$NF==""; 
                                            print [=10=], "---NF="NF ", ---NR="FNR}' file
record 1, field 1
record 1, field 2 with a = in it
record 1, field 3
---NF=3, ---NR=1
record 2, field 1
record 2, field 2 also with a = in it
record 2, field 3
---NF=3, ---NR=2
final record 3, field 1
record 3, field 2
---NF=2, ---NR=3

^ 表示 start of string,而不是 start of line。没有 start of line 字符,只有回车 return(\r = return 光标到行首)和换行(\n = 删除光标移到下一行)根据 tool/OS 一起或单独用于表示 end of line 又名 newline 的字符。 Windows 工具倾向于使用 \r\n 表示 newline 而 UNIX 单独使用 \n 这就是为什么 \n 通常被称为 newline character在 UNIX 中。

许多工具,例如sedgrep(默认情况下 awk)一次只读取 1 行,因此它们的输入缓冲区一次只包含一行,因此在该上下文中 start of stringstart of line 相同,这就是为什么您经常听到 ^ 被称为 start of line 字符,而通常情况下它不是。类似地,$end of string 字符,而不是通常提到的 end of line 字符,但在某些工具用于字符串输入缓冲区的上下文中时,可用于表示行尾reading/populating 一次一行。

这意味着如果您的工具不是一次读取一行,那么在 UNIX 文件中匹配行首字符 X 的正则表达式实际上是:

(^|\n)X

一行的结尾是:

X(\n|$)

但请注意,如果存在,这也是 matching/consuming 换行字符。

在 Windows 中将上面的 \n 更改为 \r\n 并在两者中工作,您可以使用 \r?\n 除非您的文件是在 Windows 上创建的并且可以包含换行中线,例如从 Excel 导出的 CSV 可能看起来像

field1,"field2 part a\nfield2 part b",field3\r\n

其中 \n\r 当然是字面意思。在那种情况下,您不希望独立的 \n 中场被误解为换行符。

试试这个(gawk-only 由于多字符 RS\s shorthand for [[:space:]]):

$ awk -v RS='\n(=\s*|$)' -F'\n' '{printf "%s\n--- NF=%s, NR=%s ---\n", [=13=], NF, FNR}' file
record 1, field 1
record 1, field 2 with a = in it
record 1, field 3
--- NF=3, NR=1 ---
record 2, field 1
record 2, field 2 also with a = in it
record 2, field 3
--- NF=3, NR=2 ---
final record 3, field 1
record 3, field 2
--- NF=2, NR=3 ---