【问题标题】:TCL: Grouping tunnel message together by date day and tunnelTCL:按日期和隧道将隧道消息分组在一起
【发布时间】:2023-08-13 10:04:02
【问题描述】:

您好,我是 TCL 编码的新手,在本网站的帮助下编写了这个脚本。我想知道是否有更好/更清洁的方式来写这个?

set y [read -nonewline [set f [open "tmp.txt" r]]];


array set counts [list];
foreach message [split $y "\n"] {
        # This gets the status, ie: DOWN/UP/OFFLINE.
        set status [lindex [split $message] end]; # will assign last field
        set mon [lindex [split $message] 1]; # number represents the filed number
        set day [lindex lindex[split $message] 2];
        if {$day ==""} {
            set day [lindex [split $message] 3];
        } else {
            set day [lindex [split $message] 2]
        }
    # +number represents the counter
        if {[info exists counts($mon,$day,$status)]} {
                set counts($mon,$day,$status) [expr {$counts($mon,$day,$status)+1}] 
        } else {
                set counts($mon,$day,$status) 1
        }   
}

# sort based for down type evetns
puts "\n\nTabl: Down per day"
puts "\n\n MMM DD || Cnt Status"
puts " ====================="

foreach count [lsort -increasing -unique [array names counts]] {
        foreach {mon day status} [split $count ","] { break; }
        if {$status =="down"} {
            puts " $mon $day || [set counts($count)] \t $status"
        }

    }
# process tunnels that are down and flapped 3 times

array set countsT [list];
foreach message [split $y "\n"] {
        # This gets the status, ie: DOWN/UP/OFFLINE.
        set status [lindex [split $message] end];
        set mon [lindex [split $message] 1];
        set day [lindex [split $message] 2];
        set Tunnx [lindex [split $message] 9];

        if {$day ==""} {
            set day [lindex [split $message] 3];
            set Tunnx [lindex [split $message] 10]
        } else {
            set day [lindex [split $message] 2]
        }

        if {[info exists countsT($mon,$day,$Tunnx,$status)]} {
            set countsT($mon,$day,$Tunnx,$status) [expr {$countsT($mon,$day,$Tunnx,$status)+1}]
        } else {
                set countsT($mon,$day,$Tunnx,$status) 1
        }
}

puts "\n\nTabl: Tunnel = Flap >3"
puts "\n\n MMM DD || Tunnelx\tCnt\t Status"
puts " ================================="

foreach count [lsort -increasing -unique [array names countsT]] {

        if {[set countsT($count)] < 2} { continue; } {
            foreach {mon day tun n status} [split $count ","] { break; } 
            if {$status =="down"} {
                puts " $mon $day || $tun \t [set countsT($count)] \t$status"
        }
    }
}
if {[info exists f]} { close $f }

示例日志:

670555: Mar  9 23:39:18.214: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel1, changed state to down
670557: Mar  9 23:39:50.877: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670559: Mar  9 23:41:08.662: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel4, changed state to down
670561: Mar  9 23:41:18.309: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel3, changed state to down
670562: Mar  9 23:43:13.237: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670564: Mar  9 23:45:26.549: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670567: Mar  9 23:46:45.708: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel3, changed state to down
670570: Mar  9 23:49:31.222: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670574: Mar  9 23:53:14.295: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670576: Mar  9 23:55:49.217: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670577: Mar  9 23:56:16.180: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel1, changed state to down
670581: Mar  9 23:56:45.480: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel3, changed state     to down
670583: Mar  9 23:59:54.080: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670585: Mar 10 00:00:33.224: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel1, changed state to down
670587: Mar 10 00:04:03.292: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670589: Mar 10 00:04:38.921: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel3, changed state to down
670590: Mar 10 00:05:00.505: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel1, changed state to down
670593: Mar 10 00:06:22.473: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670596: Mar 10 00:09:07.262: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel1, changed state to down
670598: Mar 10 00:11:11.294: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down
670602: Mar 10 00:14:23.649: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel1, changed state to down
670604: Mar 10 00:14:59.296: %LINEPROTO-5-UPDOWN: Line protocol on Interface Tunnel2, changed state to down


 Results:

 Tabl: Down per day


 MMM DD || Cnt Status
 =====================
 Mar 10 || 9     down
 Mar 9 || 13     down


 Tabl: Tunnel = Flap >3


  MMM DD || Tunnelx      Cnt      Status
  =================================
  Mar 10 || Tunnel1       4      down
  Mar 10 || Tunnel2       4      down
  Mar 9 || Tunnel1        2      down
  Mar 9 || Tunnel2        7      down
  Mar 9 || Tunnel3        3      down

更新:

需要记录一天中翻动的隧道数量。

使用下表作为数据参考,新表将是 3 月 10 日的 2 条隧道抖动和 3 月 9 日的 3 条隧道。

我看不到如何计算出额外的 tunnelx 字段分组来计算抖动隧道的数量。

  MMM DD || Tunnelx      Cnt      Status
  =================================
  Mar 10 || Tunnel1       4      down
  Mar 10 || Tunnel2       4      down
  Mar 9 || Tunnel1        2      down
  Mar 9 || Tunnel2        7      down
  Mar 9 || Tunnel3        3      down

【问题讨论】:

    标签: logging tcl cisco


    【解决方案1】:

    一些注意事项:

    • 您不必循环文件两次,只需在第一次循环中累积数组
    • 按行读取文件的惯用方式是使用while {[gets ...,正如我所展示的那样
    • 如您所见,split 仅在单个空白字符上拆分。我已经演示了使用 regexp 来收集字段。

    我会这样做:

    array set counts [list]
    array set countsT [list]
    
    set f [open tmp.txt r]
    
    while {[gets $f message] != -1} {
        set fields [regexp -all -inline {\S+} $message]
        set mon [lindex $fields 1] 
    
        # use day "09" instead of "9" so it gets the sorting right.
        set day [format %02d [lindex $fields 2]]
    
        # have to be careful to remove the trailing comma from the tunnel
        set Tunnx [string trimright [lindex $fields 9] ,]
    
        set status [lindex $fields end] ;# will assign last field
    
        incr counts($mon,$day,$status) 
        incr countsT($mon,$day,$Tunnx,$status) 
    }
    
    close $f
    
    
    # sort based for down type evetns
    puts "\n\nTabl: Down per day"
    puts "\n\n MMM DD || Cnt Status"
    puts " ====================="
    
    # don't need "-unique" flag: array keys will be unique
    foreach count [lsort -increasing [array names counts]] {
        foreach {mon day status} [split $count ","] { break }
        if {$status eq "down"} {
            puts [format "%s %s || %3d\t%s" $mon $day $counts($count) $status]
        }
    }
    
    puts "\n\nTabl: Tunnel = Flap >3"
    puts "\n\n MMM DD || Tunnelx\tCnt\t Status"
    puts " ================================="
    
    foreach count [lsort -increasing [array names countsT]] {
        if {$countsT($count) < 2} { continue } 
        foreach {mon day tun status} [split $count ","] { break } 
        if {$status eq "down"} {
            puts [format " %s %s || %s\t%3d\t%s" $mon $day $tun $countsT($count) $status]
        }
    }
    

    【讨论】:

    • 不知道incr对未初始化的var有效,大大简化了代码逻辑。
    • 感谢您的回答,我正在运行 tcl 8.5 的 win8 上测试代码。我得到这个错误。嗯 DD || Cnt 状态 ===================== 预期整数,但在执行“格式”%s %d || 时得到“09” %3d\t%s" $mon $day $counts($count) $status" ("foreach" 正文第 4 行) 从 "foreach count [lsort -increasing [array names counts]] { foreach {mon day status 中调用} [split $count ","] { break } if {$status eq "down"} { ..." (文件 "t4.3.tcl" 第 44 行)
    • 这看起来像经典的 Tcl 无效八进制数问题。 Tcl 8.5 将以 0 开头的数字解释为八进制数,而 09 不是有效的八进制数。请参阅此处:wiki.tcl.tk/498 了解更多详细信息和修复。
    • 已经有一段时间了,但我需要一些新的帮助。我如何将一天中的隧道翻动次数分组,即 3 月 1 日隧道 2 翻动了两次,但它的唯一记录是一次.. 请参阅更新问题
    【解决方案2】:

    我不确定“更好”,但我想提出一些改变:

    set f [open "tmp.txt" r]
    set contents [read -nonewline $f]
    close $f
    
    array set status_counts {}
    array set tunnel_counts {}
    
    foreach message [split $contents "\n"] {
        set mon    [lindex $message 1]
        set day    [lindex $message 2]
        set tunnel [lindex $message 9]
        set status [lindex $message end]
    
        # Do we really need this block?
        if {$day ==""} {
            set day [lindex $message 3]
            set tunnel [lindex $message 10]
        } else {
            set day [lindex $message 2]
        }
    
        set index "$mon,$day,$status"
        if {[info exists status_counts($index)]} {
            set status_counts($index) [expr {$status_counts($index)+1}] 
        } else {
            set status_counts($index) 1
        }   
    
        set index "$mon,$day,$tunnel,$status"
        if {[info exists tunnel_counts($index)]} {
            set tunnel_counts($index) [expr {$tunnel_counts($index)+1}]
        } else {
            set tunnel_counts($index) 1
        }
    }
    
    # sort based for down type evetns
    puts "\n\nTabl: Down per day"
    puts "\n\n"
    puts [format "%-3s %2s || %3s %s" MMM DD Cnt Status]
    puts [format "%-3s %2s || %3s %s" === == === ======]
    
    foreach count [lsort -increasing -unique [array names status_counts *,down]] {
        foreach {mon day status} [split $count ","] { break }
        puts [format "%-3s %2s || %3s %s" $mon $day $status_counts($count) $status]
    }
    
    puts "\n\nTabl: Tunnel = Flap >3"
    puts "\n\n"
    puts [format "%-3s %2s || %-8s %-4s %s" MMM DD TunnelX Cnt Status]
    puts [format "%-3s %2s || %-8s %-4s %s" === == ======= === ======]
    
    foreach count [lsort -increasing -unique [array names tunnel_counts *,down]] {
        if {[set tunnel_counts($count)] < 2} { continue; } 
        foreach {mon day tun n status} [split $count ","] { break } 
        puts [format "%-3s %2s || %-8s %-4s %s" $mon $day $tun $tunnel_counts($count) $status]
    }
    

    讨论

    以下是我所做的更改及其背后的想法。这些是我的意见,因此您可能同意也可能不同意我的观点。

    1. Tcl 不需要以分号结束一行,所以我宁愿不看到它们,这有助于代码清晰。

    2. 作为两个单独的步骤打开和读取文件。如果出现错误,我知道是哪一步导致了问题,从而更容易调试。

    3. 我更喜欢在完成后立即关闭文件,而不是在脚本结束时关闭。

    4. 将两个循环合二为一。你有两个循环,每个循环都有相似但略有不同的步骤。我将它们组合成一个循环,这样可以减少代码量、复杂性和出错的机会。

    5. 我更喜欢拼写的标识符名称,而不是缩写。例如,我更喜欢 tunnel_counts 而不是 countsT

    6. 在调用lindex 之前无需拆分消息。在大多数情况下,Tcl 以相同的方式处理字符串和列表。这意味着一个字符串可以作为一个列表,项目之间用空格分隔。

    7. 在第一个循环中,我不理解测试$day == "" 的代码块,所以我只留下那个块。我怀疑你不需要它。

    8. 我引入了变量index,它减少了打字,使代码更干净,减少出错的机会。

    9. 我没有在两个报告循环中测试$status == "down",而是将过滤器应用于array names 命令:[array names tunnel_counts *,down]。这消除了这些循环中的if 语句。

    10. 格式更改以更好地对齐

    11. 最后,原始代码的缩进不一致:有时缩进 4 个空格,有时缩进 8 个。这可能与您在编辑器中设置制表符的方式有关。

    12. 请务必注意,您的原始脚本运行良好。我建议的更改是为了便于阅读、理解和维护。

    【讨论】:

    • 很好地使用了array names的全局模式。
    • 感谢您的反馈。第 11 点是我在问题上的错,在发布问题时我仍然无法弄清楚如何正确缩进代码块。
    • 仅供参考。发布时,我只是将所有原始代码缩进 4 个空格并粘贴到 * 中。