【问题标题】:Using awk command to compare values on separate lines?使用 awk 命令比较不同行上的值?
【发布时间】:2021-07-24 01:26:33
【问题描述】:

我正在尝试构建一个 bash 脚本,该脚本使用 awk 命令逐行遍历已排序的制表符分隔文件并确定是否:

  1. 该行的字段 1(分子)与下一行相同,
  2. 行的字段 5(链)是字符串“减号”,并且
  3. 下一行的字段 5 是字符串“plus”。

如果这是真的,我想将行中字段 1 和 3 的值添加到文件中,然后将下一行中的字段 4 添加到文件中。对于上下文,排序后,输入文件如下所示:

molecule        gene    start   end     strand
ERR2661861.3269 JN051170.1      11330   10778   minus
ERR2661861.3269 JN051170.1      11904   11348   minus
ERR2661861.3269 JN051170.1      12418   11916   minus
ERR2661861.3269 JN051170.1      13000   12469   minus
ERR2661861.3269 JN051170.1      13382   13932   plus
ERR2661861.3269 JN051170.1      13977   14480   plus
ERR2661861.3269 JN051170.1      14491   15054   plus
ERR2661861.3269 JN051170.1      15068   15624   plus
ERR2661861.3269 JN051170.1      15635   16181   plus

因此,在本例中,脚本应在比较第 4 行和第 5 行时发现语句为真,并将以下行附加到文件中:

ERR2661861.3269      13000   13382

到目前为止我的脚本是:

# test input file
file=Eg2.1.txt.out

#sort the file by 'molecule' field, then 'start' field
sort -k1,1 -k3n $file > sorted_file

# create output file and add 'molecule' 'start' and 'end' headers
echo molecule$'\t'start$'\t'end >> Test_file.txt

# for each line of the input file, do this
for i in $sorted_file
do
    # check to see if field 1 on current line is the same as field 1 on next line AND if field 5 on current line is "minus" AND if field 5 on next line is "plus"
    if  [awk '{if(NR==i) print $1}' == awk '{if(NR==i+1) print $1}'] && [awk '{if(NR==i) print $5}' == "minus"] && [awk '{if(NR==i+1) print $5}' == "plus"];
    
    # if this is true, then get the 1st and 3rd fields from current line and 4th field from next line and add this to the output file
    then
        mol=awk '{if(NR==i) print $1}'
        start=awk '{if(NR==i) print $3}'
        end=awk '{if(NR==i+1) print $4}'
        new_line=$mol$'\t'$start$'\t'$end   
        echo new_line >> Test_file.txt
    fi
done

bash 脚本的第一部分按我的意愿工作,但 for 循环似乎在排序的文件中找不到任何匹配项。是否有人对为什么这可能无法按预期工作有任何见解或建议?

非常感谢!

【问题讨论】:

    标签: bash for-loop awk


    【解决方案1】:

    解释为什么您的代码不起作用

    如需更好地解决您的问题,请参阅karakfa's answer

    bash 中的字符串比较需要 [] 周围的空格

    Bash 解释您的命令 ...

    [awk '{if(NR==i) print $1}' == awk '{if(NR==i+1) print $1}']
    

    ... 作为带有参数{if(NR...==awk{if(NR...] 的命令[awk。在您的普通系统上,没有名为 [awk 的命令,因此这应该会失败并显示错误消息。在[] 之前添加一个空格。

    awk 没有被执行

    [ awk = awk ] 只是比较文字字符串awk。要执行命令并比较它们的输出,请使用[ "$(awk)" = "$(awk)" ]

    awk 缺少输入文件

    awk '{...}' 尝试从标准输入(在您的情况下为用户)读取输入。既然要读取文件,就把它作为参数添加:awk '{...}' sorted_file

    awk '... NR==i ...' 没有引用来自 bash 的 for i ini

    awk 不知道您的 bash 变量。当您在awk 脚本中写入i 时,i 将始终具有默认值0。要将变量从bash 传递到awk,请使用awk -v i="$i" ...。此外,您似乎假设 for i in 会遍历文件的 numbers 行。目前,情况并非如此,请参阅下一段。

    for i in $sorted_file 没有迭代文件 sorted_file

    您将文件称为sorted_file。但是当您编写$sorted_file 时,您引用了一个之前未声明的变量。未声明的变量扩展为空字符串,因此您什么都不迭代。
    您可能想写for i in $(cat sorted_file),但这会遍历文件内容,而不是行号。此外,未引用的$() 可能会导致无法预料的问题,具体取决于文件内容。要遍历行号,请使用for i in $(seq $(wc -l sorted_file))

    【讨论】:

    • 很好的解释——在我的应用程序中正确使用 awk 时,我还差得很远。非常感谢您的解释!
    【解决方案2】:

    这将完成最后一步,假设数据按键排序并且“减号”在“加号”之前。

    $ awk 'NR==1{next} $1==p && f && $NF=="plus"{print p,v,$3} {p=$1; v=$3; f=$NF=="minus"}' sortedfile
    
    ERR2661861.3269 13000 13382
    

    注意awk有一个隐式循环,不需要强制它在外部迭代。

    【讨论】:

    • 用这个替换我的for循环完全符合我的期望。非常感谢!
    【解决方案3】:

    在使用 awk 或任何其他程序比较流中的相邻行时,最好的办法是存储该行的相关数据,然后在读取两行后立即进行比较,如下所示awk 脚本。

    molecule = $1
    strand = $5
    if (molecule==last_molecule)
      if (last_strand=="minus")
        if (strand=="plus")
          print $1,end,$4
    last_molecule = molecule
    last_strand = strand
    end = $3
    

    【讨论】:

    • 好的,谢谢,我也可以看到这个脚本的逻辑了。有很多好的建议让我记住!
    • @AndyHudson 如果你觉得它有用,你应该点赞!
    • 谢谢,我很想这样做,但因为我是这个网站的新手,所以我没有足够的声誉来被允许这样做。当我有能力时会回来更新。再次感谢您的帮助!
    【解决方案4】:

    您基本上在要点中描述了一个原型程序:

    1. 该行的字段 1(分子)与下一行相同,
    2. 行的第 5 字段(链)是字符串“减号”,并且
    3. 下一行的字段 5 是字符串“plus”。

    您拥有使用 Perl、awk、ruby 等编写程序所需的一切。

    这里是 Perl 版本:

    perl -lanE 'if ($l0==$F[0] && $l4 eq "minus" && $F[4] eq "plus") {say join("\t", @F[0..2])}
                $l0=$F[0]; $l4=$F[4];' sorted_file
    

    -lanE 部分启用自动拆分(如awk)和自动循环并将文本编译为程序;

    if ($l0==$F[0] && $l4 eq "minus" && $F[4] eq "plus") 测试您的三个要点(但 Perl 是基于 0 的索引数组,因此“第一”是 0,第五是 4

    $l0=$F[0]; $l4=$F[4]; 保存字段 1 和 5 的当前值以比较下一个循环。 (awkperl 都允许与不存在的变量进行比较;因此为什么 $l0$l4 可以在第一次通过此循环之前进行比较。大多数其他语言,例如 ruby 他们需要先初始化...)

    这是一个awk 版本,本质上是相同的程序:

    awk '($1==l1 && l5=="minus" && $5=="plus"){print $1 "\t" $2 "\t" $3}
         {l1=$1;l5=$5}' sorted_file 
    

    Ruby 版本:

    ruby -lane 'BEGIN{l0=l4=""}
    puts $F[0..2].join("\t") if (l0==$F[0] && l4=="minus" && $F[4]=="plus")
    l0=$F[0]; l4=$F[4]
    ' sorted_file
    

    全部三个打印:

    ERR2661861.3269 JN051170.1  13382
    

    我的意思是,您非常有效地理解并陈述了您要解决的问题。 这是解决问题的 80%! 然后您需要的只是每种语言的惯用细节。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-17
      • 2011-09-22
      • 2018-06-01
      相关资源
      最近更新 更多