【问题标题】:Using grep and sed to find and replace a string使用 grep 和 sed 查找和替换字符串
【发布时间】:2011-09-04 22:19:08
【问题描述】:

我正在使用以下内容递归搜索目录以查找特定字符串并将其替换为另一个:

grep -rl oldstr path | xargs sed -i 's/oldstr/newstr/g'

这工作正常。唯一的问题是,如果字符串不存在,那么 sed 会失败,因为它没有得到任何参数。这对我来说是个问题,因为我使用 ANT 自动运行它并且构建失败,因为 sed 失败。

有没有办法让它在找不到字符串的情况下防故障?

我对我可以使用的单行简单解决方案感兴趣(不一定与 grepsed 一起使用,而是使用此类常见的 unix 命令)。

【问题讨论】:

  • 我想让它尽可能简单的原因是因为我的脚本连接到远程服务器并使用 SSH 运行这一行。如果我为此使用 shell 脚本,我必须先将 shell 脚本复制到服务器,然后在那里运行它。我试图避免它并保持简单。谢谢。
  • 来自谷歌,我正在寻找问题中给出的确切字符串! (我没有遇到 OP 遇到的问题)

标签: linux shell unix sed grep


【解决方案1】:

您可以将find-exec 直接用于sed,而不是首先使用grep 定位oldstr。它的效率可能有点低,但这可能并不重要。这样,sed 替换会在find 列出的所有文件上执行,但如果oldstr 不存在,它显然不会对其进行操作。

find /path -type f -exec sed -i 's/oldstr/newstr/g' {} \;

【讨论】:

  • @Vlad:是的,因为您为每个文件运行单独的sed,而不是让xargs 批处理它们。也就是说,除非您谈论的是数千个小文件,否则您不太可能注意到差异。
  • @geekosaur: 哦,对了.. 我还没有想过 shell 应该做多少个 execs。好点子!
  • 我的问题是:每个文件的时间戳都会被重置为“现在”,就好像它们都被“触摸”一样,即使文件中没有发生任何编辑。
  • 我已经看到了这个“Hello world”示例,它使用 find 到处都是,但没有任何限制它在每个文件上运行 sed,包括二进制文件。我还没有找到一个使用管道的工作示例,并且我尝试了多种方法来破解非常有限的实用程序的简单版本。
  • @JerryMiller 您当然可以将find 限制为特定的文件名模式,这是我经常做的事情:find -name "*.c" -o -name "*.h" -type f -exec sed... 会修改所有 .c 和 .h 文件,例如...排除二进制文件,它可以变得更复杂:unix.stackexchange.com/questions/46276/…
【解决方案2】:

您的解决方案没问题。只能这样尝试:

files=$(grep -rl oldstr path) && echo $files | xargs sed....

所以只有当 grep 返回0 时才执行xargs,例如在某些文件中找到该字符串时。

【讨论】:

  • 这是一个很好的被低估的解决方案!这就是我需要在 grep 的文件输出列表上执行 sed 并避免错误:“无法读取....没有这样的文件或目录”。感谢您的回答!
  • 同意@Leopoldo。这看起来是最 Posixy 和可移植的(对于像 Solaris 这样的操作系统)和副作用最小的一个(不触及每个文件 mtime 等)。 files=$(grep -rl oldstr path | cut -f 1 -d ':' | sort | uniq) 应该建立一个较小的列表,其中文件只列出一次。
【解决方案3】:

标准xargs没有好办法;您最好按照其他人的建议使用find -exec,或者将sed 包装在一个脚本中,如果没有参数,该脚本不会执行任何操作。 GNU xargs--no-run-if-empty 选项,而 BSD / OS X xargs-L 选项,看起来它应该做类似的事情。

【讨论】:

  • 谢谢,我尝试使用 --no-run-if-empty 但它仍然返回非零代码(返回 1),这也会触发我的构建失败。 find 命令的通用性和普遍性如何?
  • find -exec 回到 UNIX 研究版第 7 版;它应该可以在安装了find 的任何地方工作。
  • +1 我很惊讶xargs -r 修复在更多答案中没有提到。对于这个有限的用例,它既简单又足够。
【解决方案4】:

我接受了弗拉德的想法并稍作改动。而不是

grep -rl oldstr path | xargs sed -i 's/oldstr/newstr/g' /dev/null

产生的结果

sed: couldn't edit /dev/null: not a regular file

我正在与远程服务器建立 3 个不同的连接

touch deleteme
grep -rl oldstr path | xargs sed -i 's/oldstr/newstr/g' ./deleteme
rm deleteme

虽然这不太优雅,并且需要与服务器建立更多的连接(也许有一种方法可以在一行中完成所有操作),但它也能有效地完成这项工作

【讨论】:

  • 这个脚本救了我的命!
【解决方案5】:

我认为如果不使用-exec,您可以简单地提供/dev/null 作为至少一个参数,以防万一找不到:

grep -rl oldstr path | xargs sed -i 's/oldstr/newstr/g' /dev/null

【讨论】:

  • 可能是一个非常简洁的解决方法。可悲的是我得到sed: couldn't edit /dev/null: not a regular file :)
  • @michael:事实证明——运行这样的任何东西实际上会因为参数太多而导致 shell 崩溃......见superuser.com/questions/257250/…
  • 我的目录中有很多文件,但只有少数文件包含我要更改的字符串。所以我不太担心 sed 的参数太多。
  • @michael:那里列出的 10 票解决方案应该可以完成这项工作。
【解决方案6】:

我的用例是我想替换 foo:/Drive_Letterfoo:/bar/baz/xyz 就我而言,我可以使用以下代码来做到这一点。 我位于有大量文件的同一目录位置。

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

希望有所帮助。

【讨论】:

    【解决方案7】:

    不确定这是否会有所帮助,但您可以将其与远程服务器一起使用,如下例所示

    ssh example.server.com "find /DIR_NAME -type f -name "FILES_LOOKING_FOR" -exec sed -i 's/LOOKINGFOR/withThisString/g' {} ;"

    将 example.server.com 替换为您的服务器 用您的目录/文件位置替换 DIR_NAME 用您要查找的文件替换 FILES_LOOKING_FOR 用您要查找的内容替换 LOOKINGFOR 用你想在文件中替换的内容替换 withThisString

    【讨论】:

      【解决方案8】:

      如果您要替换固定字符串或某些模式,我还想添加 bash 内置模式字符串替换变量替换构造。我没有自己描述,而是引用了 bash 手册中的部分:

      ${parameter/pattern/string}

      模式被扩展以产生一个模式,就像在路径名中一样 扩张。 parameter 被扩展并且 pattern 与其值的最长匹配被替换为 string。如果模式/ 开头,pattern 的所有匹配都替换为字符串。 通常只替换第一场比赛。如果 pattern 开始 与#,它必须在扩展值的开头匹配 参数。如果 pattern% 开头,则必须匹配到最后 参数的扩展值。如果 string 为 null,则匹配 的 pattern 被删除,pattern 后面的 / 可以省略。如果参数@*,则替换操作为 依次应用于每个位置参数,然后展开 是结果列表。如果 parameter 是一个以@* 为下标的数组变量,则将替换操作应用于 数组的每个成员依次展开,展开 结果列表。

      【讨论】:

        猜你喜欢
        • 2014-01-13
        • 2018-02-24
        • 2017-05-14
        • 2013-03-23
        • 1970-01-01
        • 1970-01-01
        • 2020-11-15
        • 2014-02-11
        相关资源
        最近更新 更多