在CSV文件中搜索数值的grep命令

3

我被要求从一个非常丑陋的CSV文件中提取特定的值。

该CSV文件格式如下:

command1=value1, command2=value2, etc etc.

到目前为止还没有问题,我一直在使用grep命令来查找所需的命令,然后通过cut -f 2 -d '=' 进行管道传输,以返回只有值。

我遇到的问题是其中一个字段是文本,可以具有由逗号分隔的多个值。要增加另一个曲线球,如果(仅当)其中一个值中有空格时,该字段将用双引号括起来,因此我要提取的值可能是:

command=value,..
command=value1,value2,..
command="value 1",..
command="value 1, value 2",..

(其中 ... 是日志文件中的其他值或行尾)

我原以为只需使用grep -oP '(?<=command1=).*(?= command2)'提取两个字段名称之间的数据,然后通过rev | cut -c 2- | rev 进行管道处理就可以解决问题了。

但我现在发现字段出现的顺序并不一致,因此文件可能会是:

 command1=value1, command3=value3, command2=value2

当命令可能或可能没有用双引号括起来时,如何获取command2的值,它也可能有逗号。我很难想象它可能是如何可能的,因为grep如何知道什么是值分解和下一个字段。
非常感谢您的任何帮助。

你所描述的问题似乎可以用一个简单的 awk 脚本轻松处理,但是你需要发布一些简洁、可测试的样例输入和期望输出,以便我们能够帮助你。 - Ed Morton
3个回答

1
在最坏的情况下(例如,在另一个键的引用值中可能出现,command2 = ),唯一的解决方法可能是编写专门的解析器来处理这个烦人的格式。(不幸的是,杀掉想出这种格式的人不会解决任何问题,并可能导致新问题。我知道这很诱人,但不要这样做。)
对于一种快速而肮脏的黑客方式,也许这已经足够了:
grep -oP '(^|, )command2=\K([^,"]+|"[^"]+")'

如果字段值被引用,这将保留双引号,但如果不需要,那应该很容易修复。使用比grep更好的工具也可以带来更好的精度; 这里是一个带有额外锚定的sed变量:
sed -n 's/^\(.*, \)*command2=\(\((^,"]*\)\|"\([^"]*\)"\)\(, .*\)*$/\4\5/p' 

1
我会结合使用grepsed。假设您有这个输入文件example.csv:
command1=value1, command2=value2,
command1=value1, command2="value2, value3"
command1=value1, command3=valu3

然后是这个命令:

 grep 'command2=' example.csv |
  sed -e 's/.*command2=//g' -e 's/^\([^"][^,]*\),.*$/\1/g' -e 's/^"\([^"]*\)".*$/\1/g'

会给你这个:
value2
value2, value3

解释:

  • grep 找到正确的行
  • sed 中的第一个表达式(即第一个 -e)删除所需值之前的所有内容
  • 第二个表达式处理没有引号的情况
  • 第三个表达式处理有引号的情况

请注意,CSV 是一种非常复杂的格式。这个正则表达式做了一些假设,例如 command2 只出现作为键。如果这个 CSV 不够好,那么我会使用一个成熟的 CSV 库的真正编程语言。


grep | sed 经常可以成功地重构为一个 sed 脚本。 - tripleee
s///p,就像你的回答中所说的吗? - Tamas Rev
1
grep 'foo'sed '/foo/',但在这种情况下,我会在脚本开头加上 -e '/command2=/!d'(删除不匹配的行)。 - tripleee

0

我不知道这是否是您正在寻找的内容,但是考虑到这个输入文件:

$ cat file
command1=value1.1,command2=value2.1,value2.2,command3="value 3.1",command4="value 4.1, value 4.2"

这个GNU awk(用于split()的第四个参数)脚本可能是你想要的:

$ cat tst.awk
{
    delete(c2v)
    split($0,f,/,?[^=,]+=/,s)
    for (i=1; i in s; i++) {
        gsub(/^,|=$/,"",s[i])
        print "populating command name to value array:", s[i], "->", f[i+1]
        c2v[s[i]] = f[i+1]
    }
    print c2v["command2"]
    print c2v["command4"]
}

$ awk -f tst.awk file
populating command to value: command1 -> value1.1
populating command to value: command2 -> value2.1,value2.2
populating command to value: command3 -> "value 3.1"
populating command to value: command4 -> "value 4.1, value 4.2"
value2.1,value2.2
"value 4.1, value 4.2"

修改打印语句以适应,这应该很明显...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接