我只是想找到一种方法来使用
awk
命令检查 csv 列中是否存在字符串值。除了包含特殊字符(例如 (
或 [
)的字符串之外,它工作正常。
它适用于没有特殊字符的文本。然后,当我尝试使用特殊字符搜索文本时,它不起作用,所以我尝试转义这些字符,但它也不起作用。
所以我得到了一个
test.csv
文件,其中包含一行:
"hello","(hello)","this is a test (bye)","Alright"
然后如果我尝试搜索第一个字段,例如:
text="hello"; awk -F '","' -v text="$text" '$1~text {print $4}' test.csv
它返回
Alright"
,这很好。
然后如果我尝试搜索第二个字段,例如:
text="(hello)"; awk -F '","' -v text="$text" '$2~text {print $4}' test.csv
它返回
Alright"
,这也很好。
然后如果我尝试搜索第三个字段,例如:
text="this is a test (bye)"; awk -F '","' -v text="$text" '$3~text {print $4}' test.csv
它什么也不返回。
然后如果我尝试转义特殊字符,例如:
text="this is a test \(bye\)"; awk -F '","' -v text="$text" '$3~text {print $4}' test.csv
它返回一条消息,例如:
awk: warning: the escape sequence '\(' is treated as a simple "("
awk: warning: the escape sequence '\)' is treated as a simple ")"
与之前一样没有结果。
仅关注不匹配的正则表达式问题....
~
运算符表示将操作的右侧处理为正则表达式。当右侧是字符串(或包含字符串的变量 - 如本例所示)时,字符串将转换为正则表达式(请参阅GNU awk - 使用动态正则表达式)。
在这种情况下:
text="this is a test (bye)"
awk -F '","' -v text="$text" '$3~text {print $4}' test.csv
比较(
$3~text
)转换为:
$3~/this is a test (bye)/
这里的括号被视为特殊的正则表达式字符,而not被视为文字括号,因此这实际上与:
相同$3~/this is a test bye/
这与数据(包含文字括号)不匹配。
为了匹配文字括号,我们可以转义括号,例如:
$3~/this is a test \(bye\)/
但是正如OP发现的那样,在处理包含字符串(即
bash
)的(text="this is a test \(bye\)"
)变量时,转义这些括号并不容易。
另一种选择是将括号括起来,例如:
$3~/this is a test [(]bye[)]/
哪个可以包含在变量中,即,以下内容确实有效:
text="this is a test [(]bye[)]"
awk -F '","' -v text="$text" '$3~text {print $4}' test.csv
下一个(更大的)问题就变成了如何使用必要的括号对重新格式化(
bash
)变量;请记住,正则表达式中还有其他具有特殊含义的字符(例如,.
、*
、[
和 ]
)。
此时,当试图找出哪些字符需要在 (
bash
) 变量内“转义”时,它开始变得非常混乱。
更简单的方法是查看处理 strings(而不是 regexes)的不同比较方法。正如评论中提到的,这就是
index()
函数派上用场的地方。
index()
函数的第二个参数被处理为字符串(而不是正则表达式),因此无需担心某些字符(例如,(
和)
)被区别对待/特殊对待。如果未找到第二个参数,index()
将返回 0
,否则返回一个整数,指示第二个参数的位置。 [注意: awk
将 0
视为 false
,将任何其他数字视为 true
]
这意味着我们可以保留原来的 (
bash
) 变量赋值,并对 awk
脚本进行一些小更改:
text="this is a test (bye)" # no change
awk -F '","' -v text="$text" 'index($3,text) {print $4}' test.csv
^^^^^^^^^^^^^^ # replaces '$3~text'
返回:
Alright"
注意: 有关各种字符串函数的更多详细信息,请参阅 GNU awk - 字符串函数;注意哪些参数被视为 strings 与 regexes
那么第二段代码呢?
text="(hello)"
awk -F '","' -v text="$text" '$2~text {print $4}' test.csv # returns Alright"
awk
对待这个就像:
$2~/(hello)/`
这确实是:
$2~/hello/`
最终结果是,它的计算结果为 true,因为它匹配文字字符串
hello
并且(基本上)忽略数据中的文字括号。
注意:
text="(hello)"
/ $1~text
在这种情况下也将评估为 true。
使用
-F '","'
,您的第一个字段值为 "hello
,而不是 hello
或 "hello"
。我认为,这就是导致您认为需要进行正则表达式而不是字符串比较的原因,但当您发现这是错误的解决方案时。使用 -F ","
不仅会导致您当前的问题,而且它很脆弱,因为它会在给定像 "head","foo"",""bar","tail"
这样的输入时失败,其中中间字段 "foo"",""bar"
包含嵌套的转义引号,它们之间有逗号。
使用 GNU awk 来实现
FPAT
:
$ awk -v FPAT='([^,]*)|("([^"]|"")*")' -v text='hello' '
$1 == ("\"" text "\"") { print $4 }
' test.csv
"Alright"
$ awk -v FPAT='([^,]*)|("([^"]|"")*")' -v text='this is a test (bye)' '
$3 == ("\"" text "\"") { print $4 }
' test.csv
"Alright"
你可以用任何 awk 来做到这一点但是你需要多写一些代码:
$ awk -v fpat='([^,]*)|("([^"]|"")*")' -v OFS=',' -v text='this is a test (bye)' '
{
tail = $0
$0 = ""
while ( (tail != "") && match(tail,fpat) ) {
$(NF+1) = substr(tail,1,RLENGTH)
tail = substr(tail,RLENGTH+2)
}
}
$3 == ("\"" text "\"") { print $4 }
' test.csv
"Alright"
有关使用 awk 读取 CSV 的更多信息,请参阅 使用 awk 有效解析 CSV 的最可靠方法是什么?。
如果您的text
字符串可以包含反斜杠,请参阅如何在 awk 脚本中使用 shell 变量? 了解除
-v
(解释转义序列)之外的其他方式,以在脚本之外为 awk 变量分配一个值。