计算并返回文件中使用 sed 完成的替换次数

计算并返回文件中使用 sed 完成的替换次数

我有一个很大的 JSON 文件,我想用其中的一个字符串替换另一个字符串。不应该,但可能会在我不想替换它的上下文中使用该字符串。

我知道它在正确上下文中的文件中出现了多少次,因此我还想打印该字符串的出现次数被替换为sed.我怎样才能做到这一点?

为了查找和替换我使用的字符串:

sed -i "" "s/my_string/new_string/g" my_file.json

请注意,我使用的是 mac,但我也需要 Linux,例如:

sed -i "s/my_string/new_string/g" my_file.json

我知道我可以运行 agrep来查找文件中的字符串并返回计数,如下所示:

grep -o my_string my_file.json | wc -l

但这不是我要问的。我问是否有一种方法可以像任何文本编辑器(word,记事本,geany,...)那样做 - 如果我给出一个字符串,它会告诉我它看到这个字符串并替换它的次数和另外一个。

更多信息——它将在 Bash 脚本中运行,因此如果有其他更好的方法,我对此持开放态度。

答案1

就用perl代替吧。它几乎和 和 替换运算符一样可移植sed(在这种情况下更可移植,因为语法在任何已perl安装的机器上都是相同的),-i并且可以被告知打印替换数量:

perl -i  -lpe '$k+= s/my_string/new_string/g; END{print "$k"}' my_file.json

这将就地进行替换并将替换数打印到标准输出。

答案2

如果您必须使用 sed 那么一种方法可能是

根据@Phillipos 的建议,将其更改为:

sed -i "" -e '
  s/my_string/new_string\
/g;s/\n//w /dev/stdout
  s///g
' my_file.json | wc -l
  • 在每次出现 my_string 之后放置一个换行符 + 也进行更改。
  • 然后去掉一个换行符,因为 sed 在打印时隐式地添加了一个换行符。
  • 仅当替换成功时,才会有条件地写入 stdout,即该行包含 my_string mng 时
  • 然后我们去掉换行标记。

答案3

您可以强制vim编辑器以流式传输模式进行报告:

ex -nsc 'redir! >/dev/stderr' -c '%s/pattern/PATTERN/g' -c 'redir END' -c 'wq' my_file

3 substitutions on 2 lines

ex-vim 模式(或 vim -e)
-n--不创建交换文件
-s-脚本
-c 命令行模式(或 +'command')
'redir! >/dev/stderr'--重定向到 shell 标准错误
'redir END'-重定向结束。可以省略
'wq'-保存更改并退出编辑器。如果将其替换为'q!',则无需更改文件即可获取输出并与预期进行比较。

答案4

这是一个简单的 awk 版本,用于计算更改和受影响的行:

#! /bin/bash

Awk='
BEGIN { fmtEnd = "Made %d substitutions on %d lines.\n"; }
{
    n = gsub (/exit/, "return");
    if (n) { Lines++; Count += n; }
    print;
}
END { printf (fmtEnd, Count, Lines) > "/dev/stderr"; }
'
    awk "${Awk}" doFifo > doFifo.fix

输出(stderr)就是这样,可以重新排列它以使其更容易恢复计数:

Made 8 substitutions on 6 lines.

GNU/awk 确实有 -i inplace 扩展,但我对就地更新持极其保守的态度。我的客户经常抱怨,并声称他们的数据始终 100% 正确,因此我保留审计跟踪和每个数据版本。

下面是一个 awk 变体,它记录了每个更改的行。这仍然不是生产级别:我希望它接受模式和替换作为参数,在一次运行中处理多个文件,根据输入命名输出文件,并按文件和总体总数进行汇总。也许也允许模式数组 -> 替换。

#! /bin/bash

AwkFull='
BEGIN {
    reFix = "exit"; txFix = "return";
    fmtEnd = "Made %d substitutions on %d lines.\n";
    fmtSub = "\n.... %d Changes on file %s line %d:\n";
    fmtSub = fmtSub "Was: %s\nNow: %s\n";
}
{
    New = $0;
    n = gsub (reFix, txFix, New);
    if (n == 0) { print $0; next; }

    Lines++; Count += n;
    printf (fmtSub, n, FILENAME, FNR, $0, New) > "/dev/stderr";
    print New;
}
END { printf (fmtEnd, Count, Lines) > "/dev/stderr"; }
'
    awk "${AwkFull}" doFifo > doFifo.fix

这显示了每条更改的行,例如:

.... 2 Changes on file doFifo line 64:
Was:    (exit)  printf 1>&7 '%(%T)T  Received exit command\n' -1
Now:    (return)    printf 1>&7 '%(%T)T  Received return command\n' -1

编辑:将参数变成命令参数。

上面的第一个版本将模式和替换文本嵌入到 gsub 命令本身中。第二个版本通过(a)给它们命名,以及(b)在代码的头部声明它们,使更改它们变得更容易。

概括代码的下一阶段是从 shell 传递这些代码。这在 awk 中很容易。首先,删除定义 reFix 和 txFix 的行(约定用于re正则表达式和tx文本,但只要保持一致,就可以随意调用变量)。

要将 shell 字符串放入 awk 变量中,有一个-v选项。所以你的 awk 命令变成:

awk -v reFix="exit" -v txFix="return" "${AwkFull}" doFifo > doFifo.fix

使用 shell 变量的最后一步是使用任何形式的 shell 替换,例如:

awk -v reFix="${1}" -v txFix="${myNew}" "${AwkFull}" doFifo > doFifo.fix

有两个(也许更多)缺点:

(1) awk 知道这/exit/是一个模式。在某些情况下,您可能需要澄清语法:例如,简单的行匹配/exit/需要重写为$0 ~ reFix.但 awk 知道第一个 arggsub()是一个模式,因此语法不会改变。 (看https://www.gnu.org/software/gawk/manual/gawk.html#Strong-Regexp-Constants了解更多。)

(2) 变量中的模式在第一次读取 awk 程序时不会进行语法检查,只有在使用它们时才会进行语法检查。因此,用户输入的模式可能很容易在运行过程中中断,并出现模糊的错误消息。

相关内容