我正在使用find
和sed
替换多个文件中的字符串。这是我的脚本:
find ./ -type f -name "*.html" -maxdepth 1 -exec sed -i '' "s/${REPLACE_STRING}/${STRING}/g" {} \; -print
-print
始终打印文件,无论是否更改了内容。我想看看哪些文件已更改。理想情况下,我希望输出是这样的(因为文件正在更改):
/path/to/file was changed
- REPLACE STRING line 9 was changed
- REPLACE STRING line 12 was changed
- REPLACE STRING line 26 was changed
/path/to/file2 was changed
- REPLACE STRING line 1 was changed
- REPLACE STRING line 6 was changed
- REPLACE STRING line 36 was changed
反正有做这样的事情吗?
答案 0 :(得分:0)
好主意。由于您提到的原因,我认为-print
是死胡同,因此需要在exec
中完成。由于打印到sed
以及修改文件的挑战,我认为STDOUT
也是死路一条。因此,自然的扩展是在其周围包装一些Perl。
如果这是您的exec
语句,怎么办?
perl -p -i -e '$i=1 if not defined($i); print STDOUT "$ARGV, line $i: $_" if s/REPLACE_STRING/STRING/; $i++' {} \;
-p
将Perl语句包装在标准while(<>)
循环中,以便像sed一样逐行处理文件。-i
像sed一样进行就地替换。-e
意味着执行以下Perl语句。if not defined
是初始化行数变量的一种偷偷摸摸的方法,即使它是针对每一行执行的。STDOUT
告诉print
输出到控制台而不是文件。$ARGV
读取时,<>
是当前文件名。$_
是正在处理的行。if
意味着print
仅在找到匹配项后执行。对于包含以下内容的输入文件text.txt
:
line 1
token 2
line 3
token 4
line 5
perl -p -i -e '$i=1 if not defined($i); print STDOUT "$ARGV, line $i: $_" if s/token/sub/; $i++' text.txt
语句给我:
text.txt, line 2: sub 2
text.txt, line 4: sub 4
让text.txt
包含:
line 1
sub 2
line 3
sub 4
line 5
所以您不会得到介绍性的“文件已更改”这一行,但是对于单线用户,我认为这是一个很好的折衷。
在几个文件上运行看起来像这样:
find ./ -type f -name "*.txt" -maxdepth 1 -exec perl -p -i -e '$i=1 if not defined($i); print STDOUT "$ARGV, line $i: $_" if s/token/sub/; $i++' {} \;
.//text1.txt, line 2: sub 2
.//text1.txt, line 4: sub 4
.//text2.txt, line 1: sub 1
.//text2.txt, line 3: sub 3
.//text2.txt, line 5: sub 5
答案 1 :(得分:0)
您可以链接-exec
个动作并利用退出状态。例如:
find . \
-maxdepth 1 \
-type f \
-name '*.html' \
-exec grep -Hn "$REPLACE_STRING" {} \; \
-exec sed -i '' "s/${REPLACE_STRING}/${STRING}/g" {} \;
这将为每个匹配的文件打印路径,行号和行:
./file1.html:9:contents of line 9
./file1.html:12:contents of line 12
./file1.html:26:contents of line 26
./file2.html:1:contents of line 1
./file2.html:6:contents of line 6
./file2.html:36:contents of line 36
对于不匹配的文件,不会发生其他任何事情;对于匹配的文件,将调用sed命令。
如果您希望输出的内容更接近问题的内容,则可以添加一些操作:
find . \
-maxdepth 1 \
-type f \
-name '*.html' \
-exec grep -q "$REPLACE_STRING" {} \; \
-printf '%p was changed\n' \
-exec grep -n "$REPLACE_STRING" {} \; \
-exec sed -i '' "s/${REPLACE_STRING}/${STRING}/g" {} \; \
| sed -E "s/^([[:digit:]]+):.*/ - $REPLACE_STRING line \1 was changed/"
现在,它首先使用grep -q
静默检查文件是否包含字符串,然后打印文件名(-printf
),然后打印所有匹配的行号(grep -n
) ,然后用sed进行替换,最后用sed稍微修改输出。
由于您使用的是sed -i ''
,因此我假设您使用的是macOS;我不确定那里的股票find
是否支持printf
选项。
现在,我们已经接近在每个匹配的文件上运行复杂的脚本,因此我们也可以直接这样做:
shopt -s nullglob
for f in ./*.html; do
if grep -q "$REPLACE_STRING" "$f"; then
printf '%s\n' "$f was changed"
grep -n "$REPLACE_STRING" "$f" \
| sed -E "s/^([[:digit:]]+):.*/ - $REPLACE_STRING line \1 was changed/"
sed -i '' "s/${REPLACE_STRING}/${STRING}/g" "$f"
fi
done
答案 2 :(得分:0)
替换您的find + sed命令:
find ./ -type f -name "*.html" -maxdepth 1 -exec sed -i '' "s/${REPLACE_STRING}/${STRING}/g" {} \; -print
使用此GNU awk命令(需要gawk进行就地编辑):
gawk -i inplace -v old="$REPLACE_STRING" -v new="$STRING" '
FNR==1 { hdr=FILENAME " was changed\n" }
gsub(old,new) { printf "%s - %s line %d was changed\n", hdr, old, FNR | "cat>&2"; hdr="" }
1' *.html
如果需要,您还可以使用awk使其比sed更健壮,因为awk可以支持文字字符串,而sed不能支持
答案 3 :(得分:0)
好吧,为了提高效率,请始终使用Ed的awk
脚本,但是继续使用sed
的初步调用来确定您的文件是否包含helper
+ grep
脚本要替换的单词,您可以使用简短的辅助脚本,将${REPLACE_STRING}
,${STRING}
和filename
用作前三个位置参数,如下所示:
助手脚本名为helper.sh
#!/bin/sh
test -z "$1" && exit
test -z "$2" && exit
test -z "$3" && exit
findw="$1"
replw="$2"
fname="$3"
grep -q "$findw" "$fname" || exit
echo "$(readlink -f $fname) was changed"
grep -n "$findw" "$fname" | {
while read line; do
printf -- " - REPLACE STRING line %d was changed\n" "${line%:*}"
done }
sed -i "s/$findw/$replw/g" "$fname"
那么您对find
的呼叫可能是:
find . -type f -name "f*" -exec ./helper.sh "dog" "cat" '{}' \;
使用/输出示例
从几个名为f
的文件开始,其中包含:
$ cat f
my
dog
dog
has
fleas
在一个文件结构中,该文件结构包含当前目录中的脚本以及一个子目录d1
和f
的多个副本,例如
$ tree .
.
├── d1
│ └── f
├── f
└── helper.sh
运行脚本会产生以下结果:
$ find . -type f -name "f*" -exec ./helper.sh "dog" "cat" '{}' \;
/tmp/tmp-david/f was changed
- REPLACE STRING line 2 was changed
- REPLACE STRING line 3 was changed
/tmp/tmp-david/d1/f was changed
- REPLACE STRING line 2 was changed
- REPLACE STRING line 3 was changed
,f
的内容也会相应更改
$ cat f
my
cat
cat
has
fleas
如果在find
所在的任何文件中均未找到搜索词,则这些文件的修改时间将保持不变。
现在请记住,如果有gawk
可用,请按照Ed的建议进行,但是-您可以使用sed
和助手:)
答案 4 :(得分:0)
免费轻松安装Perl
,在bash shell上定义自己的字符串并在此处进行测试:
STRING=
REPLACE=
perl -ne 'foreach(`find . -maxdepth 1 -type f -iname "*.html"`){ open IH,$_ or die "Error $!"; print "Processing: $_";while (<IH>) {$s=$_;$t=s/$REPLACE/$STRING/; print "$s --> $_" if $t };print "Nothing replaced" if !$t}'
要真正对其进行编辑,请添加-i选项,使其为perl -i -ne
。...