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

t3irkdon  于 2023-05-26  发布在  其他
关注(0)|答案(2)|浏览(174)

我只是想找到一种方法来检查一个字符串值是否存在于一个csv列使用awk命令。除了包含特殊字符(如([)的字符串外,它可以正常工作。
它适用于没有特殊字符的文本。然后当我尝试用特殊字符搜索文本时,它不起作用,所以我试图摆脱这些字符,但它也不起作用。
所以我得到了一个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 ")"

没有像以前那样的结果。

9q78igpj

9q78igpj1#

只关注不匹配的正则表达式问题。
~操作符表示将操作的右侧作为正则表达式处理。当右边是一个字符串(或包含字符串的变量-如本例所示)时,字符串被转换为正则表达式(参见GNU awk - Using Dynamic Regexps)。
在这种情况下:

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发现,当处理包含字符串(即text="this is a test \(bye\)")的(bash)变量时,转义这些括号并不容易。
另一种选择是将括号括起来,例如:

$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)变量中“转义”时,它开始变得非常混乱。
一种更简单的方法是查看处理 * 字符串 *(而不是 * 正则表达式 *)的不同比较方法。正如在评论中提到的,这就是index()函数派上用场的地方。
index()函数的第二个参数被处理为字符串(而不是正则表达式),因此无需担心某些字符(例如,())被不同/特殊处理。如果没有找到第二个参数,index()将返回0,否则将返回一个整数,表示第二个参数的位置。[注意:awk0视为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 - String Functions;请注意哪些参数被视为 * 字符串 * 与 * 正则表达式 *

那么第二段代码是什么呢?

text="(hello)"
awk -F '","' -v text="$text" '$2~text {print $4}' test.csv it returns Alright"

awk将其视为:

$2~/(hello)/`

实际上是:

$2~/hello/`

净结果是,这计算为true,因为它匹配文字字符串hello,并且(基本上)忽略数据中的文字括号。

注意:text="(hello)"/$1~text在这种情况下也会计算为true。

tv6aics1

tv6aics12#

对于-F '","',第一个字段值是"hello,而不是hello"hello"。我认为,这就是为什么你认为你需要做regexp而不是字符串比较,但你发现这是错误的解决方案。使用-F ","不仅会导致您当前的问题,而且它很脆弱,因为它会失败,因为它会像"head","foo"",""bar","tail"这样的输入,其中中间字段"foo"",""bar"包含嵌套的转义引号,引号之间有逗号。

FPAT使用GNU awk

$ 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的更多信息,请参阅What's the most robust way to efficiently parse CSV using awk?
如果你的text字符串可以包含反斜杠,那么请参阅How do I use shell variables in an awk script?,了解除了-v(解释转义序列)之外的其他方法,在脚本之外为awk变量赋值。

相关问题