如何查找第 1 列中最后一次出现的字符串并替换第 3 列中的相应值?

如何查找第 1 列中最后一次出现的字符串并替换第 3 列中的相应值?

我的文件中有三列:

apple1        10109283      20012983
apple1        10983102      10293809
apple1        10293893      2349823049
apple10       109283019     109238901
apple10       192879234     234082034
apple10       234908443     3450983490

我想找到第 1 列中该字符串的最后一次出现(在本例中为第 3 行或第 6 行),并将第 3 列中的相应数字替换为不同的数字。示例(将第 3 行第 3 列替换为 444444444”

apple1        10109283      20012983
apple1        10983102      10293809
apple1        10293893      444444444
apple10       109283019     109238901
apple10       192879234     234082034
apple10       234908443     3450983490

到目前为止,我尝试使用 sed 但它不起作用:

sed '$s/apple1*$/444444444/'

答案1

无需管道的纯sed溶液tac

对于这样的情况,逐行方法sed没有帮助。更好地一次处理整个缓冲区,就像-zGNU 的选项sed一样(您似乎正在使用 linux 和 GNU sed,对于便携式替代品,请参阅本次问答)。

现在您可以利用 的贪婪本质.*:该模式.*apple1将匹配所有内容,包括最后一次出现的apple1,因为所有其他出现的内容都会被 吃掉.*

然后只需添加下一个字段(\s+对于列分隔符,[0-9]+对于第二列和另一列\s+,所有 GNU 扩展正则表达式)并将其括起来,()以便您可以在替换中将其重用为\1.然后在外面添加第三列()以将其替换,结果为

sed -zE 's/(.*\napple1\s+[0-9]+\s+)[0-9]+/\14444444/'

就是这样。

非 GNUsed用户请注意:便携式解决方案不太方便:

sed -E 'H;1h;$!d;x;s/(.*\napple1[[:space:]]+[0-9]+[[:space:]]+)[0-9]+/\14444444/'

答案2

tac file |
awk -v string='apple1' -v replace='444444444' '
    !flag && $1 == string { $3 = replace; flag = 1 }
                          { print }' |
tac

tac该管道首先使用GNU coreutils反转数据中行的顺序。最后一行是第一列是特定字符串的位置,这样更容易找到。

awk命令只是将第一列与给定的字符串进行比较,如果我们尚未进行替换(!flag非零),则一旦在第一列中找到该字符串,我们就会修改第三列。这样做时,我们还设置flag为 1,以便不再进行进一步的替换。

程序的其余部分awk只是打印当前行(包括修改后的行)。

在管道的末尾,我们再次使用 反转行的顺序tac

考虑到问题中的数据,其输出是

apple1        10109283      20012983
apple1        10983102      10293809
apple1 10293893 444444444
apple10       109283019     109238901
apple10       192879234     234082034
apple10       234908443     3450983490

由于第 3 列的修改,修改后的行上的列与其他行的列有点不同。为了使其看起来更好,您可以将结果传递到column -t管道末端的附加阶段。如果这样做,输出将类似于

apple1   10109283   20012983
apple1   10983102   10293809
apple1   10293893   444444444
apple10  109283019  109238901
apple10  192879234  234082034
apple10  234908443  3450983490

列之间有多个空格。


对于sed,它并不像仅替换第一行中字符串出现在第一列中的第三列那么容易(假设我们像上面的管道一样反转数据行)。我们还必须不是即使第一列与我们的字符串匹配,也请替换任何后续行中的第三列。

这是一个sed可以正确执行此操作的编辑脚本(可能有多种可行的变体):

/^apple1\>/ ! {
        p
        d
}

s/[[:digit:]]*$/444444444/

:loop
n
$ ! b loop

第一部分负责在输入开头打印apple1与第一列不匹配的行。表达式中的与\>单词的结尾匹配,apple1这样我们就不会意外匹配apple10apple12或任何其他可能出现的类似字符串。在输入开始处的每一行都会执行( print p) 和d(delete + continue with the next line from the top of the script){ ... }不是匹配表达式。

s命令(替换)针对第一行输入执行匹配apple1在行的开头。它只是用我们的 s 替换行末尾的数字字符串4

然后是一个标记的部分,它负责通过打印当前行并使用(执行打印和读取)loop读取下一行来传递未修改的其余数据。 “当前行”将在第一次循环时由命令完成修改。nns

loop如果我们还没有到达输入的最后一行,那么最后一行会分支回标签。

运行示例:

$ tac file | sed -f script.sed | tac
apple1        10109283      20012983
apple1        10983102      10293809
apple1        10293893      444444444
apple10       109283019     109238901
apple10       192879234     234082034
apple10       234908443     3450983490

答案3

尝试使用下面的命令,效果很好

for i in `awk '{print $1}' file1| awk '{if(!seen[$1]++)print }'`; do j=`awk -v i="$i" '$1 == i {print $0}' file1| awk '{print NR}'| sed -n '$p'`; awk -v i="$i" '$1 == i {print $0}' file1|awk -v i="$i" -v j="$j" 'NR==j{$3="444444444"}1'; done

相关内容