【问题标题】:use a modified version of each line of a text file as argument in a sed command (bash)使用文本文件每一行的修改版本作为 sed 命令 (bash) 中的参数
【发布时间】:2021-05-22 23:55:25
【问题描述】:

我需要从 file1 中提取两个字符串之间的所有文本。第一个字符串是 file2 的每一行,第二个字符串始终是“Lambda”。但是,我不知道如何在我的 sed 命令中声明 file2 的每个字符串。另外,我需要在 file2 的每一行开头删除一个 ">" 以匹配 file1 的内容:

示例文件1:

some_text1

random text

Lambda

some_text2

random text

Lambda

some_text3

random text

Lambda

示例文件2:

>some_text1
>some_text3

我为 1 行提出了这个不完整的命令:

sed -n '/**line from file2, without ">" at the beginning**/,/^Lambda/p' file1

而且,虽然不完整,但这将是我对循环的想法(这不包括删除 >,我在命令中也需要它):

for line in file1; do sed -n '/$line/,/^Lambda/p' file1; done

示例输出(注意 some_text2 不存在,因为它不在 file2 上:

some_text1

random text

Lambda
some_text3

random text

Lambda

我能做什么?

【问题讨论】:

  • 每行运行 sed 的单独副本 非常 慢,以至于我通常会推荐一个完全不同的工具(让您在单程)。应该可以编写一个awk 脚本,将第一行读入一个数组,然后将第二个文件的每一行与该数组 f/e 进行比较。
  • ...您可能会发现 stackoverflow.com/questions/37014532/… 是一个不错的起点。
  • 顺便说一句——/$line/$line 内容作为正则表达式匹配任何地方给定行。如果您希望 整个 行的值与 作为精确文本 (而不是正则表达式)匹配,那么这允许比我的答案更容易、更有效的实现给了。

标签: bash text-processing


【解决方案1】:

为此运行sed 的多个副本是非常低效的。下面是一个awk脚本,无论file2有多长,只需要读取一次file1

#!/usr/bin/env bash
awk '
  BEGIN   { in_block=0 }
  NR==FNR { array[substr($0, 2)]=1; next }
  in_block == 0 {
    for (item in array) {
      if ($0 ~ item) {
        in_block=1
        print($0)
        next
      }
    }
  }
  in_block == 1 { print }
  in_block == 1 && /^Lambda/ { in_block=0 }
' file2 file1

【讨论】:

    【解决方案2】:

    您可以使用sed 更有效地做到这一点,方法是创建一个匹配file2 中所有字符串的单一模式,然后在file1 上只运行一次。在您的示例中,该模式类似于(some_text1|some_text3)(尽管这是“扩展”正则表达式语法,因此您需要使用sed -E)。像这样的:

    lines=$(sed -n 's/^>//p' file2)    # This just reads in the lines with > removed
    pattern="(${lines//$'\n'/|})"      # This actually converts them to a regex pattern
    sed -En "/${pattern}/,/^Lambda/ p" file1    # Extract all matching ranges
    

    请注意,如果您希望 file2 中的字符串匹配整行,而不仅仅是行中的某个位置,您可以使用:

    pattern="^(${lines//$'\n'/|})\$"    # The ^ and $ anchor to the beginning & end of line
    

    另外,请注意,如果 file2 中的行包含任何正则表达式元字符,它们将被视为它们的正则表达式含义;如果您希望它们被视为严格的文字字符串,则需要对它们进行预处理以转义 shell 元字符。如果它们包含/,则也需要转义。

    【讨论】:

      【解决方案3】:

      在循环中使用sed 主要是不好的做法。您可以考虑使用以下版本,它首先创建sed 命令(使用sed 本身!),然后调用sed 来处理这些命令:

       sed -n -f <(sed           \
           -e 's/.//'            \
           -e 's/[]\/*.[]/\\&/g' \
           -e 's%.*%/^&$/,/^Lambda$/p%' file2) file1
      

      如果可以保证 file2 不包含任何 []\/*. 字符,您可能希望省略 -e 's/[]\/*.[]/\\&amp;/g' 部分。注意&lt;(...) 表达式是bash 中的进程替换;它显示为一个文件,其中包含括号之间的命令输出。

      【讨论】:

        【解决方案4】:

        试试

        {mawk/mawk2/gawk} 'BEGIN { FS = "^[>]"; FN = ARGV[--ARGC];     
                
                while (getline < FN) { lookupL[$2]++ }; 
                close(FN);
        
                FN = ARGV[ARGC] = ""; 
                FS = "^Lambda";
            } { 
                match($0, /[[:graph:]]+/); 
        
               if (substr($0, RSTART, RLENGTH) in lookupL) { 
        
                   do { print; 
                        if (NF>1) {break} 
        
                   } while (getline); 
               }
           }' file1 file2
        

        file2 的大小应该不是什么大问题,除非您说的容量超过 2 GB。

        【讨论】:

        • 假设搜索行总是完全匹配?这符合 OP 的样本数据(并且肯定会提高效率),但不符合其原始程序逻辑;最好从他们那里得到关于差异是否是故意的直接回答。
        • @charles : 我昨晚刚做了一些模组,还没有机会对其进行实际测试。
        猜你喜欢
        • 1970-01-01
        • 2021-12-18
        • 1970-01-01
        • 1970-01-01
        • 2018-05-24
        • 2021-10-17
        • 2013-11-13
        • 1970-01-01
        • 2013-01-14
        相关资源
        最近更新 更多