【问题标题】:How to compare and substitute strings in different lines in unix如何在unix中比较和替换不同行中的字符串
【发布时间】:2023-04-05 16:29:01
【问题描述】:

我想比较和替换 unix 中不同行中的字符串

例如我有一个文件,每行有两个单词

<a> <b>
<d> <e>
<b> <c>
<c> <e>

如果任何行的第二个单词与任何其他行的第一个单词匹配,则该行的第二个单词应该替换为匹配行的第二个单词,并且它应该迭代直到该行的第二个单词与第一个单词之间没有匹配另一行的

我需要类似的结果

<a> <e>
<b> <e>
<c> <e>
<d> <e>

我是 unix 新手,不知道如何实现它。任何人都可以提出建议或解释我们如何做到这一点

【问题讨论】:

  • 通过“它应该迭代直到没有匹配...”你的意思是它应该比较第 1 行和第 3 行,然后第 4 行等等?
  • 是的。你是对的阿什坎!它应该在所有行中搜索匹配。
  • &lt;a&gt; &lt;b&gt;第一行、&lt;e&gt; &lt;z&gt;第二行和&lt;b&gt; &lt;e&gt;第三行作为输入,你想要的输出是什么?
  • desires 输出将是
  • 现在第一行第二个单词和第二行第一个单词相等,不应该换成吗?即成为 还是有其他条件?

标签: regex unix awk sed


【解决方案1】:

这显然是递归下降解决方案的一个案例:

$ cat tst.awk
function descend(node) {return (map[node] in map ? descend(map[node]) : map[node])}
{ map[$1] = $2 }
END { for (key in map) print key, descend(key) }

$ awk -f tst.awk file
<a> <e>
<b> <e>
<c> <e>
<d> <e>

如果您的输入中可能存在无限递归,这里的方法将在递归开始之前将最后一个节点打印为第二个字段,并在其旁边放置一个“*”,以便您知道它正在发生:

$ cat tst.awk
function descend(node,  child, descendant) {
    stack[node]
    child = map[node]
    if (child in map) {
        if (child in stack) {
            descendant = node "*"
        }
        else {
            descendant = descend(child)
        }
    }
    else {
        descendant = child
    }
    delete stack[node]
    return descendant
}
{ map[$1] = $2 }
END { for (key in map) print key, descend(key) }

.

$ cat file
<w> <w>
<x> <y>
<y> <z>
<z> <x>
<a> <b>
<d> <e>
<b> <c>
<c> <e>

$ awk -f tst.awk file
<w> <w>*
<x> <z>*
<y> <x>*
<z> <y>*
<a> <e>
<b> <e>
<c> <e>
<d> <e>

如果您需要输出顺序与输入顺序匹配和/或打印重复的行两次,请将脚本的底部 2 行更改为:

{ keys[++numKeys] = $1; map[$1] = $2 }
END {
    for (keyNr=1; keyNr<=numKeys; keyNr++) {
        key = keys[keyNr]
        print key, descend(key)
    }
}

【讨论】:

  • “这显然是递归下降解决方案的一个案例” 我想我同意这一点,但我对 作为输入
  • @Ed Morton 看起来很有趣。你能解释一下或建议任何参考吗
  • @Ed Morton,+1,在这种情况下,我的答案将无限循环,我想知道如何解决这个问题。如果它可能发生在 OP 的输入中,则另一个改进是处理重复的行。
  • @Ashkan 现在它通过忽略重复行来处理它们,我只是在我的答案末尾添加了一个替代方法来打印它们。至于重复的$1s - 如果$1 in map 可以工作,只需报告并跳过它们。在这些情况下,我们需要来自 OP 的一些关于所需行为的输入。
  • 这个是守门员!已收藏。谢谢@EdMorton!
【解决方案2】:

Perl 的救援:

#!/usr/bin/perl
use warnings;
use strict;

my (@buff);
sub output {
    my $last = pop @buff;
    print map "$_ $last\n", @buff;
    @buff = ();
}

while (<>) {
    my @F = split;
    output() if @buff and $F[0] ne $buff[-1]; # End of a group.
    push @buff, $F[0] unless @buff;           # Start a new group.
    push @buff, $F[1];
}

output();                                     # Don't forget to print the last buffer.

解释:逐行读取输入。保留要打印的单词列表,其中包含相同的第二个单词。如果第一个单词与上一行的第二个单词不同,则打印缓冲输出。

【讨论】:

  • 嗨 Choroba ..感谢您的代码和解释!它是否仅与前一行进行比较(第一个单词与前一行的第二个单词不同)。如果它比较所有行会很好。我怎样才能做到这一点。我不知道 perl
  • @shalini:试试看。将其保存到文件compare.pl,运行perl compare.pl input 并查看。
  • 嗨 Choroba 感谢您的建议。我试过你的代码。它不适用于 。它给出了相同的输出!
  • @shalini:你期望什么输出?
  • @shalini 根据您的描述,是否应该提供相同的输出。 If second word of the line1 matched with first word of line2 then second word of line1 should be replaced with second word of line2 and it should iterate until there is no match between second word of the line1 with first word of line2。请更新您的问题以显示您真正需要的内容。
【解决方案3】:
awk '{i++;a[i]=$1;b[i]=$2;next}
      END{
            for(i=1;i in a;i++)
            {
              f=1;
              while (f==1)
              {
                f=0;
                for(j=i+1;j in a;j++)
                {
                  if(b[i]==a[j])
                  {
                    b[i]=b[j];
                    f=1;
                  }
                }
              }
            }
            for(i=1;i in a;i++)
            {
              print a[i],b[i];
            }
          }' input.txt

输入:

<a> <b>
<d> <e>
<b> <c>
<c> <e>

输出:

<a> <e>
<d> <e>
<b> <e>
<c> <e>

输入:

<a> <b>
<e> <z>
<b> <e>

输出:

<a> <z>
<e> <z>
<b> <e>


编辑

如果你需要得到

<a> <z>
<e> <z>
<b> <z>

作为第二个输入的输出,您可以更改此行:

if(b[i]==a[j])

到:

if(j!=i&&b[i]==a[j])

还有这个:

for(j=i+1;j in a;j++)

到:

for(j=1;j in a;j++)

另请注意,此代码假定不存在一行的第二个单词等于一行的第一个单词及其第二个单词的情况,即:

<a> <b>
<e> <z>
<b> <b>

在这种情况下,代码的执行将永远不会结束。

【讨论】:

  • @非常感谢您抽出时间来 Ashkan。最后的更改给了我正确的结果。
猜你喜欢
  • 1970-01-01
  • 2016-01-31
  • 1970-01-01
  • 1970-01-01
  • 2022-12-03
  • 1970-01-01
  • 1970-01-01
  • 2013-12-07
  • 1970-01-01
相关资源
最近更新 更多