使用 find 和 sed 时如何获取文件名

使用 find 和 sed 时如何获取文件名

我正在编写一个脚本,将 sed 应用于某些文件,然后列出已更改的文件,以便我知道哪些文件已被修改。

这就是我找到然后使用 sed 的方式:

find . -type f -a \( -name "*.txt" -o -name "*.git"\) -a -exec sed -i -e "s/"str1"/"str2"/g" {} +

如何打印更改后的文件的文件名?我想按排序顺序打印它,以便更容易阅读。

当仅使用 sed 时,我们可以这样做:

sed -i 's/$pattern/$new_pattern/w changelog.txt' $filename
if [ -s changelog.txt ]; then
  # CHANGES MADE, DO SOME STUFF HERE
else
  # NO CHANGES MADE, DO SOME OTHER STUFF HERE
fi

但是当同时使用 find 和 sed 时我该如何做到这一点呢?我检查了手册页并尝试了很多东西,但没有任何效果。

答案1

sed -i重写文件(实际上制作文件的完整新副本),无论脚本s中的任何命令sed是否成功。

在这里,您希望避免sed -i在不包含str1.使用 GNU 工具:

find . -type f \( -name "*.txt" -o -name "*.git" \) -size +3c \
  -exec grep -lZ str1 {} + |
  while IFS= read -rd '' file; do
    sed -i 's/str1/str2/g' "$file" &&
      printf '%s\n' "$file"
  done

如果成功(在创建文件的新版本时没有出现错误),则为sed包含文件名的每个文件运行一个str1并打印文件名。sed

或者您可以为每个文件运行grep一个sed

find . -type f \( -name "*.txt" -o -name "*.git" \) \( -size +3c \
  -exec grep -q str1 {} \; \
  -exec sed -i 's/str1/str2/g' {} \; \
  -printf '"%p" was modified\n' \
    -o -printf '"%p" was not modified\n"' \)

答案2

您的sed命令(正确引用):

sed 's/str1/str2/g'

这会将所有出现的 更改str1str2。包含的文件列表str1可以从以下位置获得grep -l 'str1'

find . -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec grep -l 'str1' {} \; \
    -exec sed -i 's/str1/str2/g' {} + >changelist.txt

此处,grep -l将提供将重定向到changelist.txt.它还将充当过滤器,sed以便sed仅在包含该模式的文件上运行。 sed -i然后将在文件中进行更改(并保持安静)。

或者,让find打印包含该字符串的文件的路径名:

find . -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec grep -q 'str1' {} \; \
    -print \
    -exec sed -i 's/str1/str2/g' {} + >changelist.txt

有关的:

答案3

我回答这个问题的方式与 善行难陀为了它的乐趣。所以,如果你喜欢这个,你应该为他投票。这有点不同,因为它向您展示了如何执行多个命令并处理更多的复杂性,同时仍然只调用一次 find 。

答案

如果找到匹配项(即 $? == 0),Grep 将评估为 True。因此,grep -l 'str1' filename如果 str1 在文件名中,则为 true。如果我们将此命令链接到 sed 命令,&&我们可以确保 sed 仅在 grep 匹配时运行。

仅当 sed 要进行更改时,以下命令才会输出文件名:

grep -l 'str1' filename && sed -i 's/str1/str2/g' filename

您不能&&直接在 -exec 中使用,因此我们将其包装在对 bash 的调用中。

find ./ -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec bash -c "grep -l 'str1' {} && sed -i 's/str1/str2/g' {}" \; > changelist.txt

这与 Kusalananda 的答案明显不同的是,如果 grep 与 str1 不匹配,sed 甚至不会运行。在 Kusalananda 的回答中,grep 为每个文件运行,sed 为每个文件运行。根据文件的数量,这可能会对执行时间产生巨大的影响。对于OP的问题,虽然它可能根本不会有太大区别。

可以通过替换grep -qgrep -l、替换+\;和删除来简化他的答案-print

find . -type f \( -name '*.txt' -o -name '*.git' \) \
    -exec grep -l 'str1' {} \; \
    -exec sed -i 's/str1/str2/g' {} \; >changelist.txt

所有这些都只是吹毛求疵。那么接下来就是我使用bash -cin find-exec选项的原因。我希望有人会觉得它有用。

我的方法的原因

我在这里是因为我想将 sed 与 find 一起使用来打印日志文件的一部分,并仅在 sed 输出任何内容时才打印日志文件的名称。

我有一些日志包含这样的内容:

    ---- lots of lines before ----
Failed:    0

--------------------------------------------------
Summary
--------------------------------------------------
( Cases/Passed/Failed)
Frequency Test           : (    69/    67/     2)
Carrier/Data Null Test   : (    14/    13/     1)
Total Harmonic Distortion: (     9/     9/     0)
Spur Test                : (     0/     0/     0)

--------------------------------------------------
failed Test
--------------------------------------------------
freq, rf2, 0.750e9, -70.0, pm 500,    pm 1.0
    ---- lots of lines after ----

我只想仅在 sed 检测到测试摘要时才打印测试摘要和文件名。

因此,对于一堆文件,我想要这样的输出:

File: ./4662-0003-05132021-0953.log
Summary
--------------------------------------------------
( Cases/Passed/Failed)
Frequency Test           : (    69/     0/    69)
Carrier/Data Null Test   : (    14/     0/    14)
Total Harmonic Distortion: (     9/     9/     0)
Spur Test                : (     0/     0/     0)

File: ./4745-0001-05132021-1017.log
Summary
--------------------------------------------------
( Cases/Passed/Failed)
Frequency Test           : (    69/    68/     1)
Carrier/Data Null Test   : (    14/    14/     0)
Total Harmonic Distortion: (     9/     9/     0)
Spur Test                : (     0/     0/     0)

我用这个命令实现了这一点:

find ./ -type f -name '*.log' \
    -exec bash -c "grep -q Summary {} && echo 'File: {}' && sed -n '/Summary/,/Spur/p' {} && echo" \;

分解它,grep -q Summary ()如果日志文件中没有出现摘要,则后面的任何内容都不会运行。 sed -n '/Summary/,/Spur/p'只会打印“Summary”和“Spur”之间的日志部分。

-exec cmd {} 之间的区别;和-exec cmd +

您可能想知道为什么我使用\;而不是+.如果您使用+,则 {} 将被替换为命令行中可以容纳的尽可能多的文件名。这不是我们想要的,在这种情况下 find 甚至不允许这样做。

从人身上发现:

   -exec command {} +
          This variant of the -exec action runs the specified command on the selected files, but the command line is built  by
          appending  each selected file name at the end; the total number of invocations of the command will be much less than
          the number of matched files.  The command line is built in much the same way that xargs builds  its  command  lines.
          Only  one  instance  of  `{}' is allowed within the command.  The command is executed in the starting directory.  If
          find encounters an error, this can sometimes cause an immediate exit, so some pending commands may  not  be  run  at
          all.  This variant of -exec always returns true.

结论

抱歉这本小说,但我希望它对某人有所帮助。

答案4

exec编写一个执行您想要的操作的小脚本并将该脚本作为 的参数应该很容易find。您已经有了该脚本,如果您替换$filename$1,则您已经拥有了它。您的脚本将采用以下形式

#!/bin/bash
sed -i 's/$pattern/$new_pattern/' $1
echo $1 >> changelog

我们称这个脚本为ed_notify。现在,您可以通过以下方式在选定的文件上运行它

cat changelog >> changelog.old
rm changelog
find . -type f -a \( -name "*.txt" -o -name "*.git"\) -a -exec ed_notify {} \;

相关内容