【问题标题】:How can I do a recursive find/replace of a string with awk or sed?如何使用 awk 或 sed 递归查找/替换字符串?
【发布时间】:2010-12-07 16:20:25
【问题描述】:

如何查找和替换每次出现的:

subdomainA.example.com

subdomainB.example.com

/home/www/目录树下的每个文本文件中递归?

【问题讨论】:

  • 提示:不要在 svn 结帐树中执行以下操作...它会覆盖魔法 .svn 文件夹文件。
  • 天哪,这正是我刚刚所做的。但它奏效了,似乎没有造成任何伤害。可能发生的最坏情况是什么?
  • @J.Katzwinkel:至少,它可能会损坏校验和,这可能会损坏您的存储库。
  • 给所有使用 sed 的人的快速提示:它将在您的文件中添加尾随换行符。如果您不想要它们,请先执行一个不匹配任何内容的查找替换,然后将其提交给 git。然后做真题。然后以交互方式变基并删除第一个。
  • 您可以在管道到 xargs 之前使用 -path ./.git -prune -o in find . -path ./.git -prune -o -type f -name '*matchThisText*' -print0 从结果中排除目录,例如 git

标签: bash sed awk replace


【解决方案1】:
find /home/www \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

-print0 告诉find 打印由空字符而不是新行分隔的每个结果。万一您的目录中包含名称中带有换行符的文件,这仍然可以让xargs 处理正确的文件名。

\( -type d -name .git -prune \) 是一个完全跳过所有名为.git 的目录的表达式。如果您使用 SVN 或想要保留其他文件夹,您可以轻松扩展它——只需匹配更多名称即可。它大致相当于-not -path .git,但效率更高,因为它不会检查目录中的每个文件,而是完全跳过它。 -o 后面的 -o 是必需的,因为 -prune 的实际工作方式。

有关详细信息,请参阅man find

【讨论】:

  • 这对我有用,我的情况是查找/替换 IP 地址值。不过,对于画廊的问题:为什么第一个 subdomainA\.example\.com 值而不是第二个 sudomainB.example.com 值的点被转义了?我以建议的格式执行它,它似乎完美地完成了这项工作,但我很好奇为什么只为第一个字符串模式呈现转义。
  • 如果其中一个文件具有不可变标志,则此脚本将停止而不会到达结尾并出现错误 Permission denied。最好使用-exec sed -i ... {} \; 而不是管道。
  • 我经常使用find . -type f -print0 | xargs -0 sed -i -e 's/\r$//'在特定目录中递归地用文件中的LF替换所有CRLF。
  • 使用 MACOS 并感到沮丧为什么它不起作用 -> 尝试 -> find . \( ! -regex '.*/\..*' \) -type f | LC_ALL=C xargs sed -i '' 's/foo/bar/g'
  • @elrobis(12 年后,但为了记录)第一个 URL 使用转义点,因为它在正则表达式匹配文本中并且很特殊,但第二个 URL 在替换文本中并且点是在这种情况下并不特别。
【解决方案2】:
cd /home/www && find . -type f -print0 |
      xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

【讨论】:

  • 一些解释是有序的,特别是因为它不使用任何要求的工具(问题也用它们标记)。例如,想法/要点是什么?请通过编辑您的答案来回复,而不是在 cmets 中(without "Edit:"、"Update:" 或类似的 - 答案应该看起来好像是今天写的)。
【解决方案3】:

如果您需要排除目录 (--exclude-dir=..folder) 并且可能有带空格的文件名,这是一种直接的方法(通过对grep -Z 使用 0Byte 来解决和xargs -0)

grep -rlZ oldtext . --exclude-dir=.folder | xargs -0 sed -i 's/oldtext/newtext/g'

【讨论】:

    【解决方案4】:

    或使用速度极快的 GNU Parallel:

    grep -rl oldtext . | parallel sed -i 's/oldtext/newtext/g' {}
    

    【讨论】:

    • 如何安装 GNU Parallel?
    • 尝试查找并行包。拱门:sudo pacman -S parallel; ubuntu/debian:sudo apt-get install parallel;软呢帽:dnf install parallel;我用拱顺便说一句
    【解决方案5】:

    替换(所有文件、目录、递归)的最简单方法

    find . -type f -not -path '*/\.*' -exec sed -i 's/foo/bar/g' {} +
    

    注意:有时你可能需要忽略一些隐藏文件,例如.git,你可以使用上面的命令。

    如果你想包含隐藏文件使用,

    find . -type f  -exec sed -i 's/foo/bar/g' {} +
    

    在这两种情况下,字符串 foo 都将替换为新字符串 bar

    【讨论】:

      【解决方案6】:

      这是一个比大多数版本更通用的版本;例如,它不需要find(使用du)。它确实需要xargs,这只能在Plan 9 的某些版本(如9front)中找到。

       du -a | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'
      

      如果您想添加文件扩展名等过滤器,请使用grep

       du -a | grep "\.scala$" | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'
      

      【讨论】:

        【解决方案7】:

        对我来说最简单的方法是

        grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'
        

        【讨论】:

        • 当您需要排除目录时,这尤其适用,例如.svn。例如:grep -rl oldtext . --exclude-dir=.svn | xargs sed -i 's/oldtext/newtext/g'
        • 在 macOS 上,sed -i 导致 sed: 1: "file_path": invalid command code .。这是因为 -i 是 macOS 上的不同标志。我发现grep -rl old . | xargs sed -i "" -e 's/old/new/g' 有效。我发现this很有用
        • 如果您使用的是编译语言并希望避免检查二进制文件,您可以传递 I 标志,例如 grep -Irl oldtext . | xargs sed -i 's/oldtext/newtext/g'
        • 在 git 项目中,一定要使用 git grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g' 避免搜索依赖项(可能会通过 .gitignore 忽略):) 很好的解决方案! @phyatt 这是一个更好的方法。
        • 使用 MACOS 并沮丧为什么它不起作用 -> 尝试 -> grep -rl 'SEARCHSTRING' ./ | LC_ALL=C xargs sed -i '' 's/SEARCHSTRING/REPLACESTRING/g'
        【解决方案8】:

        我只用上衣:

        find . -name '*.[c|cc|cp|cpp|m|mm|h]' -print0 |  xargs -0 tops -verbose  replace "verify_noerr(<b args>)" with "__Verify_noErr(<args>)" \
        replace "check(<b args>)" with "__Check(<args>)" 
        

        【讨论】:

        • 加一代表` '*.[c|cc|cp|cpp|m|mm|h]' `
        【解决方案9】:

        this博文:

        find . -type f | xargs perl -pi -e 's/oldtext/newtext/g;'
        

        【讨论】:

        • 如何转义斜线/ ?比如我要替换IP地址:xxx.xxx.xxx.xxxxxx.xxx.xxx.xxx/folder
        • 您可以使用 \ 转义 /。例如:find . -type f | xargs perl -pi -e 's/xxx.xxx.xxx.xxx\/folder/newtext/g;'
        【解决方案10】:

        更改多个文件(并将备份保存为*.bak):

        perl -p -i -e "s/\|/x/g" *
        

        将获取目录中的所有文件并将|替换为x 称为“Perl pie”(简单得像个馅饼)

        【讨论】:

        【解决方案11】:

        要通过递归方式减少文件到 sed,您可以为您的字符串实例使用 grep

        grep -rl <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g
        

        如果您运行 man grep,您会注意到如果您想省略搜索 .git 目录,还可以定义一个 --exlude-dir="*.git" 标志,避免其他人礼貌指出的 git 索引问题。

        带你去:

        grep -rl --exclude-dir="*.git" <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g
        

        【讨论】:

          【解决方案12】:
          perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`
          

          【讨论】:

          • 不使用awk/sed,但是perl很常见(除了嵌入式/只有busybox的系统)。
          【解决方案13】:

          只是为了避免也改变

          • NearlysubdomainA.example.com
          • 子域A.example.comp.other

          但还是

          • 子域A.example.com.IsIt.good

          (在域根背后的想法可能不好)

          find /home/www/ -type f -exec sed -i 's/\bsubdomainA\.example\.com\b/\1subdomainB.example.com\2/g' {} \;
          

          【讨论】:

            【解决方案14】:

            要替换 git 存储库中的所有匹配项,您可以使用:

            git ls-files -z | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'
            

            请参阅List files in local git repo? 了解列出存储库中所有文件的其他选项。 -z 选项告诉 git 用零字节分隔文件名,这确保 xargs(带有选项 -0)可以分隔文件名,即使它们包含空格或诸如此类的东西。

            【讨论】:

              【解决方案15】:

              如果您有权访问节点,则可以执行npm install -g rexreplace 然后

              rexreplace 'subdomainA.example.com' 'subdomainB.example.com' /home/www/**/*.*
              

              【讨论】:

                【解决方案16】:

                将当前所有 .c.h 文件中与 string_1 匹配的所有内容替换为 string_2目录和子目录(不包括.git/)

                这适用于 Mac

                find . -type f -path "*.git*" -prune -o -name '*\.[ch]' -exec \
                sed -i '' -e 's/'$1'/'$2'/g' {} +
                

                这应该可以在 Linux 上运行(尚未测试):

                find . -type f -path "*.git*" -prune -o -name '*\.[ch]' -exec \
                sed -i 's/string_1/string_2/g' {} +
                

                【讨论】:

                  【解决方案17】:

                  有点老派,但这适用于 OS X。

                  有一些诡计:

                  • 只会编辑当前目录下扩展名为.sls 的文件

                  • 必须对. 进行转义,以确保sed 不会将它们评估为“任何字符”

                  , 用作sed 分隔符,而不是通常的/

                  另请注意,这是编辑 Jinja 模板以在 import 的路径中传递 variable(但这是题外话)。

                  首先,验证您的 sed 命令是否符合您的要求(这只会将更改打印到标准输出,不会更改文件):

                  for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done
                  

                  准备好进行更改后,根据需要编辑 sed 命令:

                  for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed -i '' 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done
                  

                  请注意 sed 命令中的 -i '',我不想创建原始文件的备份(如 In-place edits with sed on OS X 或 Robert Lujo 在本页的评论中所述)。

                  祝大家快乐!

                  【讨论】:

                    【解决方案18】:

                    对于使用silver searcher (ag) 的任何人

                    ag SearchString -l0 | xargs -0 sed -i 's/SearchString/Replacement/g'
                    

                    由于 ag 默认忽略 git/hg/svn 文件/文件夹,因此在存储库中运行是安全的。

                    【讨论】:

                    • 感谢您提供有效的解决方案!我需要找到 ripgrep 的等价物。
                    • @reducingactivity 查看github.com/chmln/sd :) 我是一个快乐的用户
                    • 将 ag 替换为 rg 来实现 ripgrep 也可以正常工作。
                    【解决方案19】:

                    您可以使用 awk 来解决这个问题,如下所示,

                    for file in `find /home/www -type f`
                    do
                       awk '{gsub(/subdomainA.example.com/,"subdomainB.example.com"); print $0;}' $file > ./tempFile && mv ./tempFile $file;
                    done
                    

                    希望对你有帮助!!!

                    【讨论】:

                    • 在 MacOs 上运行没有任何问题!当包含二进制文件时,所有基于 sed 的命令都失败了,即使使用了 osx 特定设置。
                    • 小心...如果find 返回的任何文件的名称中有空格,这将爆炸!使用while read更安全:stackoverflow.com/a/9612560/1938956
                    【解决方案20】:

                    这个兼容git仓库,简单一点:

                    Linux:

                    git grep -l 'original_text' | xargs sed -i 's/original_text/new_text/g'
                    

                    苹果机:

                    git grep -l 'original_text' | xargs sed -i '' -e 's/original_text/new_text/g'
                    

                    (感谢http://blog.jasonmeridth.com/posts/use-git-grep-to-replace-strings-in-files-in-your-git-repository/

                    【讨论】:

                    • 明智地使用git-grep-z选项和xargs -0
                    • git grep 显然只在git 存储库中才有意义。一般替换为grep -r
                    • @gniourf_gniourf 你能解释一下吗?
                    • @PetrPeller: -z, git-grep 将用空字节而不是换行符分隔输出字段;并且使用-0xargs 将读取由空字节分隔的输入,而不是空格(并且不会用引号做奇怪的事情)。因此,如果文件名包含空格、引号或其他有趣字符,如果您不希望命令中断,则命令为:git grep -z -l 'original_text' | xargs -0 sed ...
                    【解决方案21】:

                    注意:不要在包含 git repo 的文件夹上运行此命令 - 更改 .git 可能会损坏您的 git 索引。

                    find /home/www/ -type f -exec \
                        sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +
                    

                    与此处的其他答案相比,这比大多数都简单,并且使用 sed 而不是 perl,这是原始问题所要求的。

                    【讨论】:

                    • 请注意,如果您使用的是 BSD sed(包括在 Mac OS X 上),您需要为 sed 的 -i 选项提供一个明确的空字符串 arg。即:sed -i '' 's/original/replacement/g'
                    • 如何修改它以排除 .git 子文件夹?
                    【解决方案22】:

                    使用grepsed 的组合

                    for pp in $(grep -Rl looking_for_string)
                    do
                        sed -i 's/looking_for_string/something_other/g' "${pp}"
                    done
                    

                    【讨论】:

                    • @tripleee 我稍微修改了一下。在这种情况下,命令grep -Rl pattern 的输出会生成模式所在的文件列表。 for 循环中未读取文件。
                    • 嗯?你还有一个for 循环;如果任何返回的文件名包含空格,它将无法正常工作,因为 shell 标记了 for 参数列表。但是随后您在循环内使用不带引号的文件名变量,因此如果您修复此问题,它将在那里中断。纠正这些剩余的错误将使您的错误与@MadMan2064 的答案相同。
                    • @tripleee 是的,没错,我错过了。
                    【解决方案23】:

                    如果您想在不完全破坏您的 SVN 存储库的情况下使用它,您可以告诉 'find' 忽略所有隐藏文件:

                    find . \( ! -regex '.*/\..*' \) -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'
                    

                    【讨论】:

                    • 括号似乎是多余的。这之前有一个格式错误,使其无法使用(Markdown 渲染会吃掉一些正则表达式中的字符)。
                    【解决方案24】:
                    find /home/www/ -type f -exec perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +
                    

                    find /home/www/ -type f 将列出 /home/www/ (及其子目录)中的所有文件。 “-exec”标志告诉 find 在找到的每个文件上运行以下命令。

                    perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +
                    

                    是在文件上运行的命令(一次很多)。 {} 被文件名替换。 命令末尾的+ 告诉find 为多个文件名构建一个命令。

                    根据find 手册页: “命令行的构建方式与 xargs 构建它的命令行。”

                    因此,无需使用xargs -0-print0 即可实现您的目标(并处理包含空格的文件名)。

                    【讨论】:

                      【解决方案25】:

                      这是我为 OSX 和 Windows (msys2) 找到的最好的全方位解决方案。应该适用于任何可以获得 sed 的 gnu 版本的东西。跳过 .git 目录,以免损坏您的校验和。

                      在 mac 上,只需先安装 coreutils 并确保 gsed 在路径中 -

                      brew install coreutils
                      

                      然后我把这个函数放在我的 zshrc/bashrc ->

                      replace-recursive() {
                          hash gsed 2>/dev/null && local SED_CMD="gsed" || SED_CMD="sed"
                          find . -type f -name "*.*" -not -path "*/.git/*" -print0 | xargs -0 $SED_CMD -i "s/$1/$2/g"
                      }
                      
                      usage: replace-recursive <find> <replace>
                      

                      【讨论】:

                        【解决方案26】:

                        grep -lr 'subdomainA.example.com' | while read file; do sed -i "s/subdomainA.example.com/subdomainB.example.com/g" "$file"; done

                        我猜大多数人不知道他们可以将某些内容通过管道传输到“同时读取文件”中,这样​​可以避免那些讨厌的 -print0 参数,同时保留文件名中的空格。

                        在 sed 之前进一步添加 echo 可以让您在实际执行之前查看哪些文件会更改。

                        【讨论】:

                        • -print0 有用的原因是它可以处理while read 根本无法处理的情况——换行符是 Unix 文件名中的有效字符,因此要使您的代码完全健壮,它也需要处理这样的文件名。 (另外,您希望 read -r 避免在 read 中出现一些讨厌的 POSIX 遗留行为。)
                        • 另外,如果没有匹配项,sed 是无操作的,所以grep 不是真正必要的;虽然它是一个有用的优化,可以避免重写不包含任何匹配项的文件,如果您有很多匹配项,或者希望避免不必要地更新文件上的日期戳。
                        【解决方案27】:

                        如果您不介意将vimgrepfind 工具一起使用,您可以跟进用户Gert 在此链接中给出的答案 --> How to do a text replacement in a big folder hierarchy?

                        这是交易:

                        • 递归 grep 查找要替换的字符串在某个路径中,并且只取匹配文件的完整路径。 (那就是$(grep 'string' 'pathname' -Rl)

                        • (可选)如果您想对集中目录中的这些文件进行预备份,也许您也可以使用它:cp -iv $(grep 'string' 'pathname' -Rl) 'centralized-directory-pathname'

                        • 之后,您可以在vim 中按照与给定链接中提供的方案类似的方案随意编辑/替换:

                          • :bufdo %s#string#replacement#gc | update

                        【讨论】:

                          【解决方案28】:

                          所有的技巧都差不多,但我喜欢这个:

                          find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
                          
                          • find &lt;mydir&gt;:在目录中查找。

                          • -type f

                            文件类型:普通文件

                          • -exec command {} +:

                            -exec 操作的这个变体在选定的文件上运行指定的命令,但命令行是通过附加来构建的 最后选择的每个文件名;该命令的总调用次数将远少于 匹配的文件。命令行的构建方式与 xargs 构建其命令行的方式非常相似。只有一个实例 命令中允许使用“{}”。该命令在起始目录中执行。

                          【讨论】:

                            【解决方案29】:

                            对于 IBMi 上的 Qshell (qsh),而不是 OP 标记的 bash。

                            qsh 命令的限制:

                            • find 没有 -print0 选项
                            • xargs 没有 -0 选项
                            • sed 没有 -i 选项

                            因此 qsh 中的解决方案:

                                PATH='your/path/here'
                                SEARCH=\'subdomainA.example.com\'
                                REPLACE=\'subdomainB.example.com\'
                            
                                for file in $( find ${PATH} -P -type f ); do
                            
                                        TEMP_FILE=${file}.${RANDOM}.temp_file
                            
                                        if [ ! -e ${TEMP_FILE} ]; then
                                                touch -C 819 ${TEMP_FILE}
                            
                                                sed -e 's/'$SEARCH'/'$REPLACE'/g' \
                                                < ${file} > ${TEMP_FILE}
                            
                                                mv ${TEMP_FILE} ${file}
                                        fi
                                done
                            

                            注意事项:

                            • 解决方案不包括错误处理
                            • 不是 OP 标记的 Bash

                            【讨论】:

                            • 这在引用和阅读for 行时存在一些令人讨厌的问题。
                            【解决方案30】:

                            一个不错的 oneliner 作为额外的。使用 git grep。

                            git grep -lz 'subdomainA.example.com' | xargs -0 perl -i'' -pE "s/subdomainA.example.com/subdomainB.example.com/g"
                            

                            【讨论】:

                            • 如果在 git repo 中工作是个好主意,因为您不会冒险覆盖 .git/ 内容(如 cmets 中报告的另一个答案)。
                            • 谢谢,我把它用作 bash 函数 refactor() { echo "Replacing $1 by $2 in all files in this git repository." git grep -lz $1| xargs -0 perl -i'' -pE "s/$1/$2/g" } 用法,例如用 'sword' 替换 'word':refactor word sword 然后验证它对 git diff 做了什么。
                            猜你喜欢
                            • 2017-08-25
                            • 1970-01-01
                            • 2022-01-05
                            • 2021-12-24
                            • 2017-11-11
                            • 2017-02-08
                            相关资源
                            最近更新 更多