如何使用 sed 替换字符串中的字符串?

How to use sed to replace a string within a string?

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

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>

但我知道 "some option here" 部分。有什么线索吗?

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 到 select 可能需要修改的文件,并仅通过管道将这些文件名发送到 sed -i 以进行更新。

如果 timestamp/overwriting 问题不重要,请考虑 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 /

      这 selects 行以 filename1 开头,后跟 space。这确保我们既不匹配 sfilename1 也不匹配 filename12.

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

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

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

尝试以下操作:

Linux:

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

OSX / BSD:

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

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

  • exec ... + 是一项 find 功能,它使用尽可能多的匹配路径调用指定的命令,可以在单个命令行中显示,可能 导致 多次 调用,但 通常 仅导致 1,这使得调用 高效.

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

  • 请注意,如果有 metadata.txt 个文件 而不是 的行以 filename1 开头,则它们是 仍然 重写,因为 sed-i 选项盲目地 "updates" 给定的输入文件(读取:创建一个新文件,然后替换原始文件)。如果不需要,请考虑 .

POSIX-合规说明:

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