awk 在 csv 文件中找不到带有特殊字符的字符串值

问题描述 投票:0回答:2

我只是想找到一种方法来使用

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 ")"

与之前一样没有结果。

shell csv awk
2个回答
4
投票

仅关注不匹配的正则表达式问题....

~
运算符表示将操作的右侧处理为正则表达式。当右侧是字符串(或包含字符串的变量 - 如本例所示)时,字符串将转换为正则表达式(请参阅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 - 字符串函数;注意哪些参数被视为 stringsregexes


那么第二段代码呢?

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。


2
投票

使用

-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 变量分配一个值。 

© www.soinside.com 2019 - 2024. All rights reserved.