ChatGPT解决这个技术问题 Extra ChatGPT

更改多个文件

sed

以下命令正确更改了 2 个文件的内容。

sed -i 's/abc/xyz/g' xaa1 xab1 

但我需要做的是动态更改几个这样的文件,我不知道文件名。我想编写一个命令,该命令将从当前目录中读取以 xa* 开头的所有文件,并且 sed 应该更改文件内容。

你的意思是 sed -i 's/abc/xyz/g' xa*
这里的答案是不够的。请参阅unix.stackexchange.com/questions/112023/…
这是一次更新多个文件的另一个答案:unix.stackexchange.com/questions/29268/…
我做了@PaulR 解决方案并且它有效,但我不明白的是所有这些复杂的其他答案!您的解决方案缺少什么?
@SoheilRahsaz 有时当文件太多时,shell 会抱怨参数列表长度,如最佳答案所示

e
ealfonso

我很惊讶没有人提到 find 的 -exec 参数,它适用于这种类型的用例,尽管它会为每个匹配的文件名启动一个进程:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

或者,可以使用 xargs,它会调用更少的进程:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

或者更简单地在 find 中使用 + exec variant 而不是 ; 以允许 find 为每个子进程调用提供多个文件:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +

我不得不像这样修改这个答案中的命令:find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \; 这是查找命令 ./ 的位置,并且是 OSX 的 -i 之后的一对单引号。
find 命令的工作原理是由 elfonso 提供的,./ 等于 . 并且在 -i 之后只有 backupsuffix 参数。
find 的 -exec 选项和 {} + 足以解决上述问题,并且可以满足大多数要求。但是 xargs 通常是更好的选择,因为它还允许使用 -p 选项进行并行处理。当您的 glob 扩展大到足以超出命令行长度时,您可能还会受益于顺序运行的加速。
l
lenik

更好的是:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

因为没有人知道那里有多少文件,而且很容易打破命令行限制。

当文件太多时会发生以下情况:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#

如果有那么多文件,您将打破 for 命令中的命令行限制。为了保护自己免受这种情况的影响,您必须使用 find ... | xargs ...
我不知道实现,但“xa *”模式确实必须在某个时候得到扩展。 shell 对 for 的扩展与对 echogrep 的扩展是否不同?
查看更新的答案。如果您需要更多信息,请提出官方问题,以便人们可以帮助您。
在 sed 命令中,您需要使用 "$i" 而不是 $i 以避免对带有空格的文件名进行分词。否则这是非常好的。
关于列表,我认为不同之处在于 for 是语言语法的一部分,甚至不仅仅是内置的。对于 sed -i 's/old/new' ** 的扩展必须全部作为 arglist 传递给 sed,我相当肯定这必须在 sed 进程启动之前发生。使用 for 循环,完整的 arglist(* 的扩展)永远不会作为命令传递,只存储在 shell 内存中并迭代。不过,我对此没有任何参考,但这似乎很可能是不同的。 (我很想听听知识渊博的人...)
R
Raj Shenoy

您可以同时使用 grep 和 sed。这允许您递归搜索子目录。

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)

这种方法对我来说的好处是我可以加入 grep -v 以避免 git 文件夹 grep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
mac的最佳解决方案!
s
slm

这些命令在 Mac OS X 附带的默认 sed 中不起作用。

man 1 sed

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

试过了

sed -i '.bak' 's/old/new/g' logfile*

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

两者都工作正常。


@sumek 这是 OS X 上的一个示例终端会话,显示 sed 替换了所有出现:GitHub Gist
我用它来用下面的一行替换我所有网站配置文件中的两个不同的行。 sed -i.bak "s/supercache_proxy_config/proxy_includes\/supercache_config/g; s/basic_proxy_config/proxy_include\/basic_proxy_config/g" sites-available/* 完成文件后不要忘记删除 *.bak 文件系统卫生着想。
C
Community

@PaulR 将此作为评论发布,但人们应将其视为答案(此答案最适合我的需要):

sed -i 's/abc/xyz/g' xa*

这适用于中等数量的文件,可能在几十个数量级,但是 probably not on the order of millions


假设您的替换中有正斜杠。文件路径 sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn 的另一个示例。
d
dkinzer

另一种更通用的方法是使用 find

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')

该 find 命令的输出被扩展,所以这并不能解决问题。相反,您应该使用 -exec
@erjoalgo 之所以有效,是因为 sed 命令可以处理多个输入文件。正是需要扩展 find 命令才能使其工作。
只要文件数量不超过命令行限制,它就可以工作。
该限制仅取决于机器可用的内存资源,并且与 exec 的限制完全相同。
那明显是错的。在上面的命令中, $(find . ...) 被扩展为一个命令,如果有很多匹配的文件,这个命令可能会很长。如果它太长(例如在我的系统中限制大约为 2097152 个字符),您可能会收到错误:“参数列表太长”并且命令将失败。请谷歌此错误以获取有关此的一些背景信息。
B
Bluesboy

我正在使用 find 来完成类似的任务。这很简单:您必须将它作为 sed 的参数传递,如下所示:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

这样您就不必编写复杂的循环,而且很容易看出要更改哪些文件,只需在运行 sed 之前运行 find


这与 @dkinzer’s answer 完全相同。
M
Mohamed Galal

你可以做

'xxxx' 文本 u 搜索并将其替换为 'yyyy'

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'

P
Paul M.

上面有一些很好的答案。我想我会再添加一个简洁且可并行化的,使用 GNU 并行,我通常更喜欢 xargs

parallel sed -i 's/abc/xyz/g' {} ::: xa*

将此与 -j N 选项结合使用可并行运行 N 个作业。


L
Lejuanjowski

如果您能够运行脚本,这是我在类似情况下所做的:

使用字典/hashMap(关联数组)和 sed 命令的变量,我们可以循环遍历数组以替换多个字符串。在 name_pattern 中包含通配符将允许用特定目录 (source_dir) 中的模式(可能类似于 name_pattern='File*.txt' )替换文件中的文件。所有更改都写入 destin_dir 中的 logfile

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

首先声明pairs数组,每对是一个替换字符串,然后将WHAT1替换为FOR1,将OTHER_string_to replace替换为文件File.txt中的string replaced。在循环中读取数组,该对的第一个成员被检索为 replace_what=$i,第二个成员被检索为 replace_for=$jfind 命令在目录中搜索文件名(可能包含通配符),sed -i 命令在相同文件中替换先前定义的内容。最后,我添加了一个重定向到日志文件的 grep 以记录文件中所做的更改。

这在 GNU Bash 4.3 sed 4.2.2 中对我有用,并且基于 VasyaNovikov 对 Loop over tuples in bash 的回答。