【问题标题】:How do I add a word before the last character in the last occurrence of a string with sed?如何使用 sed 在最后一次出现的字符串的最后一个字符之前添加一个单词?
【发布时间】:2019-07-27 19:21:11
【问题描述】:

我正在尝试使用 sed 查找文件中最后一次出现的字符串,然后在该字符串的“中间”添加一些内容。我只想搜索字符串的一部分,因为整个字符串可能不同(我要添加的字符串是一个可以具有不同值的变量)。我总是想在字符串的最后一个字符之前添加。

例如:

Thing=name
Word=value
Stuff=tree
Word=anotherValue!
This=that

我想在最后一次出现的 "Word=":

的最后一个字符之前添加一个单词
Thing=name
Word=value
Stuff=tree
Word=anotherValueOkay!
This=that

我知道tac | sed <?> | tac是如何处理最后一次出现的字符串,但是如何在最后一次出现的字符串中,在字符串的最后一个字符之前添加一个单词呢?

【问题讨论】:

  • 最简单的方法:反转文件中的行,匹配第一次出现的sed,反转文件中的行。如果您特别询问 sed,您可能希望将 sed 作为标签添加到问题中。或者是否可以接受任何 bash-list 解决方案?是的,tac | sed |塔克。我认为它仅在 sed 中可行。
  • 所以使用 tac,然后是 sed,然后是 tac?我希望特别是 sed。我会确保添加该标签,谢谢。好的,我试试,谢谢。
  • @KamilCuk 我编辑了我的问题,以便更清楚地了解我想要做什么。 tac | sed > | tac 是如何找到字符串的最后一次出现,但是如何按照我上面描述的方式添加到该字符串?

标签: bash sed


【解决方案1】:

下面嵌入了sed 脚本的bash 脚本:

cat <<EOF |
Thing
Wod
Stuff1
Wod
Stuff2
EOF
sed -n '
  # omit all the initial lines until
  # Wod is encountered
  /^Wod$/!{
    p
    d
  }

  : a
  {
    # Hold the line
    H
    # read next line
    n
    # if its the Wod line
    /Wod/{
      # that means that the Wod in hold space
      # is not the last one
      # print the hold space as it is and remove it
      x
      s/^\n//
      p
      s/.*//
      x
    }
    # if this is the last line
    ${
      # that means the Wod in hold space is the last one
      x
      G
      s/^\n//
      # So substitute it for Word
      s/^Wod/Word/
      # and print
      p
      # and quit
      q
    }

    # loop
    b a
  }
'

产生以下输出:

Thing
Wod
Stuff1
Word
Stuff2

解释以 cmets 为单位。在编写 cmets 时,我假设读者熟悉 sed 中的模式/保持空间概念。

我认为第一部分只是打印初始行,直到“Wod”可以通过检查在保持空间中是否已经存在一个 Wod 单词来删除/与其余代码合并,但它可以按原样工作。

@edit 对于 OPs 问题中的更新示例:

cat <<EOF |
Thing=name
Word=value
Stuff=tree
Word=anotherValue!
This=that
EOF
# this is the same script as above
# only comments are removed and different regexes are used to substitue and match the line. `Wod` is matched above, here `^Word=` is matched, etc.
sed -n -E '/^Word=/!{p;d;};:a;H;n;/^Word=/{x;s/^\n//;p;s/.*//;x;};${x;G;s/^\n//;s/^(Word=[^\n]*)/Word=anotherValueOkay!/;p;q;};ba'

我匹配^Word=,不知道这里应该是什么预期模式。然后我替换Word=[^\n]*,即。 Word= 部分直到与 Word=AnotherValueOkay! 行结束。

【讨论】:

  • 原谅我的无知,但我并不精通sed。这个脚本是否假设“Wod”的下一个实例将在第一个实例之后正好 1 行?另外,如果我没记错的话,“Wod”的最后一个实例正在替换为“Word”,对吗?我的问题是我要查找的字符串的最后一次出现总是以相同的几个字符开头,但整个字符串并不总是每次都包含所有相同的字符。如果我不知道字符串的全部内容是什么,我还能替代吗?
  • 1.不,它不假设。您可以在“Wod”之间有尽可能多的行,它们最终应该在保持空间中。 2. 嗯,这就是目标。 3. 所以改变正则表达式。例如,第二个片段使用 ^Word=[^\n]* 从行首到行尾进行匹配。注意,匹配直到\n newlnie 字符,做。因为多行被加载到模式空间中,所以执行 ex. Word=.* 将匹配所有行。 \n 也是一个 gnu 扩展,对于 posix sed,在 s 命令内的模式匹配中使用文字换行符。
  • tutorialspoint 似乎适用于“Wod”之间的多行并将整个最后的“Wod”行替换为其他内容。
  • 所以这将允许我将“Okay”添加到“Word=anotherValue!”即使我不知道“anotherValue”部分实际上是什么?我在这里感到困惑的原因是因为每次调用此脚本时,该行的“anotherValue”部分可能会有所不同。因此,如果我们不知道整行实际上是什么,我们如何将“Word=”的最后一个实例替换为添加了新单词的全新行?
  • 您只需将行与^Word=[^\n]* 匹配...看来您需要重新编写正则表达式。我建议从regex crosswords 开始,这是一种很酷的学习方式。然后向上移动到伟大的sed introduction 以了解模式并保持空间。 sed 正在完成,但是任何涉及一次处理多于一行的任务都不值得花时间,并且在 sed 中很快变得非常非常复杂。
【解决方案2】:

正如 Kamil Cuk 的回答所示,使用 sed 非常困难。

如果可以配合sed使用其他工具,那就简单多了。

这是一个例子:

获取Word=最后出现的行号。 = 操作将匹配行的行号打印到标准输出。然后使用tail 只保留最后一行号。

line=$(sed -n -e '/^Word=/=' sample.txt | tail -1)

然后在后续命令中使用该行号,将替换仅应用于该行:

sed -e ${line}'s/.$/!&/' sample.txt

【讨论】:

  • 感谢您的回答。我的挣扎是我不知道整条线是什么;只是开始。因此,我无法替换整行,因为我不知道整行是什么。
  • 我上面显示的替换只改变了最后一个字符,插入了一个“!”在它之前。这条线的其余部分,不管它是什么,都不要管。
  • 好的,所以当我尝试使用它时,它在替换之前一切正常。它不是在最后一个字符之前添加所需的字符串,而是将所需的字符串添加到字符串的末尾。
  • 抱歉,我弄错了;这按预期工作。非常感谢!
【解决方案3】:

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

sed -zE 's/(.*Word[^\n]*)([^\n])/\1Okay\2/' file

使用-z 选项将文件拖入内存,并通过在正则表达式中使用.* 来利用贪婪来查找以Word 开头的最后一个单词。

如果Word 必须在行首,请使用:

sed -zE 's/(.*\nWord[^\n]*)([^\n])/\1Okay\2/' file

另一种选择:

sed -nE '/(.*Word[^\n]*)([^\n])/{h;:a;$x;$s//\1Okay\2/p;n;//x;//p;//!H;ba};p' file

【讨论】:

    猜你喜欢
    • 2017-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-11
    • 2013-07-05
    • 2019-06-13
    相关资源
    最近更新 更多