我的文件中有三列:
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
没有帮助。更好地一次处理整个缓冲区,就像-z
GNU 的选项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
这样我们就不会意外匹配apple10
或apple12
或任何其他可能出现的类似字符串。在输入开始处的每一行都会执行( print p
) 和d
(delete + continue with the next line from the top of the script){ ... }
不是匹配表达式。
该s
命令(替换)针对第一行输入执行做匹配apple1
在行的开头。它只是用我们的 s 替换行末尾的数字字符串4
。
然后是一个标记的部分,它负责通过打印当前行并使用(执行打印和读取)loop
读取下一行来传递未修改的其余数据。 “当前行”将在第一次循环时由命令完成修改。n
n
s
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