【问题标题】:How to remove only the first occurrence of a line in a file using sed如何使用 sed 仅删除文件中第一次出现的行
【发布时间】:2014-07-05 00:00:50
【问题描述】:

我有以下文件

titi
tata
toto
tata

如果我执行

sed -i "/tat/d" file.txt

它将删除所有包含tat 的行。命令返回:

titi
toto

但我只想删除包含tat的文件中出现的第一行:

titi
toto
tata

我该怎么做?

【问题讨论】:

  • 那么你想删除所有第一次出现至少两次的行吗?
  • @fedorqui 仅第一次出现,而不是所有出现的行
  • 但只是针对“tata”还是针对所有这些?意思是,您想删除第一次出现的“tata”,仅此而已,或者您想删除至少出现两次的所有行的所有第一次出现?
  • @fedorqui 我更新了问题

标签: linux bash shell sed


【解决方案1】:

你可以使用双地址形式:

sed '0,/tat/{/tat/d;}' inputfile

这将删除模式的第一次出现。

引用info sed:

 A line number of `0' can be used in an address specification like
 `0,/REGEXP/' so that `sed' will try to match REGEXP in the first
 input line too.  In other words, `0,/REGEXP/' is similar to
 `1,/REGEXP/', except that if ADDR2 matches the very first line of
 input the `0,/REGEXP/' form will consider it to end the range,
 whereas the `1,/REGEXP/' form will match the beginning of its
 range and hence make the range span up to the _second_ occurrence
 of the regular expression.

【讨论】:

  • 我认为这是最好的答案。谢谢
  • @jaypal 我会从info sed 获取答案。
  • @devnull 哦,谢谢,我不知道有这样的好处。为那条信息+1! :)
  • 或更短的sed '0,/tat/{//d;}' inputfile
  • @SLePort 挑剔但 sed '0,/tat/{//d}' inputfile 更短 ;-) 但是 1000 倍感谢您教我有关 // 构造的知识。
【解决方案2】:

如果你可以使用awk,那么就可以了:

$ awk '/tata/ && !f{f=1; next} 1' file
titi
toto
tata

要将结果保存在当前文件中,请执行以下操作

awk '...' file > tmp_file && mv tmp_file file

说明

让我们在tata 第一次匹配时激活一个标志并跳过该行。从那一刻起,不要跳过这些行。

  • /tata/ 匹配包含字符串 tata 的行。
  • {f=1; next} 将标志 f 设置为 1,然后跳过该行。
  • !f{} 如果设置了标志 f,则跳过此块。
  • 1,作为 True 值,执行默认的 awk 操作:{print $0}

另一种方法,Tom Fenech

awk '!/tata/ || f++' file

|| 代表 OR,所以这个条件为真,因此只要发生这些情况,就会打印该行:

  • tata 在该行中找不到。
  • f++ 是真的。这是棘手的部分:第一次 f 默认为 0,所以第一次 f++ 将返回 False 并且不打印该行。从那一刻起,它将从一个整数值递增,并将为 True。

【讨论】:

  • 或者为了获得最佳的可读性:awk '!/tata/ || f++' file
  • 哦,这很聪明,@TomFenech!我会考虑编辑我的答案以包含这种方法。
  • 这几乎不是最自我记录的方法,但无论如何,继续吧:)
  • 感谢您的回答
  • @TomFenech 我确实看到awk '!/tata/ || f++'awk '!/tata/ || f--' 执行相同的操作,如果您想删除第二次点击,awk -v f=1 '!/tata/ || f--' and third hits awk -v f=2 '!/tata/ || f--' 等
【解决方案3】:

这是一般的方法:

$ cat file
     1  titi
     2  tata
     3  toto
     4  tata
     5  foo
     6  tata
     7  bar
$
$ awk '/tat/{ if (++f == 1) next} 1' file
     1  titi
     3  toto
     4  tata
     5  foo
     6  tata
     7  bar
$
$ awk '/tat/{ if (++f == 2) next} 1' file
     1  titi
     2  tata
     3  toto
     5  foo
     6  tata
     7  bar
$
$ awk '/tat/{ if (++f ~ /^(1|2)$/) next} 1' file
     1  titi
     3  toto
     5  foo
     6  tata
     7  bar

请注意,使用上述方法,您可以跳过任何您喜欢的 RE 出现(第 1、第 2、第 1 和第 2 次等),并且您只需指定一次 RE(而不是必须重复它一些替代解决方案)。

清晰、简单、明显、易于维护、可扩展等......

【讨论】:

  • 迄今为止最好的答案,其他的甚至都不适合我。在这方面有很大的灵活性!
【解决方案4】:

这是使用sed 的一种方法:

sed ':a;$!{N;ba};s/\ntat[^\n]*//' file
titi
toto
tata

【讨论】:

    【解决方案5】:

    这可能对你有用(GNU sed):

    sed '/pattern/{x;//!d;x}' file
    

    正常打印除包含该模式的行以外的所有行。否则,如果该行包含该模式并且保留空间不包含(第一次出现),则删除该行。

    【讨论】:

      【解决方案6】:

      您可以找到第一个与grep 匹配的行号并将其传递给sed 进行删除。

      sed "$((grep -nm1 tat file.txt || echo 1000000000:) | cut -f 1 -d:) d" file.txt
      

      grep -n 结合cut 查找要删除的行号。 grep -m1 确保最多找到一个行号。 echo 处理不匹配的情况,以免返回空结果。 sed "[line number] d" 删除该行。

      【讨论】:

      • 请将您的两个答案合并为一个答案,并用标题或粗体标题分隔,说明两种方法,以及每种方法的优缺点。仅代码的答案质量低下,可能会被删除,至少应该有 1 或 2 句话来解释您要解决的问题,用外行术语来说它在做什么,以及可选的其他陷阱。请参阅:meta.stackoverflow.com/questions/345719/… 解决这些问题,我将删除评论并投反对票。
      猜你喜欢
      • 2014-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-26
      • 1970-01-01
      • 2018-11-24
      • 2015-06-04
      • 1970-01-01
      相关资源
      最近更新 更多