【问题标题】:Deleting lines with sed or awk使用 sed 或 awk 删除行
【发布时间】:2012-12-13 01:34:21
【问题描述】:

我有一个这样的文件 data.txt。

>1BN5.txt
207
208
211
>1B24.txt
88
92

我有一个包含文本文件的文件夹 F1。

F1文件夹中的1BN5.txt文件如下所示。

ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C 
ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C  
ATOM    422  C   SER A 248      70.124 -29.955   8.226  1.00 55.81           C 
ATOM    615  H   LEU B 208       3.361  -5.394  -6.021  1.00 10.00           H
ATOM    616  HA  LEU B 211       2.930  -4.494  -3.302  1.00 10.00           H 
ATOM    626  N   MET B  87       1.054  -3.071  -5.633  1.00 10.00           N  
ATOM    627  CA  MET B  87      -0.213  -2.354  -5.826  1.00 10.00           C 

F1文件夹中的1B24.txt文件如下所示。

ATOM    630  CB  MET B  87      -0.476  -2.140  -7.318  1.00 10.00           C 
ATOM    631  CG  MET B  88      -0.828  -0.688  -7.575  1.00 10.00           C
ATOM    632  SD  MET B  88      -2.380  -0.156  -6.830  1.00 10.00           S
ATOM    643  N   ALA B  92      -1.541  -4.371  -5.366  1.00 10.00           N  
ATOM    644  CA  ALA B  94      -2.560  -5.149  -4.675  1.00 10.00           C

我只需要 1BN5.txt 文件中包含 207,208,211(第 6 列)的行。我想删除 1BN5.txt 文件中的其他行。像这样,我只需要 1B24.txt 文件中包含 88,92 的行。

Desired output

1BN5.txt 文件

ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C
ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C 
ATOM    615  H   LEU B 208       3.361  -5.394  -6.021  1.00 10.00           H  
ATOM    616  HA  LEU B 211       2.930  -4.494  -3.302  1.00 10.00           H

1B24.txt 文件

ATOM    631  CG  MET B  88      -0.828  -0.688  -7.575  1.00 10.00           C
ATOM    632  SD  MET B  88      -2.380  -0.156  -6.830  1.00 10.00           S
ATOM    643  N   ALA B  92      -1.541  -4.371  -5.366  1.00 10.00           N 

【问题讨论】:

  • 从您从几个合理的答案中得到的错误来看,您的文件data.txt中可能有一些额外的字符。
  • 文件data.txt中没有多余的字符。

标签: sed awk


【解决方案1】:

这是使用GNU awk 的一种方式。运行方式:

awk -f script.awk data.txt

script.awk的内容:

/^>/ {
    file = substr($1,2)
    next
}

{
    a[file][$1]
}

END {

    for (i in a) {

        while ( ( getline line < ("./F1/" i) ) > 0 ) {

            split(line,b)

            for (j in a[i]) {

                if (b[6]==j) {

                    print line > "./F1/" i ".new"
                }
            }
        }

        system(sprintf("mv ./F1/%s.new ./F1/%s", i, i))
    }
}

或者,这里是单行:

awk '/^>/ { file = substr($1,2); next } { a[file][$1] } END { for (i in a) { while ( ( getline line < ("./F1/" i) ) > 0 ) { split(line,b); for (j in a[i]) if (b[6]==j) print line > "./F1/" i ".new" } system(sprintf("mv ./F1/%s.new ./F1/%s", i, i)) } }' data.txt


如果您有旧版本的awk,早于GNU Awk 4.0.0,您可以尝试以下操作。运行方式:

awk -f script.awk data.txt

script.awk 的内容:

/^>/ {
    file = substr($1,2)
    next
}

{
    a[file]=( a[file] ? a[file] SUBSEP : "") $1
}

END {

    for (i in a) {

        split(a[i],b,SUBSEP)

        while ( ( getline line < ("./F1/" i) ) > 0 ) {

            split(line,c)

            for (j in b) {

                if (c[6]==b[j]) {

                    print line > "./F1/" i ".new"
                }
            }
        }

        system(sprintf("mv ./F1/%s.new ./F1/%s", i, i))
    }
}

或者,这里是单行:

awk '/^>/ { file = substr($1,2); next } { a[file]=( a[file] ? a[file] SUBSEP : "") $1 } END { for (i in a) { split(a[i],b,SUBSEP); while ( ( getline line < ("./F1/" i) ) > 0 ) { split(line,c); for (j in b) if (c[6]==b[j]) print line > "./F1/" i ".new" } system(sprintf("mv ./F1/%s.new ./F1/%s", i, i)) } }' data.txt


请注意,此脚本完全按照您的描述进行。它希望像 1BN5.txt1B24.txt 这样的文件驻留在当前工作目录中的文件夹 F1 中。它还将覆盖您的原始文件。如果这不是所需的行为,请放弃 system() 呼叫。 HTH。

结果:

F1/1BN5.txt的内容:

ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C 
ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C  
ATOM    615  H   LEU B 208       3.361  -5.394  -6.021  1.00 10.00           H
ATOM    616  HA  LEU B 211       2.930  -4.494  -3.302  1.00 10.00           H 

F1/1B24.txt的内容:

ATOM    631  CG  MET B  88      -0.828  -0.688  -7.575  1.00 10.00           C
ATOM    632  SD  MET B  88      -2.380  -0.156  -6.830  1.00 10.00           S
ATOM    643  N   ALA B  92      -1.541  -4.371  -5.366  1.00 10.00           N

【讨论】:

  • 感谢您的回答。您的代码在我的示例中运行良好。但是对于大文件,我没有得到正确的输出。它只打印几行。如果您在我的示例中增加行数,您可以理解其中的区别。
  • @user1606106:嗯。我现在会调查一下。有机会向我发送那些较大的文件吗?如果可以,请使用Dropbox 和我猜的某种压缩。
  • @user1606106:我已经为你更新了脚本。它现在应该适用于非常大的文件。如果您有任何问题,请告诉我。 HTH。
  • @steve getline 只是多余的,因为 awk 在没有它的情况下读取文件中的所有行,所以就像您有一个执行 WHILE read line { workon line } 的脚本/工具并将其修改为道德上的 @ 987654341@。即使它是安全的,这也不是一个好方法......
  • 上面的 getline 方法与我发布的非 getline 解决方案的问题的简单示例。忽略初始文件。假设您想增强脚本以额外打印每个选定文件的第 17 行的第 4 字段?使用 getline 方法,您需要引入一个变量来计算读取的行数,然后调用 split(),而使用非 getline 方法,您只需使用 FNR 和 $4。您可以删除“line”变量以免费获得拆分,但您已经失去了原来的 0 美元。您可以创建一个变量来存储它...明白了吗?这只是一堆变通办法。
【解决方案2】:

不要尝试从现有文件中删除行,尝试创建一个仅包含您想要的行的新文件:

cat 1bn5.txt | awk '$6 == 207 || $6 == 208 || $6 == 211 { print }' > output.txt

【讨论】:

    【解决方案3】:

    假设 gnu awk,从包含 data.txt 的目录运行此命令:

    awk -F">" '{if($2 != ""){fname=$2}if($2 == ""){term=$1;system("grep "term" F1/"fname" >>F1/"fname"_results");}}' data.txt
    

    这会解析data.txt 中的文件名和搜索词,然后从awk 内部调用grep,将data.txt 中列出的每个文件和词的匹配项附加到F1 中名为originalfilename.txt_results 的新文件中.

    如果你想完全替换原始文件,你可以运行这个命令:

    grep "^>.*$" data.txt | sed 's/>//' | xargs -I{} find F1 -name {}_results -exec mv F1/{}_results F1/{} \;
    

    【讨论】:

    • 感谢您的回答。你的代码运行良好。但是不需要的行正在与输出一起打印。
    • 如果您可以提供更大的样本集或扩展您看到的额外不需要的输出,我可以尝试进行一些修改。
    【解决方案4】:

    这会将 F1 中的所有文件移动到名为“backup”的 tmp 目录,然后在 F1 下重新创建生成的非空文件

    mv F1 backup &&
    mkdir F1 &&
    awk '
    NF==FNR {
       if (sub(/>/,"")) {
          file=$0
          ARGV[ARGC++] = "backup/" file
       }
       else {
          tgt[file,$0] = "F1/" file
       }
       next
    }
    (FILENAME,$6) in tgt {
       print > tgt[FILENAME,$6]
    }
    ' data.txt &&
    rm -rf backup
    

    如果您也想要空文件,这是一个微不足道的调整,如果您想保留备份目录,只需在最后删除 "&amp;&amp; rm.."(无论如何都要在测试期间这样做)。

    编辑:仅供参考,在这种情况下,您可能会认为 getline 的情况并不完全不正确,因为它解析的第一个文件在结构和意图上与其余文件完全不同,因此解析一个文件与其他文件不同是'以后不会引起任何维护问题:

    mv F1 backup &&
    mkdir F1 &&
    awk -v data="data.txt" '
    BEGIN {
       while ( (getline line < data) > 0 ) {
          if (sub(/>/,"",line)) {
             file=line
             ARGV[ARGC++] = "backup/" file
          }
          else {
             tgt[file,line] = "F1/" file
          }
       }
    }
    (FILENAME,$6) in tgt {
       print > tgt[FILENAME,$6]
    }
    ' &&
    rm -rf backup
    

    但正如您所见,它使脚本更加复杂(尽管效率稍高一些,因为现在主体中没有对 FNR==NR 的测试)。

    【讨论】:

    • 我有点困惑,可能是因为凌晨 1 点……但是当参数列表中只列出一个文件时,FNR==NR 的意义何在? “tmpdir/”从何而来?
    • 我们从一个文件开始,但是当它读取第一个文件时,它正在向 ARGV 添加文件名,所以在读取第一个文件之后,现在有很多文件要解析。 “tmpdir”应该是“备份”,我会修复它,谢谢。
    • 哇,这是一个巧妙的解决方案——如果可以的话,我会再给它投票。谢谢你的解释:-)
    • @steve 不客气,我也刚刚添加了一个适合 getline 的替代方案,只是为了改变节奏:-)。
    【解决方案5】:

    此解决方案使用记录分隔符玩了一些技巧:“data.txt”使用 > 作为记录分隔符,而其他文件使用换行符。

    awk '
        BEGIN {RS=">"}
        FNR == 1 {
            # since the first char in data.txt is the record separator, 
            # there is an empty record before the real data starts
            next
        }
        {
            n = split($0, a, "\n")
            file = "F1/" a[1]
            newfile = file ".new"
            RS="\n"
            while (getline < file) {
                for (i=2; i<n; i++) {
                    if ($6 == a[i]) {
                        print > newfile
                        break
                    }
                }
            }
            RS=">"
            system(sprintf("mv \"%s\" \"%s.bak\" && mv \"%s\" \"%s\"", file, file, newfile, file))
        }
    ' data.txt 
    

    【讨论】:

    • 感谢您的回答。当我运行您的代码时,我收到错误 ^ invalid char ''' in expression。如何解决这个错误?
    • 不知道。我在测试时没有收到任何错误。你是剪切粘贴还是重新输入?
    • 对于包含空格的文件名和不安全地使用 getline 将失败(不测试它的返回)。也不确定是否所有非 gawk awks(仅允许 RS 中的单个字符)将RS="\n" 视为 RS=newline,或者是否部分或全部将其视为 RS=反斜杠。您可能需要使用RS=sprintf("%s","\n")。最后,打印应该使用 > 而不是 >> (如果文件存在,您所拥有的只会造成伤害,但充其量是误导)。
    • 真实的&gt;&gt;。带空格的文件名呢?我正在使用 RS=>,然后在换行符上拆分。
    • 这是system("mv")。您需要引用该字符串中将填充文件名的位置。
    【解决方案6】:

    绝对是awk 的工作:

    $ awk '$6==207||$6==208||$6==211 { print }' 1bn5.txt
    ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C 
    ATOM    421  CA  SER A 207      68.627 -29.819   8.533  1.00 50.79           C  
    ATOM    615  H   LEU B 208       3.361  -5.394  -6.021  1.00 10.00           H
    ATOM    616  HA  LEU B 211       2.930  -4.494  -3.302  1.00 10.00           H 
    
    $ awk '$6==92||$6==88 { print }' 1B24.txt
    ATOM    631  CG  MET B  88      -0.828  -0.688  -7.575  1.00 10.00           C
    ATOM    632  SD  MET B  88      -2.380  -0.156  -6.830  1.00 10.00           S
    ATOM    643  N   ALA B  92      -1.541  -4.371  -5.366  1.00 10.00           N 
    

    重定向以保存输出:

    $ awk '$6==207||$6==208||$6==211 { print }' 1bn5.txt > output.txt
    

    【讨论】:

      【解决方案7】:

      我认为您不能仅使用 sed 单独执行此操作。您需要一个循环来读取您的文件 data.txt。例如,使用bash 脚本:

      #!/bin/bash
      
      # First remove all possible "problematic" characters from data.txt, storing result
      # in data.clean.txt. This removes everything except A-Z, a-z, 0-9, leading >, and ..
      sed 's/[^A-Za-z0-9>\.]//g;s/\(.\)>/\1/g;/^$/d' data.txt >| data.clean.txt
      
      # Next determine which lines to keep:
      cat data.clean.txt | while read line; do
         if [[ "${line:0:1}" == ">" ]]; then
            # If input starts with ">", set remainder to be the current file
            file="${line:1}"
         else
            # If value is in sixth column, add "keep" to end of line
            # Columns assumed separated by one or more spaces
            # "+" is a GNU extension, so we need the -r switch
            sed -i -r "/^[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +$line +/s/$/keep/" $file
         fi
      done
      
      # Finally delete the unwanted lines, i.e. those without "keep":
      # (assumes each file appears only once in data.txt)
      cat data.clean.txt | while read line; do
         if [[ "${line:0:1}" == ">" ]]; then
            sed -i -n "/keep/{s/keep//g;p;}" ${line:1}
         fi
      done
      

      【讨论】:

      • 感谢您的回答。当我运行您的代码时,我收到错误“预期一元运算符”。如何解决这个错误?
      • 在我的环境中工作正常。但stackoverflow.com/a/408980/318716 建议将[ 更改为[[ 可能会有所帮助,将] 更改为]]。这行得通吗?
      • 更好的建议:将两个${line:0:1} 替换为"${line:0:1}"。您的 data.txt 可能有一个空行或类似的内容。
      • 我添加了对 data.txt 的初始清理,以删除除 A-Z、a-z、0-9、前导 > 和 . 之外的所有内容,以防某些无关字符破坏了脚本。跨度>
      • UUOC 并且对于包含以空格开头或结尾的行或包含反斜杠或已经包含单词 keep 的文件将失败。
      猜你喜欢
      • 1970-01-01
      • 2013-02-27
      • 2023-03-05
      • 1970-01-01
      • 2011-05-11
      • 2011-12-21
      • 2020-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多