Tcl script to search strings and store in variable - tcl

Loop = (
{
Value = (
{
{
Key :
{
A = "B";
C = "D";
Class = (
{
section = "section_a";
Pairs = (
{
Name = "Ram";
Mark = "80";
},
{
Name = "Latha";
Mark = "70";
},
{
Name = "Mohan";
Mark = "90";
},
{
Name = "David";
Mark = "76";
} );
} );
Id = 1;
};
Absent :
{
DAYS = "Two days";
};
},
{
Key :
{
A = "B";
C = "D";
Class = (
{
section = "section_b";
Pairs = (
{
Name = "Ram";
Mark = "30";
},
{
Name = "Latha";
Mark = "45";
},
{
Name = "Mohan";
Mark = "100";
},
{
Name = "David";
Mark = "76";
} );
} );
Id = 2;
};
Absent :
{
DAYS = "Four days";
};
},
} );
} );
I am new to tcl script. I have a txt file in a above format. Using tcl script I have to store strings(section, Name , mark and Absent days) in a different variables to store in a csv file.
I tried below code to search the word Key
set search "Key"
set file [open "Marks.txt" r]
while {[gets $file data] != -1} {
if {[string match *[string toupper $search]* [string toupper $data]] } {
puts "Found '$search' in the line '$data'"
} else {
puts "does not match"
}
}
It is working to find the word key and printing whenever it matches. It works for that line which has the word Key. But, here I want to find the word Loop then I want search for the word Key in side loop. If it sees the word Key then it has to copy some strings present in the loop to variables. The word Key will be repeated in the files multiple times. After the word Key there will be {..} all over there file has some content. The script has to read the content and store it in some variable.
Exp: The script need to find the word Key in the text file, then look for the
section if present then section_b need to be stored in variable temp1(exp. temp1=section_a), Like wise:
If it sees Ram then Mark below the line needs to be stored in temp2 (exp. temp2=80).
If it sees Latha then Mark below the line needs to be stored in temp3 (exp. temp3=70).
then find id and need to to store the value 1 need to be stored in temp4(exp. temp4=1).
then Days meed to be stored in temp5(exp. temp5=Two days)
These temp values need to be written in the csv file everytime when it sees the word Key in the text file in below format.
section Ram Latha id Days
Section_a 80 70 1 Two days
Section_b 30 45 2 Four days
Can you help me in writing the tcl script to get this. Thank you.

This is the kind of thing that awk is really good for:
awk '
function quoted(string, a) {
split(string, a, /"/)
return a[2]
}
BEGIN { OFS="\t"; print "section", "Ram", "Latha", "id", "Days" }
$1 == "section" {section = quoted($0); delete marks}
$1 == "Name" {name = quoted($0)}
$1 == "Mark" {marks[name] = quoted($0)}
$1 == "Id" {id = gensub(/;/, "", 1, $3)}
$1 == "DAYS" {print section, marks["Ram"], marks["Latha"], id, quoted($0)}
' file
Translating that as a Tcl "one-liner", I'd write
echo '
proc trim {str} {string trim $str {"}}
puts "section\tRam\tLatha\tid\tdays"
set fh [open [lindex $argv end]]
while {[gets $fh line] != -1} {
if {[regexp -- {(\S+) = ("[^"]+"|\d+);} $line -> key value]} {
switch -exact -- $key {
section {set section [trim $value]; array set marks {}}
Name {set name [trim $value]}
Mark {set marks($name) [trim $value]}
Id {set id $value}
DAYS {puts [join [list $section $marks(Ram) $marks(Latha) $id [trim $value]] \t]}
}
}
}
' | tclsh - file

Related

How to break a single TCL list into multiple sublists and easily searchable?

I have single TCL list that is extracted from a text file.
{ First Name = John
Last Name = Doe
Country = USA
Hello;
World;
Hello;
World;
First Name = Dwayne
Last Name = Jhonson
Country = USA
DoYou;
Smellwhatthe;
RockisCooking;
First Name = Harry
Last Name = Potter
Country = UK
The;
BoyWHo;
Lived; }
I want to be able to have the user input the text file(list), First name,last name and country. The code needs to dump out the remaining information for further post processing.
The way I am thinking of coding it right now is with multiple FOR loops, but I am sure there is a more efficient way to do this. Any tips?
proc display_name_information {text_file first_name last_name country} {
set fid [open $text_file r]
set filecontent [read $fid]
set input_list [split $filecontent "\n"]
foreach elem $input_list{
set first_word [lindex $line 0]
set second_word [lindex $line 1]
set third_Word [lindex $line 2]
if {[expr {$first_word== "First"}] && [expr {$third_word== "$first_name"}]}
*Then similarly check last name and country*
*and then output everything until I reach the keyword "First Name" again*
This feels very inefficient for large files.
A generic method of processing a text file is using a state machine. In the following example, each time a text line matches the expected pattern, the state machine goes to the next state. In each state, you may do further processing, such as extracting data from the text line. Until all lines are done.
set state 0
set chn1 [open input_file r]
array set record [list]
while { [gets $chn1 s1] >= 0 } {
set s1 [string trim $s1]
switch -- $state {
0 - 3 {
if { [regexp {^First Name\s*=\s*(.*)$} $s1 match data] } {
set first_name $data
set state 1
} elseif { $state == 3 } {
append record($key) $s1 { }
}
}
1 {
if { [regexp {^Last Name\s*=\s*(.*)$} $s1 match data] } {
set last_name $data
set state 2
}
}
2 {
if { [regexp {^Country\s*=\s*(.*)$} $s1 match data] } {
set country $data
set key ${first_name},${last_name},${country}
set state 3
}
}
}
}
append record($key) $s1 { }
close $chn1
parray record
exit

How to access .txt file using tcl PROC function

I have two files and I am comparing specific lines between two files using the def function.
def readPinFile(filename):
result = None
with open(filename, "r") as file:
result = {}
lastPin = None
for line in file:
lines = line.strip()
if lines[:3] == "PIN":
lastPin = lines.split(" ")[1]
result[lastPin] = {"LAYER": None, "RECT": None}
if lines[:5] == "LAYER":
result[lastPin]["LAYER"] = lines.split(" ")[1]
if lines[:4] == "RECT":
result[lastPin]["RECT"] = lines.split(" ")
return result
pin_of_file1 = readPinFile("osi_hbmp_top_briscm_1.lef") #lef file1
pin_of_file2 = readPinFile("osi_hbmp_top_briscm_2.lef")#lef file2
comparing between pins
with open("file04.txt", "r+") as output_file4: #compare same pins with layer and location
for pin, pin_data in pin_of_file1.items():
if pin in pin_of_file2:
if pin_of_file2[pin]["LAYER"] == pin_data["LAYER"] and pin_of_file2[pin]["RECT"] == pin_data["RECT"]:
output_file4.write(pin + "\n\n")
The TCL code I tried to get the same output
proc fileinput {filename} {
set filedata [open filename r]
set file1 [ read $filedata ]
foreach line [split $file1 \n] {
set pindata { PIN { LAYER {} RECT {} }}
if {[string match *PIN* $line]} {
dict lappend pindata PIN $line
}
if {[string match *LAYER* $line]} {
dict lappend pindata PIN {LAYER{$line}}
}
if {[string match *RECT* $line]} {
dict lappend pindata PIN {RECT{$line}}
}
}
return $pindata
}
set fileinput1 [fileinput osi_hbmp_top_briscm_1.txt]
set fileinput2 [fileinput osi_hbmp_top_briscm_2.txt]
In tcl I am trying to write comparing between the pins section, but I am stuck in the middle. i am fully confused to continue this code
foreach $pin, $pin_data [gets $fileinput1]
if{[string match $pin $fileinput2]}
This is the code I tried
The error trace tells you the immediate problem:
couldn't open "filename": no such file or directory
while executing
"open filename r"
(procedure "fileinput" line 2)
You need to give the name of the file, not the name of the variable containing the file name. Tcl cares about whether things are uses or references/names a lot. You fix this by using:
set filedata [open $filename r]
in the procedure; the added $ is vital as it says "read from the variable and use its value here".

Tcl How to sort certain words in the text and take the last one

I have a text and contains
#AA_VERSION = Aa/10.10-d87_1
#AA_VERSION = Aa/10.10-d887_1
#AA_VERSION = Aa/10.10-d138_1
#AA_VERSION = Aa/10.10-d82_1
How can I sort all the #AA_VERSION = beginning and print the last one?
And if the text don't have the # beginning ,how to show space or don't have version.
Thanks for your kindly help !!
Assuming you've already got a list of the contents of the lines, what you need to do is iterate over that list and test whether the line in question matches your critera; if it does, you store that matched information in a variable. At the end of the loop, the variable will contain the last such info that was matched.
set version ""
set current ""
foreach line $lines {
if {[regexp {^(#?)AA_VERSION *= *(.+)} $line -> commented info]} {
if {$commented eq "#"} {
set version [string trim $info]
} else {
if {$current ne ""} {
puts stderr "WARNING: multiple current versions"
}
set current [string trim $info]
}
}
}
# All lines scanned; describe what we've found
if {$version eq ""} {
puts "no #AA_VERSION line"
} else {
puts "#AA_VERSION is $version"
}
if {$current eq ""} {
puts "no current AA_VERSION"
} else {
puts "current AA_VERSION is $current"
}
The classic way to get a list of all lines in a file is this procedure:
proc linesOf {filename} {
set f [open $filename]
set data [read $filename]
close $f
return [split $data "\n"]
}
set lines [linesOf "mydata.txt"]

Convert Large CSV into multiple JSON arrays of fixed records (eg. 100 records json arrays ) using Shell

How do i convert a large CSV into JSON Arrays of fixed record set ( JSON arrays of 100 records ) through SHELL script or command line?
Eg. of Input CSV file:
identifier,type,locale
91617676848,MSISDN,es_ES
91652560975,MSISDN,es_ES
91636563675,MSISDN,es_ES
Expected output:
1.json (json array having 100 array records)
[
{
"identifier": "91617676848",
"type": "MSISDN",
"locale": "es_ES"
},
.
.
.
.
{
"identifier": "91652560975",
"type": "MSISDN",
"locale": "es_ES"
}
]
2.json (json array having 100 array records)
[
{
"identifier": "91636563675",
"type": "MSISDN",
"locale": "es_ES"
},
.
.
.
.
{
"identifier": "91636563999",
"type": "MSISDN",
"locale": "es_ES"
}
]
I created a simple php script (i called it converter.php).
You can call as is: php converter.php test.csv where test.csv contains and default csv data with the first line as header.
<?php
// open the file passed as parameter
// Ex: php converter.php test.csv
// Where test.csv contains the data passed on question
if (($handle = fopen($argv[1], 'r')) !== false) {
$count = 0;
$lines = [];
while (($data = fgetcsv($handle, 0, ',', '\'')) !== false) {
if ($count == 0) {
$headers = $data;
} else {
$lines[] = array_combine($headers, $data);
}
$count++;
}
// Here, separate in array of arrays with 100 elements on each
// On test i used 2 on second parameter of array_chunk to test with your toy data
$groups = array_chunk($lines, 100);
foreach ($groups as $key => $group) {
file_put_contents('json_data-'.$key.'.json', json_encode($group));
}
}
I run locally and i separated the files by two elements to test it and resulted in two files saved locally, named json_data-<key>.json
And the results are here:
json_data-0.json:
[
{"identifier":"91617676848","type":"MSISDN","locale":"es_ES"},{"identifier":"91652560975","type":"MSISDN","locale":"es_ES"}
]
json_data-1.json:
[
{"identifier":"91636563675","type":"MSISDN","locale":"es_ES"}
]
Would you please try an awk solution:
awk -v bs=10 '
NR == 1 {
cols = split($0, header, ",")
next
}
{
if ((NR - 1) % bs == 1) {
file = sprintf("%d.json", ++n)
print "[\n {" > file
} else {
print ",\n {" >> file
}
split($0, a, ",")
for (i = 1; i <= cols; i++) {
printf(" \"%s\": \"%s\"", header[i], a[i]) >> file
print (i < cols) ? "," : "" >> file
}
printf "%s", " }" >> file
}
(NR - 1) % bs == 0 {
print "\n]" >> file
close(file)
}
END {
if ((NR - 1) % bs != 0) print "\n]" >> file
}
' input.csv
The variable bs holds a number of arrays per file.
It processes the input file line by line and has so many conditional branches to produce proper json files. Sigh.
Using bash implementation, task can be completed by repeatedly slicing the line range from the file (2-101, 102-201, ...) until the end of the file. Code below uses sed to extract line, and csvjson to format each block into JSON.
You can replace any of your favorite tools (there are few csv to json alternatives).
Code slightly more verbose that needed.
#! /bin/sh
csv=$1
lines=$(wc -l < $csv)
blocks=$((1+(lines-1)/100))
for (( i=1 ; i <= blocks ; i++ )) ; do
sed -ne "1p;$((i*100-98)),$((i*100+1))p" $csv | csvjson -i2 > $i.json
done
Assuming reasonable file size, reprocessing the input file will not incur lot of overhead

How to find multiple sub string patterns in a string in TCL

I am trying to find multiple string patterns in a string in TCL. I cannot get the correct and optimized way to do that.
I have tried some code and it is not working
I have to find -h ,-he,-hel ,-help in the string -help
set args "-help"
set res1 [string first "-h" $args]
set res2 [ string first -he $args]
set res3 [string first -hel $args]
set res4 [string first "-help" $args"]
if { $res1 == -1 || $res2 || $res3 || $res4 } {
puts "\n string not found"
} else {
puts "\n string found"
}
how to use regexp here I am not sure , so need some inputs.
The expected output is
This is a case where using regexp is easier. (Asking if a string is a prefix of -help is a separate problem.) The trick here is to use ? and (…) (or rather (?:…) which is the non-capturing version) in the RE and you must use the -- option because the RE begins with a -:
if {[regexp -- {-h(?:e(?:lp?)?)?} $string]} {
puts "Found the string"
} else {
puts "Did not find the string"
}
If you want to know what string you actually found, add in a variable to pick up the overall match:
if {[regexp -- {-h(?:e(?:lp?)?)?} $string matched]} {
puts "Found the string '$matched'"
} else {
puts "Did not find the string"
}
If you instead want the indices where it matched, you need an extra option:
if {[regexp -indices -- {-h(?:e(?:lp?)?)?} $string match]} {
puts "Found the string at $match"
} else {
puts "Did not find the string"
}
If you were instead interested in whether the string was a prefix of -help, you instead should do:
if {[string equal -length [string length $string] $string "-help"]} {
puts "Found the string"
} else {
puts "Did not find the string"
}
Many uses of this sort of thing are actually doing command line parsing. In that case, the tcl::prefix command is very useful. For example, tcl::prefix match finds the entry in a list of options that a string is a unique prefix of and generates an error message when things are ambiguous or simply don't match; the result can be switched on easily:
set MY_OPTIONS {
-help
-someOtherOpt
}
switch [tcl::prefix match $MY_OPTIONS $string] {
-help {
puts "I have -help"
}
-someOtherOpt {
puts "I have -someOtherOpt"
}
}