【问题标题】:Extract lines from multiple files based on condition in one line根据一行中的条件从多个文件中提取行
【发布时间】:2020-01-13 08:44:00
【问题描述】:

我有许多目录,其中包含格式如下的文本文件:

class FeatureFE():
    meta_data = MetaData(
        name='COOL_FEATURE',
        sub_type='EXTRA_COOL_FEATURES',
        required_data=[accounts, logs],
        has_graph=True,
        x_axis_label='Time',
        y_axis_label='Foo',
        graph_caption='Description of my feature',
        priority='low',
    )

我被赋予的任务是遍历每个.py 文件,如果是has_graph=True,则提取namerequired_datagraph_caption - 最终目标是一个结构如下的CSV:

name, required_data, graph_caption
'COOL_FEATURE', [accounts, logs],'Description of my feature',

这对awk/sed/grep 来说绝对可行,但我很难做到。到目前为止,我已经做到了:

grep -E -B 4 -A 5 "has_graph=True" feature_17.py | tr -s ' ' | grep '^ name\|^ required_data\|^ graph_caption' | sed 's/.*=//'

返回

'COOL_FEATURE',
[accounts, logs],
'Description of my feature',

对于一个文件,但在 *.py 上运行时什么都没有。

非常感谢您的帮助!

【问题讨论】:

  • 所需的输出 CSV 标题行 name, required_data, graph_caption 是 3 个字段,但数据行 'COOL_FEATURE', [accounts, logs],'Description of my feature' 是 4 个字段(计算逗号)。您需要将每个字段括在双引号中,因此还需要将每个字段中可能出现的任何双引号加倍(您应该在示例中包含它)以使输出成为有效的 CSV。

标签: perl awk sed grep text-processing


【解决方案1】:

每当您的数据中有 name=value 对时,我发现最好先创建一个包含这些映射的数组,然后简单地通过它们的名称访问这些值。例如,使用 GNU awk 将第三个 arg 用于 match() 和 ENDFILE:

$ cat tst.awk
BEGIN {
    OFS = ","
    numNames = split("name required_data graph_caption",names)
}

match($0,/^\s*(\w+)\s*=\s*(.*\S)\s*,\s*$/,a) {
    name  = a[1]
    value = a[2]
    name2value[name] = value
}

ENDFILE {
    if ( name2value["has_graph"] == "True" ) {
        if ( !doneHdr++ ) {
            for (nameNr=1; nameNr<=numNames; nameNr++) {
                name = names[nameNr]
                printf "%s%s", name, (nameNr<numNames ? OFS : ORS)
            }
        }
        for (nameNr=1; nameNr<=numNames; nameNr++) {
            name  = names[nameNr]
            value = name2value[name]
            gsub(/"/,"\"\"",value)
            printf "\"%s\"%s", value, (nameNr<numNames ? OFS : ORS)
        }
    }
    delete name2value
}

$ awk -f tst.awk file
name,required_data,graph_caption
"'COOL_FEATURE'","[accounts, logs]","'Description of my feature'"

我在打印前添加了双引号,以确保输出是有效的 CSV,即使您的值包含 ,(如 [accounts, logs] 那样)和/或双引号。

要将上述内容与find 一起使用,我会这样做:

find . -name '*.py' -exec awk -f tst.awk {} +

但先删除这部分脚本:

        if ( !doneHdr++ ) {
            for (nameNr=1; nameNr<=numNames; nameNr++) {
                name = names[nameNr]
                printf "%s%s", name, (nameNr<numNames ? OFS : ORS)
            }
        }

因此您不会为从 find 传递给 awk 的每批文件打印一次标题行,而只需稍后手动添加标题行或在运行脚本之前打印它。还有其他方法可以解决这个问题,但这是最简单的。

【讨论】:

  • 感谢这个工作非常好。我可以使用awk -f tst.awk *.py 在整个文件夹上执行此操作,但是有没有办法为嵌套文件夹递归执行此操作? (如果不是我能做到,你仍然为我节省了大量时间!)
  • 将查找文件的命令和对文件内容进行操作的命令分开。查找文件的命令是 find,所以只需使用它来查找文件(参见 find 手册页),然后调用我在结果文件列表中发布的 awk 脚本。
  • 是的 - 将文件名列表传送到 awk 以作为输入读取。你需要给 awk 一个文件名列表作为参数,而不是作为输入,所以 awk 可以打开每个文件并读取它的 contents 作为输入。我编辑了我的答案以显示使用 awk 和 find 的最简单方法。
  • 非常感谢!切线,find -execfind | xargs有区别吗?
  • 不客气。是的,但如果您想了解相关信息,请提出一个带有这些命令的新问题。
【解决方案2】:

Perl 解决方案:

perl -0777 -nE 'for my $key (qw( name required_data graph_caption )) {
                  ($h{$key}) = /\b$key=(.*),/;
                }
                say join ",", @h{qw{ name required_data graph_caption }};
               ' -- *.py
  • -n逐条读取输入的记录,分别执行代码
  • -0777 读取整个文件而不是逐行读取
  • %h 哈希填充了从正则表达式匹配中捕获的值,\b 代表“单词边界”

【讨论】:

  • 谢谢,这很好,但它不检查条件,对吧?
【解决方案3】:

您能否尝试以下操作(考虑到您的 python 文件只会出现 1 次此类)。用 GNU awk 测试和编写。

awk '
BEGIN{
  FS="="
  s1="\047"
  OFS=","
  print "name, required_data, graph_caption"
}
/has_graph=True/{
  found=1
}
found && /name/{
  sub(/,/,"",$2)
  name=$2
  next
}
found && /required_data/{
  sub(/,/,"",$2)
  data=$2
}
found && /graph_caption/{
  sub(/,/,"",$2)
  print s1 name s1,s1 data s1,s1 $2 s1
  nextfile
}
'  *.py

【讨论】:

    猜你喜欢
    • 2017-11-10
    • 1970-01-01
    • 1970-01-01
    • 2020-08-11
    • 2016-12-06
    • 1970-01-01
    • 1970-01-01
    • 2017-01-22
    • 2022-08-18
    相关资源
    最近更新 更多