如何在awk或perl(或python,或......)中编写这个sed / bash命令?

时间:2012-07-18 16:36:34

标签: perl bash scripting sed awk

我需要使用新值Progress (n,m)替换脚本语言中Progress label="some text title" (n,m)(N,M)的实例

N= integer ((n/m) * normal)
M= integer ( normal )

进度语句可以在脚本行的任何位置(更糟糕的是,虽然不是当前脚本,但是跨行分割)。

normal是1到255之间的指定数字,nm是浮点数

到目前为止,我的sed实施情况如下。它仅适用于Progress (n,m)格式而不是Progress label="Title" (n,m)格式,,但它只是简单的坚果

#!/bin/bash
normal=$1; 
file=$2
for n in $(sed -rn '/Progress/s/Progress[ \t]+\(([0-9\. \t]+),([0-9\. \t]+)\).+/\1/p' "$file" )
do 
    m=$(sed -rn "/Progress/s/Progress[ \t]+\(${n},([0-9\. \t]+).+/\1/p" "$file")
    N=$(echo "($normal * $n)/$m" | bc)
    M=$normal
    sed -ri "/Progress/s/Progress[ \t]+\($n,$m\)/Progress ($N,$M)/" "$file"
done

简单地说:这有效,但有更好的方法吗?

我的工具箱中包含sedbash脚本,而不是perlawk等,我认为这个问题更适合。< / p>

编辑示例输入。

Progress label="qt-xx-95" (0, 50) thermal label "qt-xx-95" ramp(slew=.75,sp=95,closed) Progress (20, 50) Pause  5 Progress (25, 50) Pause  5 Progress (30, 50) Pause  5 Progress (35, 50) Pause  5 Progress (40, 50) Pause  5 Progress (45, 50) Pause  5 Progress (50, 50)
Progress label="qt-95-70" (0, 40) thermal label "qt-95-70" hold(sp=70)        Progress (10, 40) Pause  5 Progress (15, 40) Pause  5 Progress (20, 40) Pause  5 Progress (25, 40) Pause  5 

3 个答案:

答案 0 :(得分:1)

awk具有良好的分割能力,因此对于这个问题可能是一个不错的选择。

这是一个适用于提供的输入的解决方案,我们称之为update_m_n_n.awk。在bash中运行它:awk -f update_m_n_n.awk -v normal=$NORMAL input_file

#!/usr/bin/awk

BEGIN {
  ORS = RS = "Progress"
  FS = "[)(]"
  if(normal == 0) normal = 10
}

NR == 1 { print }

length > 1 { 
  split($2, A, /, */)
  N = int( normal * A[1] / A[2] )
  M = int( normal )
  sub($2, N ", " M)
  print $0
}

解释

  • ORS = RS = "Progress":在Progress拆分部分,并在输出中加入Progress
  • FS = "[)(]":括号中的单独字段。
  • NR == 1 { print }:在第一部分之前插入ORS
  • split($2, A, /, */):假设在Progress出现之间只有带括号的项目,这会将mn拆分为A数组。
  • sub($2, N ", " M):将新值替换为当前记录。

答案 1 :(得分:1)

这有些脆弱,但似乎可以解决问题?它可以改为perl -pe的一行,但我认为这更清楚:


use 5.16.0;
my $normal = $ARGV[0];
while(<STDIN>){
        s/Progress +(label=\".+?\")? *( *([0-9. ]+) *, *([0-9. ]+) *)/sprintf("Progress $1 (%d,%d)", int(($2/$3)*$normal),int($normal))/eg;
        print $_;
}

基本思路是选择性地捕获$ 1中的label子句,并将n和m捕获到$ 2和$ 3中。我们使用perl的能力,通过提供“e”修饰符,用匹配的代码片段替换匹配的字符串。如果label子句有任何转义引号或者包含匹配看起来像Progress toekn的字符串的字符串,它将会大大失败,所以它不理想。我同意你需要一个诚实的善良解析器,虽然你可以修改这个正则表达式来纠正一些明显的缺陷,比如n和m的弱数匹配。

答案 2 :(得分:0)

我最初的想法是尝试sed使用递归替换(t命令),但我怀疑它会卡住。

perl代码可能适用于不跨行拆分的语句。对于跨行分割,可能有必要编写一个单独的预处理器来加入不同的行。

代码将“Progress”语句拆分为单独的行段,应用任何替换规则,然后将段重新加入一行并打印。简单地打印不匹配的线。匹配代码使用反向引用并变得有些不可读。我假设您的“正常”参数可以采用浮动值,因为规格似乎不清楚。

#!/usr/bin/perl -w

use strict;

die("Wrong arguments") if (@ARGV != 2);
my ($normal, $file) = @ARGV;
open(FILE, '<', $file) or die("Cannot open $file");

while (<FILE>) {
    chomp();
    my $line = $_;

    # Match on lines containing "Progress"
    if (/Progress/) {

        $line =~ s/(Progress)/\n$1/go;    # Insert newlines on which to split
        my @segs = split(/\n/, $line);    # Split line into segments containing possibly one "Progress" clause

        # Apply text-modification rules
        @segs = map {
            if (/(Progress[\s\(]+)([0-9\.]+)([\s,]+)([0-9\.]+)(.*)/) {
                my $newN = int($2/$4 * $normal);
                my $newM = int($normal);
                $1 . $newN . $3 . $newM . $5;
            } elsif (/(Progress\s+label="[^"]+"[\s\(]+)([0-9\.]+)([\s,]+)([0-9\.]+)(.*)/) {
                my $newN = int($2/$4 * $normal);
                my $newM = int($normal);
                $1 . $newN . $3 . $newM . $5;
            } else {
                $_;    # Segment doesn't contain "Progress"
            }
        } @segs;

        $line = join("", @segs);    # Reconstruct the single line
    }

    print($line,"\n");    # Print all lines
}