更改多个文件


194

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

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

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


61
你的意思是sed -i 's/abc/xyz/g' xa*
Paul R

Answers:


135

更好的是:

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)
#

18
如果有那么多文件,您将打破命令中的命令行限制for。为了保护自己免受find ... | xargs ...
侵扰

1
我不知道实现方式,但是“ xa *”模式确实必须在某个时候进行扩展。请问做外壳扩展为不同的for比它的echo还是grep
格伦·杰克曼

4
查看更新后的答案。如果您需要更多信息,请提出一个正式问题,以便其他人可以为您提供帮助。
lenik 2012年

5
在sed命令中,您需要使用"$i"而不是$i来避免带空格的文件名拆分单词。否则,这是非常好的。
2015年

4
关于列表,我相信区别在于这for是语言语法的一部分,甚至不仅仅是内置语法。对于sed -i 's/old/new' **必须将sall 的扩展作为arglist传递给sed,而且我很确定,这必须在sed进程启动之前进行。使用for循环,完整的arglist(的扩展名*)永远不会作为命令传递,仅存储在shell内存中并进行迭代。我对此完全没有任何参考,这似乎是有区别的。(我很想听听其他知识渊博的人的话...)
Wildcard'Nov

165

我很惊讶没有人提到要查找的-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'

或者更简单地使用+ exec变量而不是;in find来允许find为每个子进程调用提供多个文件:

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

7
我必须像这样修改答案中的命令:find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;这是find命令的位置,后跟OSX ./的一对单引号-i
shelbydz

find命令像由ealfonso提供的那样起作用,./等于.-i仅具有backupsuffix参数。
uhausbrand '18 -10-5

-execfind和find 的选项{} +足以解决所述问题,并且对于大多数需求来说应该是不错的选择。但是xargs通常是更好的选择,因为它还允许与该-p选项并行处理。当您的全局扩展足够大以至于使命令行长度溢出时,您可能还会从顺序运行的加速中受益。
阿米特·奈杜

78

您可以一起使用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)

3
这个方法对我的奖励是我能滑在grep -v避免git的文件夹grep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
马丁莱恩

Mac的最佳解决方案!
马奎斯·布朗特

30

这些命令在sedMac OS X随附的默认设置中不起作用。

来自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

两者都很好。


2
@sumek这是OS X上的示例终端会话,该示例显示sed替换了所有
实例

我用它用下面的一个衬里替换了我所有网站配置文件中的两个不同行。sed -i.bak“ s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g”站点可用/ *完成文件操作后,不要忘记删除* .bak文件系统卫生的缘故。
Josiah 2015年


9

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

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

1
该find命令的输出得到扩展,因此无法解决该问题。相反,您应该使用-exec
ealfonso 2015年

@erjoalgo之所以有效,是因为sed命令可以处理多个输入文件。确实需要扩展find命令才能使其正常运行。
dkinzer 2015年

只要文件数不超过命令行限制,它就起作用。
ealfonso

该限制仅取决于计算机可用的内存资源,并且与exec的限制完全相同。
dkinzer

4
那明显是错的。在上面的命令中,$(find。...)扩展为单个命令,如果有许多匹配文件,这可能会很长。如果太长(例如,在我的系统中,限制为2097152个字符),您可能会收到错误消息:“参数列表太长”,命令将失败。请谷歌这个错误,以获取一些背景。
ealfonso

2

我正在find执行类似的任务。这很简单:您必须sed像这样将其作为参数传递:

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

这样,您不必编写复杂的循环,并且很容易看到要更改的文件,而仅在运行find之前运行sed


1
这与@dkinzer的答案完全相同。
陶先生


0

如果您能够运行脚本,这是我针对类似情况所做的操作:

使用字典/ hashMap(关联数组)和sed命令变量,我们可以遍历数组以替换几个字符串。在中包含通配符name_pattern将允许name_pattern='File*.txt'使用特定目录(source_dir)中的模式(例如)替换文件中的原位。所有的变化都写在logfiledestin_dir

#!/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 replacestring replaced在file中被替换为File.txt。在循环中,读取数组,该对的第一个成员检索为replace_what=$i,第二个检索为replace_for=$j。该find命令在目录中搜索文件名(可能包含通配符),并且该sed -i命令将替换先前定义的文件。最后我加了一个grep日志文件重定向,以记录在文件中所做的更改。

这对我GNU Bash 4.3 sed 4.2.2有用,并基于VasyaNovikov对bash中的元组进行循环的答案 。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.