用结果替换数学方程

用结果替换数学方程

在文件中替换使用某些预定义正则表达式找到的方程与结果的最佳方法是什么?

假设每个方程公式都与bc -l(具有浮点数处理的基本计算器)兼容。

例如,假设方程由[[and分隔]](仅作为示例):

输入为:

Results show that each unit should generate
approx. [[7*9/2.0]]Wh per day.
All the same ...

期望的输出:

Results show that each unit should generate
approx. 31.5Wh per day.
All the same ...

我最好的尝试:

while read line; do
    
    if [[ $line =~ \[\[.*\]\] ]]; then
    
        equ=`echo "$line" | sed "s|.*\[\[||" | sed "s|]].*||"`
        res=`echo "$equ" | bc -l | awk '{print $1+0}'`
        
        new_line=`echo $line | sed "s|\[\[.*]]|$res|"`
        
        echo $new_line
        
    else
        
        echo $line
        
    fi

done < $infile

输出:

Results show that each unit should generate
approx. 31.5Wh per day.
All the same ...

但想知道是否有更简单的方法(没有 while 循环)。

此外,如果每行只有一个方程,这也将起作用。

答案1

bc -l你说兼容?我猜你最好的选择就是逃跑bc -l。使用 Perl 可以很容易地做到这一点。如果我们有math.txt这样的:

two times [[3*7]] is [[2*3*7]]
[[scale=6; a=4; a*s(3.141/4)]]

[[...]]这将扫描它并通过传递其中的块bc -l

$ perl -pe 's,\[\[(.*?)\]\], $a=qx/echo "$1" | bc -l /; chomp $a; $a ,ge' math.txt 
two times 21 is 42
2.828008

[[它只是捕获和之间的内容]],将其作为输入推到bc -l,并用输出替换括号内的块。

请注意,这确实为每个块运行一个新实例bc,因此变量分配不会保留在块之间。 (但是您可以设置scale每个块,就像我上面所做的那样。)匹配是非贪婪的,因此每行多个块都可以工作,但这确实意味着结束分隔符不能作为表达式的一部分出现,即为什么我用]]. (使用括号时,可能会发生冲突:例如((3*(a+(b+c))))会失败,并且您需要用空格分隔括号,就像((3*(a+(b+c) ) ))相反。)

\[\[括号在正则表达式中仍然很特殊,因此它们与and匹配\]\];正则表达式中的括号用于捕获内部部分。不过,可能有更好的方法来处理打印的换行符bc

为每个算术块启动一个新进程似乎有点繁重,但它是一个相当简单的解决方案。

请注意,这对于恶意输入是不安全的!当块的内容通过 shell 传递给 时bc,算术表达式中的任何 shell 语法都将被处理。例如,这将运行uname命令:

[[ $(uname -a >&2 ) ]]

这个问题可以通过打开 Perl 的管道来解决bc,但它可能不适合单行代码。

答案2

您的示例表达式看起来可以perl直接计算,在这种情况下,您可以避免使用bc以下内容调用每个表达式:

perl -pe 's{\[\[([\d\s./*+-]+)\]\]}{eval$1}ge' < "$infile"

这里只处理包含数字、空格的表达式,./*+-避免引入命令注入漏洞。

它还与zsh的算术表达式兼容,因此如果可以选择从 bash 切换到 zsh,则可以通过以下方式完成:

set -o extendedglob
print -r -- ${"$(<$infile)"//(#b)\[\[([[:digit:][:space:].\/*+-]##)\]\]/$((match[1]))}

您还可以bc -l通过以下方式进行计算:

set -o extendedglob
print -r -- ${"$(<$infile)"//(#b)\[\[([[:digit:][:space:].\/*+-]##)\]\]/$(bc -l <<<$match[1])}

答案3

使用任何 awk:

$ cat tst.awk
{
    while ( match($0,/\[\[.*]]/) ) {
        $0 = substr($0,1,RSTART-1) eval(substr($0,RSTART+2,RLENGTH-4)) substr($0,RSTART+RLENGTH)
    }
    print
}

function eval(equation,      cmd,line,rslt) {
    rslt = "FAILED:" equation
    if ( equation !~ /[\047"]|system/ ) {
        cmd = "awk \047BEGIN{print " equation "; exit}\047"
        if ( (cmd | getline line) > 0 ) {
            rslt = line+0
        }
        close(cmd)
    }
    return rslt
}

$ awk -f tst.awk file
Results show that each unit should generate
approx. 31.5Wh per day.
All the same ...

awk再次打电话而不是bc做数学部分,因为虽然我们希望bc存在(它没有在我的 cygwin 安装中),但我们知道awk我们已经这么称呼它了。

不过,这会很慢,因为它每次都会生成一个子 shell 来执行eval.

相关内容