ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 awk 或 sed 递归查找/替换字符串?

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

subdomainA.example.com

subdomainB.example.com

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

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

P
Peter Mortensen
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,但更有效,因为它不是检查目录中的每个文件,而是完全跳过它。由于 -prune 的实际工作方式,它后面的 -o 是必需的。

有关详细信息,请参阅 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$//' 以递归方式将特定目录中的文件中的所有 CRLF 替换为 LF。
使用 MACOS 并感到沮丧为什么它不工作 ->尝试-> find . \( ! -regex '.*/\..*' \) -type f | LC_ALL=C xargs sed -i '' 's/foo/bar/g'
@elrobis(12 年后,但为了记录)第一个 URL 使用转义点,因为它在正则表达式匹配文本中并且是特殊的,但第二个 URL 在替换文本中并且点在该上下文中并不特殊。
A
Anatoly

对我来说最简单的方法是

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'
m
mikemaccana

注意:不要在包含 git 存储库的文件夹上运行此命令 - 更改 .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 子文件夹?
@reducingactivity 嗨!您可以使用这个:grep -rl placeholder . | grep -Ev ".git" | xargs sed -i s/placeholder/lol/g(grep -Ev 排除模式) - 提示:在实际运行它来替换它之前,首先使用它而不使用 -i,就像试运行一样。
I
I159

所有的技巧几乎都是一样的,但我喜欢这个:

find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +

find :在目录中查找。

-type f:文件类型:普通文件

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


C
Community

对我来说,最容易记住的解决方案是 https://stackoverflow.com/a/2113224/565525,即:

sed -i '' -e 's/subdomainA/subdomainB/g' $(find /home/www/ -type f)

注意-i '' 解决 OSX 问题sed: 1: "...": invalid command code .

注意:如果要处理的文件太多,您将获得 Argument list too long。解决方法 - 使用上述 find -execxargs 解决方案。


在 Cygwin 上,它产生 sed: can't read : No such file or directory。为什么以及如何解决?
P
Peter Mortensen
cd /home/www && find . -type f -print0 |
      xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

一些解释是有序的,特别是因为它不使用任何要求的工具(问题也用它们标记)。例如,想法/要点是什么?请通过编辑您的答案来回复,而不是在评论中(没有“编辑:”、“更新:”或类似的 - 答案应该看起来好像是今天写的)。
J
Jacob Wang

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

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

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


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

一个不错的 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/ 内容(如对另一个答案的评论中所报告的)。
谢谢,我将它用作 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" } 用法,例如将 'word' 替换为 'sword':refactor word sword 然后验证它对 git diff 做了什么。
s
seddonym

这个与 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:使用 -zgit-grep 将用空字节而不是换行符分隔输出字段;并且使用 -0xargs 将读取由空字节分隔的输入,而不是空格(并且不会用引号做奇怪的事情)。因此,如果您不想在文件名包含空格、引号或其他有趣字符时中断命令,则命令为:git grep -z -l 'original_text' | xargs -0 sed ...
d
domdambrogia

要通过递归 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

S
Sazzad Hissain Khan

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

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


i
inetphantom

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

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

我见过的所有其他 7 个以上的答案都忽略了空格!
u
unutbu
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 即可实现您的目标(并处理包含空格的文件名)。


H
Henno

我只是需要这个并且对可用示例的速度不满意。所以我想出了我自己的:

cd /var/www && ack-grep -l --print0 subdomainA.example.com | xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

Ack-grep 在查找相关文件方面非常有效。这个命令轻而易举地替换了 ~145 000 个文件,而其他命令花了很长时间,我等不及他们完成了。


不错,但 grep -ril 'subdomainA' * 远没有 grep -Hr 'subdomainA' * | cut -d: -f1 快。
@Henno:只有一个问题:如何排除二进制文件(可执行文件)?
ack-grep 会自动为您执行此操作。
@Henno:它是否包含 shell 脚本?
是的。以下是它支持的文件类型的完整列表:beyondgrep.com/documentation
m
microo8

或使用速度极快的 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;我用拱顺便说一句
J
Jason Plank

尝试这个:

sed -i 's/subdomainA/subdomainB/g' `grep -ril 'subdomainA' *`

嗨@RikHic,不错的提示-正在考虑这样的事情;不幸的是,上面的格式并不完全正确 :) 所以我会尝试使用 pre 标签(不起作用) - 所以用转义反引号然后:sed -i 's/subdomainA/subdomainB/g'` grep -ril 'subdomainA' /home/www/*` - 这看起来仍然不太好了,但应该在复制粘贴中幸存下来:) 干杯!
M
MadMan2064

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 不是必需的;虽然它是一个有用的优化,可以避免重写不包含任何匹配项的文件,如果您有很多匹配项,或者想要避免不必要地更新文件上的日期戳。
p
petrus4
#!/usr/local/bin/bash -x

find * /home/www -type f | while read files
do

sedtest=$(sed -n '/^/,/$/p' "${files}" | sed -n '/subdomainA/p')

    if [ "${sedtest}" ]
    then
    sed s'/subdomainA/subdomainB/'g "${files}" > "${files}".tmp
    mv "${files}".tmp "${files}"
    fi

done

s
sarath kumar

您可以使用 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
这不适用于名称包含空格或换行符的文件
J
J.Hpour

根据 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;'
C
Community

如果您不介意将 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 |更新

:bufdo %s#string#replacement#gc |更新


C
Community

有点老派,但这适用于 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 在本页的评论中所述)。

快乐的seding伙计们!


P
Perseids

要替换 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)可以分隔文件名,即使它们包含空格或诸如此类的东西。


N
NeronLeVelu

只是为了避免改变

NearlysubdomainA.example.com

subdomainA.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' {} \;

t
tgunr

我只使用上衣:

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]' 加一
b
bbarker

这是一个比大多数版本更通用的版本;例如,它不需要 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'

C
Christoff Erasmus

对于 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 行方面存在一些令人讨厌的问题。
t
tripleee

如果您想在不完全破坏您的 SVN 存储库的情况下使用它,您可以通过执行以下操作告诉“查找”忽略所有隐藏文件:

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

括号似乎是多余的。这之前有一个格式错误,使其无法使用(Markdown 渲染会吃掉正则表达式中的一些字符)。
P
Pawel

使用 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 是的,这是真的,我错过了这个。
这不适用于名称包含空格或换行符的文件
S
Sheena
perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`

不使用 awk/sed,但 perl 很常见(嵌入式/系统除外)。
这不适用于名称包含空格或换行符的文件