【问题标题】:How to use sed to replace a string within a string?如何使用 sed 替换字符串中的字符串?
【发布时间】:2015-05-02 03:24:42
【问题描述】:

我阅读了以下文章“Using grep and sed to find and replace a string”,但如何将其扩展为链接多个 grep。例如我有以下目录/文件结构

dir1/metadata.txt
dir2/metadata.txt

dir1/metadata.txt 有

filename1 '= 1.0.0'
filename2 '= 1.0.0'

dir2/metadata.txt 有

filename1     '= 1.0.0'
long_filename '= 1.0.0'

也就是说,dir1/metadata.txt 和 dir2/metadata.txt 都包含“filename '1.0.0'”,但是每个文件中“filename”和“'1.0.0'”之间的空格是不同的.

现在我想在所有 metadata.txt 文件中将 filename1 的关联版本替换为“2.0.0”,因此生成的文件看起来像...

dir1/metadata.txt 有

filename1 '= 2.0.0'
filename2 '= 1.0.0'

dir2/metadata.txt 有

filename1     '= 2.0.0'
long_filename '= 1.0.0'

我在努力

find . -name metadata.txt | xargs grep filename1 | sed -i "s/1\.0\.0/2.0.0/g" <some option here>

但我知道“这里的一些选项”部分。有什么线索吗?

【问题讨论】:

  • 您需要更改所有*filename*(例如filename2long_filename)还是只更改filename1
  • tivn:只有文件名 1。 filename2 和 long_filename 保持不变
  • 庇护所:你的命令只会改变一个文件
  • sed 不能对字符串进行操作,但请参阅stackoverflow.com/questions/29613304/… 以获得解决方法。
  • @EdMorton:总体来说不错,但这里的问题不是如何一般地sed替换字符串,而是如何将findsed结合起来(使用 specific 搜索和替换字符串,OP 已经为其提供了所需的转义)。

标签: bash sed grep


【解决方案1】:

尝试以下方法:

Linux:

find . -name metadata.txt \
  -exec sed -i "s/^\(filename1[[:space:]]\{1,\}'= \)1\.0\.0/\12.0.0/" {} +

OSX/BSD:

find . -name metadata.txt \
  -exec sed -i '' "s/^\(filename1[[:space:]]\{1,\}'= \)1\.0\.0/\12.0.0/" {} +

注意:需要特定于平台的命令的唯一原因是 GNU sedBSD sed 解释了 非标准 -i 选项,它指定了 后缀 用于原始文件的可选备份,不同的是:GNU sed 考虑-i 的选项参数可选,而BSD sed 认为它强制,需要一个 explicit 参数来指定空字符串(表示希望 创建备份文件)

  • exec ... + 是一个find 功能,它调用指定命令的匹配路径与单个命令行中的匹配路径一样多,可能导致多个 em> 个调用,但 通常 只导致 1,这使得调用 高效

  • "s/\(filename1[[:space:]]\{1,\}'= \)1\.0\.0/\12.0.0/" 是符合 POSIX 的 sed 脚本,匹配行首的文字 filename1,后跟可变数量的空格 ([[:space:]]\{1,\}),后跟文字 '= 1.0.0 ,并将1.0.0. 替换为2.0.0

  • 请注意,如果有metadata.txt 文件没有有以filename1 开头的行,它们仍然被重写,因为sed' s -i 选项盲目地“更新”给定的输入文件(阅读:创建一个新文件,然后替换原始文件)。如果不希望这样做,请考虑John1024's answer

POSIX 合规说明:

  • find-exec primary 的 -exec ... + 变体自 2001 年以来一直是 POSIX 的一部分(POSIX.1-2001 / IEEE Std 1003.1-2001 / SUS v3 - 请参阅 http://pubs.opengroup.org/onlinepubs/009695399/;谢谢,@JonathanLeffler )
  • 相比之下,sed-i 选项就地更新 POSIX 兼容的 - 所以你可能需要解决这个问题。

【讨论】:

  • 使用+ 代替\;?
  • 根据Apple's man find,其当前版本的find支持{} +。当然,也会有其他或更旧的 BSD 系统不支持它。
  • 谢谢,@John1024:find-exec .... + POSIX 兼容的(参见 pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html),但 sed-i 不是.
  • @mklement0 是的,非常好。 GNU 和 BSD 版本都加 1。
  • 是的:POSIX 2004 文档的“标题页”说:摘要: 2004 年版包含第 1 号技术勘误和技术勘误 2 解决自 2001 版批准以来发现的问题。这主要是由于解决了合并 Base 文件所引发的集成问题。 因此,有争论的余地是,没有时间将+ 添加到find 的制造商已经拥有了整整 13 年( 2002-2014) 以及 2001 年和 2015 年的部分内容来解决问题。
【解决方案2】:
find . -name metadata.txt -exec grep -l --null filename1 {} + | xargs -0 sed -i "/^filename1 /{s/'= 1\.0\.0'/'= 2.0.0'/;}"

sed -i 将更新它处理的每个文件的时间戳,无论它是否更改文件的内容。这是因为,在操作中,sed -icreates a new file for each file processed and then overwrites the old file with the new file。为了限制这一点,上面的代码使用grep 仅选择可能需要修改的文件,并仅通过管道将这些文件名发送到sed -i 以进行更新。

如果时间戳/覆盖问题不重要,请考虑mklement0's answer,它消除了对管道的需求,从而简化了命令。

工作原理

  • find . -name metadata.txt -exec grep -l --null filename1 {} +

    这会生成名称为metadata.txt 的文件列表,其中还包含filename

    --null 告诉grep 用 NUL 字符分隔文件名。

  • xargs -0 sed -i "/^filename1 /{s/'= 1\.0\.0'/'= 2.0.0'/;}"

    这适用于sed -i 就地更改名称由上述find 命令返回的文件。

    更详细的:

    • /^filename1 /

      这将选择以filename1 开头的行,后跟一个空格。这确保我们既不匹配 sfilename1 也不匹配 filename12

    • s/'= 1\.0\.0'/'= 2.0.0'/

      这会更改所选行的版本号。 (这里假设等号后面只有一个空格。如果这个假设不正确,我们可以很容易地改变它。)

    xargs-0 选项告诉它期望其输入是 NUL 分隔的文件名列表。即使文件名包含空格、换行符或其他难读字符,这也使管道安全。

【讨论】:

  • 请注意,-exec 处理文件名中的空格,xargs 不会。您可以使用 GNU 工具链通过使用 grep -lZxargs -0 来解决此问题,以便文件名以空字节(而不是换行符)终止。或者,您可以在-exec 选项中执行sed。使用-exec 的缺点是它可能会编辑一个不包含filename1 的文件。即使有数千个文件要处理,这也不太重要,除非有理由不冒险修改文件的“最后更改时间”,除非确实发生了变化。
  • “可能会修改不需要修改的文件”的观察适用于其他答案。我喜欢更简单的 'match the marker;替换 sed 脚本中标记行'操作上的相关文本。我不明白为什么人们坚持使用单行来编写 shell 脚本——“单行”在 APL 中是一个贬义词。
  • @JonathanLeffler 谢谢。我更新了答案以包括-Z/-0。我保留了 grep-to-sed 管道,因为last changed time 问题确实让那些不期待它的用户感到惊讶/困惑。另外,我发现编写良好的 APL 脚本非常易读。我不介意 shell 工具是否由具有 Ken Iverson 眼光的人重写以保持逻辑一致性。
  • 请注意,BSD grep 有一个 -Z 选项,但它与 GNU grep -Z 完全不同:它使它像 zgrep 一样工作(因此它也搜索压缩文件)。
  • @mklement0 我将答案更新为--null,提到了时间戳问题,并链接回您的答案。
猜你喜欢
  • 2012-10-09
  • 2021-03-01
  • 1970-01-01
  • 2015-01-13
  • 2020-03-13
  • 2020-07-28
  • 2016-06-07
  • 2017-02-08
  • 2020-02-15
相关资源
最近更新 更多