以下命令正确更改了 2 个文件的内容。
sed -i 's/abc/xyz/g' xaa1 xab1
但我需要做的是动态更改几个这样的文件,我不知道文件名。我想编写一个命令,该命令将从当前目录中读取以 xa*
开头的所有文件,并且 sed
应该更改文件内容。
sed -i 's/abc/xyz/g' xa*
?
我很惊讶没有人提到 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' {} +
更好的是:
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 ...
for
的扩展与对 echo
或 grep
的扩展是否不同?
"$i"
而不是 $i
以避免对带有空格的文件名进行分词。否则这是非常好的。
for
是语言语法的一部分,甚至不仅仅是内置的。对于 sed -i 's/old/new' *
,*
的扩展必须全部作为 arglist 传递给 sed,我相当肯定这必须在 sed
进程启动之前发生。使用 for
循环,完整的 arglist(*
的扩展)永远不会作为命令传递,只存储在 shell 内存中并迭代。不过,我对此没有任何参考,但这似乎很可能是不同的。 (我很想听听知识渊博的人...)
您可以同时使用 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 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
两者都工作正常。
@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
的另一个示例。
另一种更通用的方法是使用 find
:
sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')
我正在使用 find
来完成类似的任务。这很简单:您必须将它作为 sed
的参数传递,如下所示:
sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`
这样您就不必编写复杂的循环,而且很容易看出要更改哪些文件,只需在运行 sed
之前运行 find
。
你可以做
'xxxx' 文本 u 搜索并将其替换为 'yyyy'
grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'
上面有一些很好的答案。我想我会再添加一个简洁且可并行化的,使用 GNU 并行,我通常更喜欢 xargs
:
parallel sed -i 's/abc/xyz/g' {} ::: xa*
将此与 -j N
选项结合使用可并行运行 N
个作业。
如果您能够运行脚本,这是我在类似情况下所做的:
使用字典/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=$j
。 find
命令在目录中搜索文件名(可能包含通配符),sed -i
命令在相同文件中替换先前定义的内容。最后,我添加了一个重定向到日志文件的 grep
以记录文件中所做的更改。
这在 GNU Bash 4.3
sed 4.2.2
中对我有用,并且基于 VasyaNovikov 对 Loop over tuples in bash 的回答。
不定期副业成功案例分享
find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;
这是查找命令./
的位置,并且是 OSX 的-i
之后的一对单引号。./
等于.
并且在-i
之后只有 backupsuffix 参数。-exec
选项和{} +
足以解决上述问题,并且可以满足大多数要求。但是xargs
通常是更好的选择,因为它还允许使用-p
选项进行并行处理。当您的 glob 扩展大到足以超出命令行长度时,您可能还会受益于顺序运行的加速。