AWK:反斜杠作为众多字段分隔符之一

  • 本文关键字:字段 分隔符 AWK bash awk
  • 更新时间 :
  • 英文 :


请帮助我做错了什么?如何转义反斜杠?我已经尝试了 2,3,4试图逃避它。请帮忙。

echo "email@gmail.com:aaa/bbbccc ddd" |awk -F"[/@: ]" '
{
print $1 " | " $2 " | " $3 " | " $4 " | " $5
}'

预期产出:

email | gmail.com | aaa | bbb | ccc | ddd

以下是编写脚本的方法:

$ printf '%sn' 'email@gmail.com:aaa/bbbccc ddd' |
awk -F'[/@: \\]' -v OFS=' | ' '
{
$1 = $1
print
}
'
email | gmail.com | aaa | bbb | ccc | ddd

shell中引号之间的区别(注意:这与 awk 或您可能从 shell 调用的任何其他工具无关,这都是关于 shell 的):

  1. 'foo'="嘿,壳,远离这个,不要看它">
  2. "foo"="嘿,壳牌,请解释一下这个来扩展变量,等等。
  3. foo="嘿,shell,请解释为做与双引号相同的事情,但也做通配、文件名扩展等"。

有关所有血腥细节,请参阅 https://mywiki.wooledge.org/Quotes。

所以外壳引用规则是:

始终在所有字符串和脚本两边使用单引号,除非你需要shell 来扩展变量,然后使用双引号,除非你还需要shell 进行通配,然后不使用引号。

现在对于awk部分 - 当您将FS指定为字符串时,您实际上是在编写一个动态(也称为计算)正则表达式(请参阅 https://www.gnu.org/software/gawk/manual/gawk.html#Computed-Regexps),因此awk必须解析该字符串两次,一次将其转换为正则表达式,然后将其用作正则表达式。这意味着您希望在正则表达式中出现的任何反斜杠都必须在字符串中出现两次,以便在将字符串转换为正则表达式的第一遍之后,仍然剩下 1 个反斜杠。

因此,如果你想要一个正则表达式,比如说,让你在输入中找到|,那么你只想写:

$ echo 'abc' | awk '/[|]/'
awk: cmd. line:1: /[|]/
awk: cmd. line:1:  ^ unterminated regexp
awk: cmd. line:1: error: Unmatched [, [^, [:, [., or [=: /[|]//

但是你不能,因为是正则表达式中的转义字符,所以你需要转义 IT 以使其成为文字:

$ echo 'abc' | awk '/[|\]/'
abc

现在,如果您想使用动态而不是文字正则表达式(这也适用于设置FS),您需要执行以下操作:

$ echo 'abc' | awk -v re='[|\]' '$0 ~ re'
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: invalid regexp: Unmatched [, [^, [:, [., or [=: /[|]/

但是你不能,因为表示动态正则表达式的字符串必须转换为文字,并且会使用一组反斜杠,因此您必须编写:

$ echo 'abc' | awk -v re='[|\\]' '$0 ~ re'
abc

如果你改为写(现在要[错误地]使用双引号而不是单引号):

$ echo 'abc' | awk -v re="[|\\]" '$0 ~ re'
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: invalid regexp: Unmatched [, [^, [:, [., or [=: /[|]/

然后你会要求 shell 在 awk 看到它之前解释字符串,所以你需要另一层反斜杠来让 THAT 传递使用:

$ echo 'abc' | awk -v re="[|\\\\]" '$0 ~ re'
abc

所以 - 除非你需要邀请 shell 来解释字符串,否则不要这样做。只需遵循我上面给出的外壳报价规则即可。

现在,请记住 - awk 不是 shell,因此 awk脚本中的双引号是awk语言的一部分,而不是 shell 语言的一部分,因此没有相同的语义。当你用 awk 编写"foo"时,你不是在邀请 awk 或 shell 或其他任何东西来解释它,而是在写一个文字字符串,就像你在 shell 中编写'foo'一样,所以你不需要对 awk 脚本中的字符串进行任何额外的转义:

$ echo 'abc' | awk 'BEGIN{re="[|\\]"} $0 ~ re'
abc

最后一条语句假设您的 awk 脚本存储在文件中或从 shell 调用的单引号内。如果你选择在awk脚本周围使用双引号而不是单引号,那么你就会邀请一个痛苦的世界,不得不逃避$符号,在反斜杠上加倍等,因为你要求shell在awk看到之前解释整个ask脚本 - 不要这样做。在Windows上,我知道您必须这样做,这就是为什么标准建议将awk脚本保存在文件中而不是在命令行上引用它的原因。

GNU awk 的解决方法:

awk 'BEGIN{FS="[^a-zA-Z0-9.-]"; OFS=" | "} {$1=$1; print}'

使用除a-zA-Z0-9.-以外的所有内容作为输入字段分隔符。

或者将FS="[^a-zA-Z0-9.-]"替换为FPAT="[a-zA-Z0-9.-]+"以定义哪些字符是字段的一部分。

您还可以使用sub来摆脱''并简化字段分隔符,例如

awk -F"[@:/ ]" '{ sub(/\/,"/",$0); print $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 }'

示例使用/输出

$ printf "email@gmail.com:aaa/bbbccc ddd" | 
awk -F"[@:/ ]" '{ sub(/\/,"/",$0); print $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 }'
email | gmail.com | aaa | bbb | ccc | ddd

加上@CyrusOFS改进,表达式简化为:

awk -F"[@:/ ]" -v OFS=" | " '{ sub(/\/,"/",$0); $1=$1 }1'

(输出相同,但命令的纠察栅栏外观较少)

显然,您需要获得的所有反斜杠,并避免外壳陷阱:

$ echo "email@gmail.com:aaa/bbbccc ddd" | awk -F"[/@:\\ ]" '{ print $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 }'
email | gmail.com | aaa | bbbccc | ddd | 
$ echo "email@gmail.com:aaa/bbbccc ddd" | awk -F'[/@:\\ ]' '{ print $1 " | " $2 " | " $3 " | " $4 " | " $5 " | " $6 }'
email | gmail.com | aaa | bbb | ccc | ddd

请注意,两者之间的区别在于外壳引用,""解释反斜杠,而''则不解释。

(另外,您有 6 个输出项,因此需要添加" | " $6)

编辑

  • 这不是特定于gawk的。
  • 它特定于-F,而不是直接从脚本(BEGIN { FS="[/@:\\ ]"})设置FS
  • 正如Cyrus的回答和David C. Rankin的回答中所述,您可以使用OFS=" | "简化脚本。

所以最干净的版本是:

$ echo "email@gmail.com:aaa/bbbccc ddd" | awk 'BEGIN { FS="[/@:\\ ]"; OFS=" | " } { $1=$1; print }'
email | gmail.com | aaa | bbb | ccc | ddd

您也可以在此处使用sed

$ s='email@gmail.com:aaa/bbbccc ddd'
$ echo "$s" | sed 's%[/@: ]% | %g'
email | gmail.com | aaa | bbb | ccc | ddd

GNU sed上测试,如果后面的字符没有创建有效的转义序列,则不需要对进行转义

,例如t

最新更新