Mac 和 Linux 上的文本文件中的递归搜索和替换

Posted

技术标签:

【中文标题】Mac 和 Linux 上的文本文件中的递归搜索和替换【英文标题】:Recursive search and replace in text files on Mac and Linux 【发布时间】:2012-03-31 00:48:30 【问题描述】:

在 linux shell 中,以下命令将递归搜索并用 'that' 替换所有 'this' 实例(我面前没有 Linux shell,但应该可以)。

find . -name "*.txt" -print | xargs sed -i 's/this/that/g'

在 OSX 上类似的命令会是什么样子?

【问题讨论】:

应该转移到apple.stackexchange.com,因为它对于 linux 和所有开发人员来说都不够通用。 【参考方案1】:

OS X 混合使用 BSD 和 GNU 工具,所以最好经常检查文档(尽管我知道 less 甚至不符合 OS X 手册页):

https://web.archive.org/web/20170808213955/https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/sed.1.html

sed 将-i 之后的参数作为备份的扩展名。提供一个空字符串 (-i '') 表示不进行备份。

以下应该做:

find . -type f -name '*.txt' -exec sed -i '' s/this/that/g +

-type f 只是一个好习惯;如果你给它一个目录左右,sed 会抱怨。

-exec 优先于xargs;你不必为-print0 或任何东西而烦恼。

末尾的 + 意味着find 会将所有结果作为参数附加到被调用命令的一个实例,而不是为每个结果重新运行它。 (一个例外是操作系统允许的最大命令行参数数量被破坏;在这种情况下,find 将运行多个实例。)

如果您收到诸如“无效字节序列”之类的错误,则可以通过在命令开头添加 LC_ALL=C 来强制使用标准语言环境,如下所示:

LC_ALL=C find . -type f -name '*.txt' -exec sed -i '' s/this/that/g +

【讨论】:

我在这个替换中的“this”包含一个正斜杠(localhost/site)——我正在替换 .html 文件中的 URL 的一部分......我该如何进行这样的替换。我尝试输入双引号,但失败了。 Sed 语法允许使用几乎 any 字符代替斜杠,例如您可以使用% 字符:sed "s%localhost/site%blah/blah%"。另一种选择是反斜杠转义分隔符:sed "s/localhost\/site/blah\/blah/" 谢谢你让我试试。但是,我确实尝试使用 来分隔斜线,但仍然出现错误... 还有其他人收到illegal byte sequence 错误吗?如果是这样,请尝试:LC_ALL=C find . -type f -name '*.txt' -exec sed -i '' s/this/that/ +,它对我有用。 这将只替换每个文件一个 ocurreny,使用 /g 表示多个 ocurreny,例如 LC_ALL=C find . -type f -exec sed -i '' s/search/replace/g + 【参考方案2】:

对于 mac,更类似的方法是这样的:

find . -name '*.txt' -print0 | xargs -0 sed -i "" "s/form/forms/g"

【讨论】:

我希望每次我回到它并使用它时都可以投票。现在应该是 +15 了,很简单。 出于某种原因,它对我不起作用。它什么也不做。我在 form360 文件夹中,我正在尝试将名为 easyform 的所有字符串实例更改为 form360,我正在运行以下命令:find . -name '*.php' -print0 | xargs -0 sed -i "" "s/easyform/form360/g" 对我来说,这应该是正确的答案。这是唯一对我有用的。 -print0 |当文件名包含空格时,xargs -0 在我的 Mac 上不起作用。 sed: .: 就地编辑仅适用于常规文件【参考方案3】:

作为替代解决方案,我在 Mac OSX 10.7.5 上使用这个解决方案

grep -ilr 'old-word' * | xargs -I@ sed -i '' 's/old-word/new-word/g' @

归功于:Todd Cesere's answer

【讨论】:

这个很好用!在某些情况下,其他脚本会在 OSX 上添加额外的行尾!非常感谢! 将 grep 与 * 一起使用的缺点是它会慢得多。考虑使用-I 来避免二进制文件,使用--exclude-dirdir1,dir2 之类的模式 不起作用 $ grep -ilr 'fastapi' * | xargs -I@ sed -i '' 's/fastapi/flask/g' @ 并且输出为 sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence sed: RE error: illegal byte sequence【参考方案4】:

以上都不适用于 OSX。

执行以下操作:

perl -pi -w -e 's/SEARCH_FOR/REPLACE_WITH/g;' *.txt

【讨论】:

如果 SEARCH_FOR 和 REPLACE_WITH 是路径,如何转义 '/'? 使用不同的分隔符。如果您使用路径,则可以使用冒号或管道。 's|搜索|替换|g',例如。我们使用大括号,如 'sSEARCHREPLACE'。 dito 问题,在 Mac 上尝试不尝试 - 但它似乎会产生错误?例如,我的路径被解释为文件? -bash: localhost/nohost: 没有这样的文件或目录 这不会在文件夹深处递归。只有一层。 有关此命令的更多信息,请阅读此lifehacker.com/5810026/…【参考方案5】:

适用于 Linux 和 Mac OS X 的版本(通过将 -e 开关添加到 sed):

export LC_CTYPE=C LANG=C
find . -name '*.txt' -print0 | xargs -0 sed -i -e 's/this/that/g'

【讨论】:

我必须从这个答案 + 已接受答案的行中导出(我不希望生成备份文件) 要解决“非法字节序列”错误,请在运行命令之前尝试设置 LOCALE:export LC_CTYPE=C && export LANG=C 永远不要使用 '*' 而不是 '*.filetype' 来运行它,就像我在使用 Git 时所做的那样。或者您可以告别所有未发表的作品。 mac版sed命令需要在-i后面加'',所以这个答案不正确【参考方案6】:

这是我的可行的。在 Mac OS X 10.10.4 上

grep -e 'this' -rl . | xargs sed -i '' 's/this/that/g'

以上使用find会更改不包含搜索文本的文件(在文件末尾添加新行),这是冗长的。

【讨论】:

不起作用$ grep -e 'fastapi' -rl . | xargs sed -i '' 's/fastapi/flask/g',这是输出sed: RE error: illegal byte sequence【参考方案7】:

2021

什么对我有用:

LC_ALL=C && LANG=C && find . -type f | xargs sed -i '' 's/old/new/g'

【讨论】:

没有LC_ALL=C && LANG=C && 部分对我有用。为什么需要它?【参考方案8】:

每当我输入这个命令时,我似乎总是把它灌满,或者忘记一个标志。我根据 TaylanUB 的回答在 github 上创建了一个 Gist,它从当前目录进行全局查找替换。这是 Mac OSX 特有的。

https://gist.github.com/nateflink/9056302

很好,因为现在我只是弹出一个终端然后复制:

curl -s https://gist.github.com/nateflink/9056302/raw/findreplaceosx.sh | bash -s "find-a-url.com" "替换-a-url.com"

你可能会得到一些奇怪的字节序列错误,所以这里是完整的代码:

#!/bin/bash
#By Nate Flink

#Invoke on the terminal like this
#curl -s https://gist.github.com/nateflink/9056302/raw/findreplaceosx.sh | bash -s "find-a-url.com" "replace-a-url.com"

if [ -z "$1" ] || [ -z "$2" ]; then
  echo "Usage: ./$0 [find string] [replace string]"
  exit 1
fi

FIND=$1
REPLACE=$2

#needed for byte sequence error in ascii to utf conversion on OSX
export LC_CTYPE=C;
export LANG=C;

#sed -i "" is needed by the osx version of sed (instead of sed -i)
find . -type f -exec sed -i "" "s|$FIND|$REPLACE|g"  +
exit 0

【讨论】:

【参考方案9】:

如果您使用的是 zsh 终端,则可以使用通配符魔法:

sed -i "" "s/search/high-replace/g" *.txt

【讨论】:

【参考方案10】:

我使用了这种格式 - 但是...我发现我必须运行它三次或更多次才能让它真正改变我发现非常奇怪的每个实例。运行一次会改变每个文件中的一些但不是全部。运行完全相同的字符串两四次将捕获所有实例。

find . -type f -name '*.txt' -exec sed -i '' s/thistext/newtext/  +

【讨论】:

您需要多次运行此命令 b/c 您的 sed 正则表达式最后需要一个 g,否则它只会替换一行中第一次出现的 thistext。所以你的正则表达式应该是s/thistext/newtext/g【参考方案11】:
find . -type f | xargs sed -i '' 's/string1/string2/g'

更多信息请参考here。

【讨论】:

【参考方案12】:

macOS 和 Linux:

我不确定其他答案是否适用于 Linux 和 macOS。如果有一些这样做,也许我们可以编辑它们以明确这一点。

以下是我用来定位两个平台的内容。

假设我们要执行查找/替换的文件包含以下文本:

file.txt:

FROM hello world

这是脚本,replace.bash:

    #!/bin/bash
    PATTERN="s/FROM/HELLOWORLD/"
    if [[ `uname -s`" == "Darwin" ]]; then
        sed -i '' "$PATTERN" file.txt
        echo Darwin
    else
        sed -i "$PATTERN" file.txt
        echo Linuxxxx
    fi

后来我发现在 sh 和 bash 之间使用单括号和单等号可以更好地配合:

替换.sh:

    PATTERN="s/FROM/HELLOWORLD/"
    if [ `uname -s` = "Darwin" ]; then
        sed -i '' "$PATTERN" file.txt
        echo Darwin
    else
        sed -i "$PATTERN" file.txt
        echo Linuxxxx
    fi

你自己的跨平台sed:

我可以看到有人如何将 $PATTERN 替换为 $1 并将 file.txt 替换为 $2 并且实际上能够创建一个适用于两个平台的 sed 包装器,例如:

ssed

    PATTERN="$1"
    FILE=$2
    if [ `uname -s` = "Darwin" ]; then
        sed -i '' "$PATTERN" $FILE
    else
        sed -i "$PATTERN" $FILE
    fi
$ chmod 755 ssed
$ ./ssed 's/FROM/jameswashere/' file

该文件将包含:

jameswashere hello world

递归查找/替换呢?

现在我们有了自己的独立于平台的 sed 包装器,我们可以使用它和 find 来循环子目录并对匹配的文件执行查找/替换:

$ find . -name "file" -exec ./ssed 's/FROM/that/g'  \;

【讨论】:

【参考方案13】:

https://bitbucket.org/masonicboom/serp 是一个 go 实用程序(即跨平台),在 OSX 上进行了测试,它对给定目录中的文件中的文本进行递归搜索和替换,并确认每个替换。它是新的,所以可能有问题。

用法如下:

$ ls test
a  d  d2 z
$ cat test/z
hi
$ ./serp --root test --search hi --replace bye --pattern "*"                         
test/z: replace hi with bye? (y/[n]) y
$ cat test/z
bye

【讨论】:

以上是关于Mac 和 Linux 上的文本文件中的递归搜索和替换的主要内容,如果未能解决你的问题,请参考以下文章

案例+解读,来自有道大神的17个常用Linux命令深度解析

来自运维大牛的17个常用Linux命令深度解析

Linux的常用命令2

linux中“grep”和“find”的区别是啥?

在mac中使用shell命令递归更改文件和文件夹的权限

Mac下find命令常用语法总结