Pretty print a TCL list - scripting

Having a TCL list that looks like below (formatted for better visibility):
set mylist [list \
"title_1 title_2 title_3 " \
"row1col1_bla row1col2 row1col3 " \
"r2c1 r2c2_blablabla r2c3" \
"r3c1_really_long_string r3c2 r3c3" \
]
I need a procedure that prints $mylist like:
title_1 title_2 title_3
1) row1col1_bla row1col2 row1col3
2) r2c1 &FN_1 r2c3
3) &FN_2 r3c2 r3c3
Footnotes:
FN_1: r2c2_blablabla
FN_2: r3c1_really_long_string
The procedure should take as input:
COLMAXLEN: the maximum length of any individual string from $mylist, beyound which that string will go to the Footnotes section (being replaced by the $SHORTCUT_$index value)
SHORTCUT: string that replaces any individual member of $mylist, if its length is greater than $COLMAXLEN.
The following function does exactly this. Are there any suggestions to possibly simplify or improve it?
#!/usr/bin/tclsh
set COLMAXLEN 12
set SHORTCUT "FN"
proc puts_list {mylist} {
global COLMAXLEN SHORTCUT
set num_row [llength $mylist]
set num_col [llength [lindex $mylist 0]]
set ref_list {}
# Define/init col_width (a list having $num_col elements)
set col_width {}
for {set col 0} {$col < $num_col} {incr col} {
lappend col_width 0
}
# Get the max width of each column AND
# replace the elements > $COLMAXLEN with footnote shortcuts!
for {set row 0} {$row < $num_row} {incr row} {
set new_row {}
for {set col 0} {$col < $num_col} {incr col} {
set myrow [lindex $mylist $row]
set myitem [lindex $myrow $col]
set mysize [string length $myitem]
if { $mysize > $COLMAXLEN } {
lappend ref_list $myitem
set myitem "&[subst $SHORTCUT]_[llength $ref_list]"
set mysize [string length $myitem]
}
if { $mysize > [lindex $col_width $col] } {
lset col_width $col $mysize
}
lappend new_row $myitem
}
lset mylist $row $new_row
}
# Start printing
set num_col_width [expr [string length $num_row] +1]
puts ""
for {set row 0} {$row < $num_row} {incr row} {
if { $row == 0 } {
puts -nonewline [format "%[subst $num_col_width]s" { }]
} else {
puts -nonewline [format "%[subst $num_col_width]s" "$row)"]
}
puts -nonewline " "
for {set col 0} {$col < $num_col} {incr col} {
set myrow [lindex $mylist $row]
set myitem [lindex $myrow $col]
set mysize [expr [lindex $col_width $col] +1]
puts -nonewline [format "%-[subst $mysize]s" $myitem]
}
puts ""
}
puts ""
puts " Footnotes:"
set ref_num [llength $ref_list]
for {set i 0} {$i < $ref_num} {incr i} {
puts " [subst $SHORTCUT]_[format %-[subst [string length $ref_num]]s [expr $i + 1]]: [lindex $ref_list $i]"
}
puts ""
}
ASSUMPTION: TCL version is 8.4

# Print the row number
proc put_row_number {rowNumber} {
if {$rowNumber == 0} {
puts -nonewline " "
} else {
puts -nonewline "$rowNumber) "
}
}
proc put_table {table {colmaxlen 12} {shortcut FN}} {
set ref {}
set refCount 0
set rowCount 0
foreach row $table {
put_row_number $rowCount
incr rowCount
foreach cell $row {
if {[string length $cell] > $colmaxlen} {
incr refCount
set key "${shortcut}_${refCount}"
lappend ref $key $cell
set cell "&$key"
}
puts -nonewline [format "%-*s " $colmaxlen $cell]
}
puts ""
}
puts "\n Footnotes:"
foreach {key value} $ref {
puts " $key: $value"
}
}
set mylist {
"title_1 title_2 title_3 "
"row1col1_bla row1col2 row1col3 "
"r2c1 r2c2_blablabla r2c3"
"r3c1_really_long_string r3c2 r3c3"
}
put_table $mylist

Related

Delete elements during loop awk semantics

If we assume the loop returns k==0 first (this order is implementation dependent according to the spec). How many times should the loop body run? Once or twice? If twice what should be printed for arr[1]?
BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}
$ gawk --version
GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)
....
$ gawk 'BEGIN { arr[0] = "zero"; arr[1] = "one"; for (k in arr) { print "key " k " val " arr[k]; delete arr[k+1] } }'
key 0 val zero
key 1 val
$ goawk --version
v1.19.0
$ goawk 'BEGIN { arr[0] = "zero"; arr[1] = "one"; for (k in arr) { print "key " k " val "
key 0 val zero
gnu-awk runs it twice with arr[1] == "" and goawk runs it once. Mawk (mawk 1.3.4 20200120) sorts keys 1,0 but has the same fundamental behavior as gnu-awk, looping twice and print the empty string for the deleted key). What is the posix defined expected behavior of this program?
Essentially should keys deleted in past loops appear in future loops?
According to the POSIX spec:
The results of adding new elements to array within such a for loop are
undefined
but it doesn't define what happens if you delete them other than:
The delete statement shall remove an individual array element
However, according to the GNU AWK manual:
As a point of information, gawk sets up the list of elements to be iterated over before the loop starts, and does not change it. But not all awk versions do so.
so the behavior is undefined by POSIX, defined for GNU AWK, and you'd have to check the man page for every other AWK to see what it does.
Decide which behavior you want and then to get that behavior robustly and portably in all awks you could write whichever one of these you want:
gawks behavior:
BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
indices[k]
}
for (k in indices) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}
goawks apparent behavior from your example:
BEGIN {
arr[0] = "zero";
arr[1] = "one";
for ( k in arr ) {
indices[k]
}
for (k in indices) {
if ( k in arr ) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}
}
Notes on your code in general:
for ( k in ... ) could visit the indices in any order so relying on delete arr[k+1] to delete an element of arr[] isn't robust as, for example, you might be trying to delete an index past the end of the array on your first iteration through the loop if in decides to start with k set to the last index in the array.
All builtin and generated awk arrays, fields, and strings start at index 1, not 0, so don't create your own arrays starting at 0, start them at 1 to avoid having to remember which type of array it is when writing code to visit the indices and inevitably tripping over that difference at some point.
apparently mawk-1 is rather unique in this aspect :
mawk 1.3.4
% mawk 'BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}'
key 1 val one <<<<<<
key 0 val zero
Furthermore, setting the WHINY_USERS shell environment flag changes its behavior (an entirely value-less declaration already suffices) :
WHINY_USERS= mawk 'BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}'
key 0 val zero
key 1 val
% mawk 'BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}'
key 1 val one
key 0 val zero
mawk-2 (beta-1.9.9.6)
% mawk2 'BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}'
key 0 val zero
key 1 val
gawk 5.2.0
gawk -e 'BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}'
key 0 val zero
key 1 val
nawk 20200816
% nawk 'BEGIN {
arr[0] = "zero";
arr[1] = "one";
for (k in arr) {
print "key " k " val " arr[k];
delete arr[k+1]
}
}'
key 0 val zero

Some Changes To Make Here In This Tlc Script

Now the script is work great, but need to change the commands and remove channel name, when i add IP in exemp.txt file list,
someone can help here, how to change this public comand , thx in advance
!exemp help change in !help
!exemp list change in !list
!exemp del 1 change in !del 75.34.37.12 75.34.37.* 75.34.*
!exemp add change in !add 75.34.37.12 75.34.37.* 75.34.*
and to remove the name channel when i add Ip on exemp.txt list
to add only the IP cozz is added and channel name with Ip on file exemp.txt
#Test 75.34.37.12
thank you in advance
proc check_ip {ip} {
set file [open "exemp.txt" r]
set fdata [split [read $file] \n]
close $file
foreach entry $fdata {
if {[string match $entry $ip]} {
return 1
}
}
return 0
}
bind pub n|n !exemp exemp:cmd
set exemp(file) "exemp.txt"
if {![file exists $exemp(file)]} {
set file [open $exemp(file) w]
close $file
}
proc exemp:cmd {nick host hand chan arg} {
global exemp
set arg0 [lindex [split $arg] 0]
set arg1 [lindex [split $arg] 1]
if {$arg0 == ""} {
putserv "NOTICE $nick :EXEMP Use: \002!exemp help\002 for more informations."
return
}
switch $arg0 {
add {
if {$arg1 == ""} {
putserv "NOTICE $nick :EXEMP Use: \002!exemp help\002 for more informations."
return
}
set file [open $exemp(file) a]
puts $file "$chan $arg1"
close $file
putserv "NOTICE $nick :EXEMP I added\002 $arg1 \002in my ExempList."
}
list {
set file [open $exemp(file) "r"]
set read [read -nonewline $file]
close $file
set data [split $read "\n"]
set i 0
if {$data == ""} {
putserv "NOTICE $nick :EXEMP There are\002 no IP \002added to ExempList."
return
}
putserv "NOTICE $nick :EXEMP The list of IP added in my ExempList."
foreach line $data {
set read_chan [lindex [split $line] 0]
if {[string match -nocase $read_chan $chan]} {
set i [expr $i +1]
set read_blackchan [lindex [split $line] 1]
putserv "NOTICE $nick :$i.) $read_blackchan"
}
}
}
del {
array set exempdel [list]
if {![regexp {.*?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*?\((.*?)\)} $arg1]} {
putserv "NOTICE $nick :EXEMP Use: \002!exemp help\002 for more informations."
return
}
set file [open $exemp(file) "r"]
set data [read -nonewline $file]
close $file
set lines [split $data "\n"]
set counter -1
set line_counter -1
set current_place -1
foreach line $lines {
set line_counter [expr $line_counter + 1]
set read_chan [lindex [split $line] 0]
if {[string match -nocase $read_chan $chan]} {
set counter [expr $counter + 1]
set exempdel($counter) $line_counter
}
}
foreach place [array names exempdel] {
if {$place == [expr $arg1 - 1]} {
set current_place $exempdel($place)
}
}
if {$current_place == "-1"} {
putserv "NOTICE $nick :EXEMP The entry number\002 $arg1 \002does not exist."
return
}
set delete [lreplace $lines $current_place $current_place]
set files [open $exemp(file) "w"]
puts $files [join $delete "\n"]
close $files
set file [open $exemp(file) "r"]
set data [read -nonewline $file]
close $file
if {$data == ""} {
set files [open $exemp(file) "w"]
close $files
}
putserv "NOTICE $nick :EXEMP The entry number\002 $arg1 \002was removed from ExempList."
}
help {
putserv "NOTICE $nick :EXEMP You can add IP using: \002!exemp add <IP>\002 "
putserv "NOTICE $nick :EXEMP To see all the IP in ExempList use:\002 !exemp list\002"
putserv "NOTICE $nick :EXEMP To delete a IP use:\002 !exemp del <number>\002 (from the ExempList)"
}
}
}
How is work now
<BoB> !exemp add 75.34.37.12
TestBot EXEMP I added 75.34.37.12 in my ExempList.
<BoB> !exemp list
TestBot EXEMP The list of IP added in my ExempList.
TestBot 1.) 75.34.37.12
<BoB> !exemp del 1
TestBot EXEMP The entry number 1 was removed from ExempList.
<BoB> !exemp list
TestBot EXEMP There are no IP added to ExempList.
<BoB> !exemp del 75.34.37.12
TestBot EXEMP Use: !exemp help for more informations.
<BoB> !exemp help
TestBot EXEMP You can add IP using: !exemp add <IP>
TestBot EXEMP To see all the IP in ExempList use: !exemp list
TestBot- EXEMP To delete a IP use: !exemp del <number> (from the ExempList}

How do I dump the contents of SYMTAB in gawk?

How do I dump the contents of SYMTAB in gawk? I've tried things like the following which displays scalars just fine. It also displays the array names and indices, but it doesn't display the value of each array element.
for (i in SYMTAB) {
if (isarray(SYMTAB[i])) {
for (j in SYMTAB[i]) {
printf "%s[%s] = %s\r\n", i, j, SYMTAB[i, j]
}
} else {
printf "%s = %s\r\n", i, SYMTAB[i]
}
}
which gives results like:
OFS =
ARGC = 1
PREC = 53
ARGIND = 0
ERRNO =
ARGV[0] =
For example, I would expect to see a value after ARGV[0] but I'm not.
Use SYMTAB[i][j] instead of SYMTAB[i,j] - you're using multi-dimensional array syntax in the loops to access the indices so just keep doing that.
Here's a recursive function to dump SYMTAB or any other array or scalar:
$ cat tst.awk
function dump(name,val, i) {
if ( isarray(val) ) {
printf "%*s%s %s%s", indent, "", name, "{", ORS
indent += 3
for (i in val) {
dump(i,val[i])
}
indent -= 3
printf "%*s%s %s%s", indent, "", name, "}", ORS
}
else {
printf "%*s%s = <%s>%s", indent, "", name, val, ORS
}
}
BEGIN {
dump("SYMTAB",SYMTAB)
}
.
$ awk -f tst.awk
SYMTAB {
ARGV {
0 = <awk>
ARGV }
ROUNDMODE = <N>
ORS = <
>
OFS = < >
LINT = <0>
FNR = <0>
ERRNO = <>
NR = <0>
IGNORECASE = <0>
TEXTDOMAIN = <messages>
NF = <0>
ARGIND = <0>
indent = <3>
ARGC = <1>
PROCINFO {
argv {
0 = <awk>
1 = <-f>
2 = <tst.awk>
argv }
group9 = <15>
ppid = <2212>
...
strftime = <%a %b %e %H:%M:%S %Z %Y>
group8 = <11>
PROCINFO }
FIELDWIDTHS = <>
CONVFMT = <%.6g>
SUBSEP = <>
PREC = <53>
ENVIRON {
SHLVL = <1>
ENV = <.env>
...
INFOPATH = </usr/local/info:/usr/share/info:/usr/info>
TEMP = </tmp>
ProgramData = <C:\ProgramData>
ENVIRON }
RS = <
>
FPAT = <[^[:space:]]+>
RT = <>
RLENGTH = <0>
OFMT = <%.6g>
FS = < >
RSTART = <0>
FILENAME = <>
BINMODE = <0>
SYMTAB }
Massage to suit...
Thank you Ed Morton. Looks like a recursive process would be required if I needed to support arbitrary levels of nested arrays, but for now this code dumps my gawk SYMTAB without errors:
for (i in SYMTAB) {
if (!isarray(SYMTAB[i])) {
printf "%s = %s\r\n", i, SYMTAB[i]
} else {
for (j in SYMTAB[i]) {
if (!isarray(SYMTAB[i][j])) {
printf "%s[%s] = %s\r\n", i, j, SYMTAB[i][j]
} else {
for (k in SYMTAB[i][j]) {
if (!isarray(SYMTAB[i][j][k])) {
printf "%s[%s][%s] = %s\r\n", i, j, k, SYMTAB[i][j][k]
} else {
printf "Skipping highly nested array.\r\n"
}
}
}
}
}
}
Thanks again!

Awk input variable as a rule

Good day!
I have the next code:
BLOCK=`awk '
/\/\* R \*\// {
level=1
count=0
}
level {
n = split($0, c, "");
for (i = 1; i <= n; i++)
{
printf(c[i]);
if (c[i] == ";")
{
if(level==1)
{
level = 0;
if (count != 0)
printf("\n");
};
}
else if (c[i] == "{")
{
level++;
count++;
}
else if (c[i] == "}")
{
level--;
count++;
}
}
printf("\n")
}' $i`
That code cuts the piece of the file from /* R */ mark to the ';' symbol with taking into account the details like braces etc. But that isn't important. I want to replace the hard-coded /* R */ by the variable:
RECORDSEQ="/* R */"
...
BLOCK=`awk -v rec="$RECORDSEQ" '
rec {
level=1
count=0
}
But that doesn't work.
How can I fix it?
Thank you in advance.
Found the solution:
RECORDSEQ="/* R */"
# Construct regexp for awk
RECORDSEQREG=`echo "$RECORDSEQ" | sed 's:\/:\\\/:g;s:\*:\\\*:g'`
# Cycle for files
for i in $SOURCE;
do
# Find RECORDSEQ and cut out the block
BLOCK=`awk -v rec="$RECORDSEQREG" '
$0 ~ rec {
level=1
count=0
}
...
Many thanks to people who helped.

awk nesting curling brackets

I have the following awk script where I seem to need to next curly brackets. But this is not allowed in awk. How can I fix this issue in my script here?
The problem is in the if(inqueued == 1).
BEGIN {
print "Log File Analysis Sequencing for " + FILENAME;
inqueued=0;
connidtext="";
thisdntext="";
}
/message EventQueued/ {
inqueued=1;
print $0;
}
if(inqueued == 1) {
/AttributeConnID/ { connidtext = $0; }
/AttributeThisDN / { thisdntext = $2; } #space removes DNRole
}
#if first chars are a timetamp we know we are out of queued text
/\#?[0-9]+:[0-9}+:[0-9]+/
{
if(thisdntext != 0) {
print connidtext;
print thisdntext;
}
inqueued = 0; connidtext=""; thisdntext="";
}
try to change
if(inqueued == 1) {
/AttributeConnID/ { connidtext = $0; }
/AttributeThisDN / { thisdntext = $2; } #space removes DNRole
}
to
inqueued == 1 {
if($0~ /AttributeConnID/) { connidtext = $0; }
if($0~/AttributeThisDN /) { thisdntext = $2; } #space removes DNRole
}
or
inqueued == 1 && /AttributeConnID/{connidtext = $0;}
inqueued == 1 && /AttributeThisDN /{ thisdntext = $2; } #space removes DNRole
awk is made up of <condition> { <action> } segments. Within an <action> you can specify conditions just like you do in C with if or while constructs. You have a few other problems too, just re-write your script as:
BEGIN {
print "Log File Analysis Sequencing for", FILENAME
}
/message EventQueued/ {
inqueued=1
print
}
inqueued == 1 {
if (/AttributeConnID/) { connidtext = $0 }
if (/AttributeThisDN/) { thisdntext = $2 } #space removes DNRole
}
#if first chars are a timetamp we know we are out of queued text
/\#?[0-9]+:[0-9}+:[0-9]+/ {
if (thisdntext != 0) {
print connidtext
print thisdntext
}
inqueued=connidtext=thisdntext=""
}
I don't know if that'll do what you want or not, but it's syntactically correct at least.