查找并替换字符串并在更改时打印文件目录

时间:2019-04-13 02:29:18

标签: shell sed replace terminal find

我正在使用findsed替换多个文件中的字符串。这是我的脚本:

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

反正有做这样的事情吗?

5 个答案:

答案 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

在一个文件结构中,该文件结构包含当前目录中的脚本以及一个子目录d1f的多个副本,例如

$ 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。...