【问题标题】:How can execute sed on file by file basis using result of find?如何使用 find 的结果逐个文件执行 sed?
【发布时间】:2018-07-15 12:46:48
【问题描述】:

我需要通过搜索模式对文件执行替换,并且替换是通过执行另一个提供要用作替换的字符串的命令来获得的。但是,这也需要对目录中的每个文件进行。以下语法不起作用,因为 sed 没有输入文件。

find . type f -exec sed -i "s/<pattern1>/$(sed -n 's/abcd\(pattern2\)efgh/\1/p')/g" {} \;

这个想法是内部 sed 返回 pattern2 以供外部 sed 用作 pattern1 的替代品。但是我需要对目录中的每个文件执行此操作。我无法弄清楚语法。

如果我对内部 sed 使用 find,

find . type f -exec sed -i "s/<pattern1>/$(find . -type f -exec sed -n 's/abcd\(pattern2\)efgh/\1/p' {} \;)/g" {} \;

我以错误结束 sed: -e expression #1, char 53: unterminated 's' command.

我能得到一些帮助吗?

【问题讨论】:

  • 我很难理解。 :) 所以对于一个文件,你必须用 N 个文件的 N-x 替换结果替换 1 个模式?对于内部模式,每个文件是否总是有一个匹配项或至少一个匹配项?外部搜索和替换循环的模式列表是否相同?提取模式的文件是否与要修改的文件相同?你能用简单的模式展示一下修改前后的样本数据吗,3-4个文件的2-3行。
  • 您可以尝试对文件进行循环。在循环中,您运行 sed,它为您提供 pattern2 并在第二个独立命令中为 patern1 应用 pattern2。
  • 有没有涉及到子目录?
  • 您希望内部sed 从哪里接收输入?
  • @tripleee:这正是要指定的问题

标签: regex linux sed find


【解决方案1】:

如有疑问,请牺牲简洁性以提高可读性。这是一个临时脚本,ad-hoc.sh(在 cmets 和聊天中讨论后):

#!/bin/bash
file="$1"
patternInner=$(sed -n -r "s/.*class\s*(.*)\s*:\s*public.*/\1/p" "$file")
if [[  -n "$patternInner" ]]
then
    echo -e "$patternInner matching in $file" >> findsed.log 
    # sed -i "s/void\s*\(test.*\)\s*()/MWTEST($patternInner, \1)/" "$file"
    sed "s/void\s*\(test.*\)\s*()/MWTEST($patternInner, \1)/" "$file"
else
    echo patternInner empty for file $file >&2
fi

修改:来自现实生活的 sed 表达式。测试非空patternInner。

完成某种记录,如果找到模式,文件名,匹配的模式。

别忘了chmod a+x ad-hoc.sh。 如果测试看起来有效,您必须使用 sed -i 而不是没有 -i 的那一行来激活该行,以使更改不仅发生在屏幕上,而且发生在文件中。

用法:

for f in *.?pp # would work for a flat directory as well as find.
do
    ./ad-hoc.sh "$f"
done 2> forsed.err

如果你坚持的话,也可以用 find:

find . -type f -name "*.?pp" -exec ./ad-hoc.sh {} ";" 2> findsed.err

当然也可以放在单引号中,用

执行
... -exec bash -c '....(ad-hoc.sh 
     code 
     here)' {} ";" 

但在这种情况下,我更喜欢临时脚本。减少掩​​蔽麻烦。更好的可编辑性,更好的可测试性。

根据 $patternInner 的可变性,必须考虑屏蔽,但我希望它可能只是字符和数字,没有美元、星号和反斜杠。

【讨论】:

  • 谢谢。但我认为我的第二行可能表明我正在执行嵌套操作,但事实并非如此。我正在尝试将使用内部 sed 的操作应用到一个文件,并将结果传递给外部 sed,外部 sed 在同一文件上执行其部分操作,一次 1 个文件。你的例子是否表明了这一点?我感觉你以为我一次将内部 sed 应用于所有内部文件。实际上并非如此。它一次在内部和外部 1 个文件上完成,并且在 find 返回的所有文件上都在同一个文件上完成。我的问题是找到该语法
  • 好吧,我把这个丑陋的东西扔进垃圾箱,它属于它。 :)
  • 非常感谢您的更新!对此,我真的非常感激。上面的解决方案也与您的类似。我不确定该接受哪个。如果我接受上述内容可以吗,因为它没有关于命令本身的更多详细信息?
  • 接受,随便你。这完全是您的选择和标准。
  • 我(在我们的聊天中提到的)关于无法运行脚本中的命令的问题与脚本可能包含一些控制字符有关,因为它是在 Windows 上创建的第一次。我通过在 Linux 上创建一个新文件并将命令放在那里解决了这个问题。接受这个答案,因为它解决了我的问题。
【解决方案2】:

如果您假设每个找到的文件将包含一个 单个 匹配 abcd\(pattern2\)efgh 并且您想从那里拉出 pattern2 并将其用作替换 pattern1 的东西在同一个文件中,这就像用户未知的原始答案一样。这是重新粉刷那个特定自行车棚的尝试。

find . -type f -exec sh -c 'for f; do
    sed -n "s%abcd\\(pattern2\\)efgh%s/<pattern1>/\\1/g%p" "$f" |
        sed -i -f - "$f"; done' _ {} +

这里可以突出显示一些内容。

  • 我们在sed 替换中使用了不同的分隔符,因此我们可以明确地嵌套它们。
    • 如果%/ 可能出现在模式中,显然选择另一个分隔符。
  • 因为我们不能嵌套单引号,所以我在sed 脚本周围使用双引号。这意味着所有的反斜杠都必须加倍,以防止它们被双引号“吃掉”。
  • 我们将find 结果传递给一个简单的sh -c 脚本。
    • 为了提高效率,我将其重构为循环查找的文件,这样您就可以避免为每个找到的文件生成一个新的 shell 进程。简单来说,如果没有循环,这将是 find ... -exec sh -c 'sed -n "s%foo%s/moo/bar/%p" "$1" | sed -i -f - "$1"' _ {} \;
  • sed -f - 表示从标准输入读取脚本。这不是便携式的。
    • 在这不起作用的平台上,尝试使用/dev/stdin 而不是-
    • 但是,例如在 MacOS 上,默认的 sed 只是拒绝从标准输入读取脚本。使用临时文件或其他解决方法。

【讨论】:

  • 这简直是令人头晕目眩的紧凑。即使盯着它看一分钟,它仍然有很多东西要打开——但绝对很酷。这肯定是个新把戏。
  • 非常感谢您的解决方案!我不熟悉 sed 语法的各个方面,但希望您的解决方案能帮助我了解更多。我的原始命令与现在一样 find . -type f -exec sh -c 'for f; do sed -n 's%.*class\s*\(.*\)\s*:\s*public%s/void\s*\(test.*\)()/MWTEST(\1, \2)/g%p' "$f" | sed -f - "$f"; done' _ {} + 但我从 bash /bin/bash -c 收到语法错误:第 0 行:意外标记附近的语法错误 `(' 即使嵌套,命令中的引号也可以吗?
  • 你不能这样嵌套单引号; 'foo'bar'baz' 在两个单引号字符串之间包含一个未加引号的裸标记 bar
  • 是的,我了解,但在您的解决方案中,您也有嵌套引号正确吗?具有 ... -exec sh -c 'for f; do sed -n 's%abcd\(pattern2\)efgh ... 引号的部分表示 sh 的开始和 sed 的开始。
  • 哎呀,是的,对不起。更新了解决该问题的答案。
猜你喜欢
  • 2016-07-24
  • 2020-09-27
  • 2015-09-26
  • 1970-01-01
  • 2014-02-12
  • 2015-01-13
  • 2015-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多