【问题标题】:Multiple Problems with Sed on Mac OS XMac OS X 上 Sed 的多个问题
【发布时间】:2016-08-05 15:05:53
【问题描述】:

使用 Mac OS X 命令行,我想在当前目录及其许多子目录中的 大量 文件中执行简单的查找和替换。

我需要执行许多替换,因此我希望脚本尽可能高效。

无论我尝试什么似乎都会导致一些随机错误,所以我终于寻求帮助。

所以鉴于我有两个变量:

FIND=oldText
REPLACE=newText

这是我迄今为止尝试过的:

sed -i '' "s/${FIND}/${REPLACE}/g" *
> sed: Build: in-place edditing only works for regular expressions

显然这是试图对目录路径本身进行 sed,所以我随后尝试(排除目录被 sed'ed)

find * -type f  -print | xargs sed -i '' "s/${FIND}/${REPLACE}/g"
> xargs: sed: Argument list too long

所以因为我有这么大的文件列表来操作 xargs 无法处理它。显然 -exec 更适合大型列表..

find * -type f  -print -exec sed -i '' "s/${FIND}/${REPLACE}/g" {} \;

现在这确实有效,但是 sed 决定它必须纠正所有文件中丢失的 eof/换行符,尽管文件中没有替换。不幸的是,有成千上万个这种性质的文件,我不能为当前的工作进行如此大规模的更改。 (请不要宣扬我应该如何更正文件,这不是我要问的问题)。

因此,为了解决这个问题,我尝试首先提取确实包含我的 ${FIND} 术语的文件列表,然后只对这些文件执行 sed...

grep -r -l -e "${FIND}" "." | sed -i '' "s/${FIND}/${REPLACE}/g"
> sed: -i may not be used with stdin

-

grep -r -l -e "${FIND}" "." | -exec sed -i '' "s/${FIND}/${REPLACE}/g" {} \;
> ./file1.txt: line 10: -exec: command not found

-

$( grep -r -l -e "${FIND}" "." ) -exec sed -i '' "s/${FIND}/${REPLACE}/g" {} \;
> ./file1.txt: line 10: -exec: command not found

-

FILEPATHS_CONTAINING_FIND=$( grep -r -l -e "${FIND}" "." )
sed -i '' "s/${FIND}/${REPLACE}/g" "${FILEPATHS_CONTAINING_FIND}"
> sed: ./File1.txt
./File2.txt
./File3.txt: No such file or directory

我认为这里将变量 ${FILEPATHS_CONTAINING_FIND} 视为单个长文件路径。如果我删除双引号 "" 它不会处理带有空格的路径,所以这也不是一个选项。 现在回到尝试 xargs ,因为过滤后的文件列表更短了...

$( grep -r -l -e "${FIND}" "." ) | xargs sed -i '' "s/${FIND}/${REPLACE}/g"
> ./Script.sh: line 10: ./File1.txt: Permission denied

在不同的地方尝试 sudo 没有区别。

无论如何,我已经使用了这个 for 循环,但我真的更喜欢更简洁和高效的东西。

IFS=$'\n' # Ensure spaces don't mess up the for loop
for FILEPATH_CONTAINING_FIND in $(grep -r -l -e "${FIND}" "."); do
    sed -i '' "s/${FIND}/${REPLACE}/g" "${FILEPATH_CONTAINING_FIND}"
done

谁能帮我解决上面遇到的问题?

【问题讨论】:

  • 为什么在脚本还没有工作的情况下使用-i '' 覆盖文件?只有当您非常有信心整个事情会正常工作时,才应该在没有备份的情况下覆盖文件。好吧,没关系;它们是你的文件——你可以随心所欲。但是理智要求你不要(通常)四处覆盖文件,直到你确定你会做对。
  • 如果您要使用-exec,请使用{} + 而不是{} \;,因为它使find 的行为类似于xargs 并使用多个文件名作为参数运行。错误消息xargs: sed: Argument list too long 很奇怪。你的${FIND}${REPLACE} 字符串到底有多长?
  • “所以试图克服这个问题”之后的 4 个命令序列很奇怪。他们应该失败。您是否有要处理的带有空格的文件名?如果不是,那么grep -r -l … | xargs sed … 应该处理包含匹配项的文件。如果名称中包含可移植文件名字符集之外的空格或其他字符,请说明。它使你的工作更难。如果你的文件名包含换行符,那就更难了;这将是重要的信息。
  • @JonathanLeffler,我使用 git,所以所有文件都被备份,我可以简单地在两次尝试之间重置我的工作副本。感谢您的关心。
  • ${FIND} 和 ${REPLACE} 在这种情况下特别短,一般是个位数,没有字符。

标签: bash macos shell sed


【解决方案1】:

你可以像这样使用find + grep + sed

# cd to parent dir

while IFS= read -d '' -r file; do
   grep -q "$FIND" "$file" &&
   sed -i '' "s/${FIND}/${REPLACE}/g" "$file"
done < <(find . -type f  -print0)
  • 使用print0,我们从find 命令生成空字节终止的文件名
  • 使用read -d '' 我们将read 分隔在空字节上
  • 使用grep -q,我们确保在运行sed之前在文件中找到模式$FIND
  • 如果$FIND 只是一个普通字符串,那么您可以考虑使用grep -F

编辑:您可以使用while 循环和grep -r --null 改进您的for 循环:

while IFS= read -d '' -r file; do
   sed -i '' "s/${FIND}/${REPLACE}/g" "$file"
done < <(grep -lR --null "$FIND" .)

【讨论】:

  • 这比我最后介绍的 for 循环好吗?似乎远比这复杂。
  • 是的,更好的是for 循环在文件名有空格或其他一些全局字符时由于分词而容易出错。可能不是find,我们可以使用grep -r--null 选项(如果您希望我也提供该选项,请告诉我。
  • grep --null 有一个问题 — Mac OS X 上的 BSD grep 不支持它;它是一个 GNU grep 扩展。
  • 啊;有趣的。程序(当您询问 grep --help 时列出 --null)和手册页(仅在上下文“插入符号 `^' 匹配空字符串”中提到 null)之间存在差异。给定一个选择,假设程序的自我文档比“打印”文档更准确。我生活,我学习。
  • @EdMorton: :) 是的,我熟悉grep -r 上的观点。我最初的答案根本没有使用grep -r,我只是添加了因为OP 尝试使用for 循环使用grep -r,我尝试在编辑部分改进它。我建议始终使用find
猜你喜欢
  • 2012-08-09
  • 2012-08-15
  • 2016-09-26
  • 1970-01-01
  • 1970-01-01
  • 2013-12-06
  • 2011-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多