Get result of TCL exec command into array - tcl

How to get the result of a tcl exec command into an array of strings where each item is a line of my exec output?
Example:
exec ls -la
How to capture that result into an array and print it in a foreach?

Can I advise you to use list instead of array? If so...
set output [exec ls]
set output_list [split $output \n]
foreach line $output_list {
puts $line
}
List is much more useful collection in this situation, because all you need is to store lines one by one. On the other hand, array in Tcl was made to store named collection (without order).
I can make it with array, but it would be ugly.
set output [exec ls]
set output_list [split $output \n]
set i 0
foreach line $output_list {
set arr($i) $line
incr i
}
foreach index [array names arr] {
puts $arr($index)
}
As you can see, foreach for arrays can't guaranty order of records. For example I've got this
% foreach index [array names arr] {
puts arr($index)
}
arr(8)
arr(4)
arr(0)
arr(10)
arr(9)
arr(5)
arr(1)
arr(6)
arr(2)
arr(7)
arr(3)
So if you want to work with array as it is ordered collection, you need to use counter.
for {set i 0} {$i < [array size arr]} {incr i} {
puts $arr($i)
}

Related

Getting the next key in an array

My goal is to find the next key in an array... below my data :
# Index increment may change, there is not necessarily continuity like this example.
# My $index can be 1,2,3,4,8,12,25,32...
# but the size of my array is about 100,000 elements.
for {set index 1} {$index < 100000} {incr index} {
set refdata($index,Pt,X) [expr {10 + $index}]
}
I need to know the next key to be able to build a geometric line... I did not find in the help a command that allows me to find the next key of my array so I created my own function below :
proc SearchNextKeyArrayElement {dataarray mykey} {
upvar $dataarray myarray
set mydata [lsort -dictionary [array names myarray]]
set index [lsearch $mydata $mykey]
if {$index > -1} {
return [lindex $mydata [expr {$index + 1}]]
}
return ""
}
foreach k [lsort -dictionary [array names refdata]] {
if {[string match "*,Pt,*" $k]} {
set nextkey [SearchNextKeyArrayElement refdata $k]
}
}
And it takes a long time...array nextelement command is maybe the solution...But I do not understand how to use it ?
Here's an example:
start a search with array startsearch
loop while array anymore is true
get the next key with array nextelement
tidy up with array donesearch
use try {} catch {} finally for safety
# array foreach
# to be subsumed in Tcl 8.7 by `array for`
# https://core.tcl.tk/tips/doc/trunk/tip/421.md
#
# example:
# array set A {foo bar baz qux}
# array foreach {key val} A {puts "name=$key, value=$val"}
#
# A note on performance: we're not saving any time with this approach.
# This is essentially `foreach name [array names ary] {...}
# We are saving memory: iterating over the names versus extracting
# them all at the beginning.
#
proc array_foreach {vars arrayName body} {
if {[llength $vars] != 2} {
error {array foreach: "vars" must be a 2 element list}
}
lassign $vars keyVar valueVar
# Using the complicated `upvar 1 $arrayName $arrayName` so that any
# error messages propagate up with the user's array name
upvar 1 $arrayName $arrayName \
$keyVar key \
$valueVar value
set sid [array startsearch $arrayName]
# If the array is modified while a search is ongoing, the searchID will
# be invalidated: wrap the commands that use $sid in a try block.
try {
while {[array anymore $arrayName $sid]} {
set key [array nextelement $arrayName $sid]
set value [set "${arrayName}($key)"]
uplevel 1 $body
}
} trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
puts stderr [list $e]
dict set e -errorinfo "detected attempt to add/delete array keys while iterating"
return -options $e
} finally {
array donesearch $arrayName $sid
}
return
}
Generally speaking, Tcl arrays have no order at all; they can change their order on any modification to the array or any of its elements. The commands that iterate over the array (array for, array get, array names, and the iteration commands array startsearch/array nextelement/array anymore) only work with the current order. However, you can use array names to get the element names into a Tcl list (which is order preserving), sort those to get the order that you're going to iterate over, and then use foreach over that. As long as you're not adding or removing elements, it'll be fine. (Adding elements is sort-of OK too; you'll just not see them in your iteration.)
foreach key [lsort -dictionary [array names myarray]] {
ProcessElement $key $myarray($key)
}
By contrast, trying to just go from one element to the next will hurt a lot; that operation is not exposed.
Using the iteration commands is done like this:
set s [array startsearch myarray]
while {[array anymore myarray $s]} {
set key [array nextelement myarray $s]
ProcessElement $key $myarray($key)
}
Note that you don't get an option to sort the search. You won't see these used much in production code; doing array names or array get is usually better. And now (well, 8.7 is still in alpha) you've also got array for:
array for {key value} myarray {
ProcessElement $key $value
}
Efficient for large arrays, but still doesn't permit sorting; supporting direct sorting would require a different sort of storage engine on the back of the array.
This is why it's slow: You're sorting the array names once for the foreach command and then again for each element. Sort once and cache it, then you can iterate over it much more efficiently
set sorted_names [lsort -dictionary [array names refdata -glob {*,Pt,*}]]
set len [llength $sorted_names]
for {set i 0; set j 1} {$i < $len} {incr i; incr j} {
set this_name [lindex $sorted_names $i]
set next_name [lindex $sorted_names $j]
# ...
}

How to find duplicated strings which appear more than once in a file

I have following code to print string which appears more than once in the list
set a [list str1/str2 str3/str4 str3/str4 str5/str6]
foreach x $a {
set search_return [lsearch -all $a $x]
if {[llength $search_return] > 1} {
puts "search_return : $search_return"
}
}
I need to print str3/str4 which appears more than once in the list
The canonical methods of doing this are with arrays or dictionaries, both of which are associative maps. Here's a version with a single loop over the data using a dictionary (it doesn't know the total number of times an item appears when it prints, but sometimes just knowing you've got a multiple is enough).
set a [list str1/str2 str3/str4 str3/str4 str5/str6]
# Make sure that the dictionary doesn't exist ahead of time!
unset -nocomplain counters
foreach item $a {
if {[dict incr counters $item] == 2} {
puts "$item appears several times"
}
}
I guess you could use an array to do something like that, since arrays have unique keys:
set a [list str1/str2 str3/str4 str3/str4 str5/str6]
foreach x $a {
incr arr($x) ;# basically counting each occurrence
}
foreach {key val} [array get arr] {
if {$val > 1} {puts "$key appears $val times"}
}

how to split a file to list of lists TCL

I'm coding TCL and I would like to split a file into two lists of lists,
the file contain:
(1,2) (3,4) (5,6)
(7,8) (9,10) (11,12)
and I would like to get two list
one for each line, that contain lists that each one contain to two number
for example:
puts $list1 #-> {1 2} {3 4} {5 6}
puts [lindex $list1 0] #-> 1 2
puts [lindex $list2 2] #-> 11 12
I tried to use regexp and split but no success
The idea of using regexp is good, but you'll need to do some post-processing on its output.
# This is what you'd read from a file
set inputdata "(1,2) (3,4) (5,6)\n(7,8) (9,10) (11,12)\n"
foreach line [split $inputdata "\n"] {
# Skip empty lines.
# (I often put a comment format in my data files too; this is where I'd handle it.)
if {$line eq ""} continue
# Parse the line.
set bits [regexp -all -inline {\(\s*(\d+)\s*,\s*(\d+)\s*\)} $line]
# Example results of regexp:
# (1,2) 1 2 (3,4) 3 4 (5,6) 5 6
# Post-process to build the lists you really want
set list([incr idx]) [lmap {- a b} $bits {list $a $b}]
}
Note that this is building up an array; long experience says that calling variables list1, list2, …, when you're building them in a loop is a bad idea, and that an array should be used, effectively giving variables like list(1), list(2), …, as that yields a much lower bug rate.
An alternate approach is to use a simpler regexp and then have scan parse the results. This can be more effective when the numbers aren't just digit strings.
foreach line [split $inputdata "\n"] {
if {$line eq ""} continue
set bits [regexp -all -inline {\([^()]+\)} $line]
set list([incr idx]) [lmap substr $bits {scan $substr "(%d,%d)"}]
}
If you're not using Tcl 8.6, you won't have lmap yet. In that case you'd do something like this instead:
foreach line [split $inputdata "\n"] {
if {$line eq ""} continue
set bits [regexp -all -inline {\(\s*(\d+)\s*,\s*(\d+)\s*\)} $line]
set list([incr idx]) {}
foreach {- a b} $bits {
lappend list($idx) [list $a b]
}
}
foreach line [split $inputdata "\n"] {
if {$line eq ""} continue
set bits [regexp -all -inline {\([^()]+\)} $line]
set list([incr idx]) {}
foreach substr $bits {
lappend list($idx) [scan $substr "(%d,%d)"]
# In *very* old Tcl you'd need this:
# scan $substr "(%d,%d)" a b
# lappend list($idx) [list $a $b]
}
}
You have an answer already, but it can actually be done a little bit simpler (or at least without regexp, which is usually a good thing).
Like Donal, I'll assume this to be the text read from a file:
set lines "(1,2) (3,4) (5,6)\n(7,8) (9,10) (11,12)\n"
Clean it up a bit, removing the parentheses and any white space before and after the data:
% set lines [string map {( {} ) {}} [string trim $lines]]
1,2 3,4 5,6
7,8 9,10 11,12
One way to do it with good old-fashioned Tcl, resulting in a cluster of variables named lineN, where N is an integer 1, 2, 3...:
set idx 0
foreach lin [split $lines \n] {
set res {}
foreach li [split $lin] {
lappend res [split $li ,]
}
set line[incr idx] $res
}
A doubly iterative structure like this (a number of lines, each having a number of pairs of numbers separated by a single comma) is easy to process using one foreach within the other. The variable res is used for storing result lines as they are assembled. At the innermost level, the pairs are split and list-appended to the result. For each completed line, a variable is created to store the result: its name consists of the string "line" and an increasing index.
As Donal says, it's not a good idea to use clusters of variables. It's much better to collect them into an array (same code, except for how the result variable is named):
set idx 0
foreach lin [split $lines \n] {
set res {}
foreach li [split $lin] {
lappend res [split $li ,]
}
set line([incr idx]) $res
}
If you have the results in an array, you can use the parray utility command to list them in one fell swoop:
% parray line
line(1) = {1 2} {3 4} {5 6}
line(2) = {7 8} {9 10} {11 12}
(Note that this is printed output, not a function return value.)
You can get whole lines from this result:
% set line(1)
{1 2} {3 4} {5 6}
Or you can access pairs:
% lindex $line(1) 0
1 2
% lindex $line(2) 2
11 12
If you have the lmap command (or the replacement linked to below), you can simplify the solution somewhat (you don't need the res variable):
set idx 0
foreach lin [split $lines \n] {
set line([incr idx]) [lmap li [split $lin] {
split $li ,
}]
}
Still simpler is to let the result be a nested list:
set lineList [lmap lin [split $lines \n] {
lmap li [split $lin] {
split $li ,
}
}]
You can access parts of the result similar to above:
% lindex $lineList 0
{1 2} {3 4} {5 6}
% lindex $lineList 0 0
1 2
% lindex $lineList 1 2
11 12
Documentation:
array,
foreach,
incr,
lappend,
lindex,
lmap (for Tcl 8.5),
lmap,
parray,
set,
split,
string
The code works for windows :
TCL file code is :
proc captureImage {} {
#open the image config file.
set configFile [open "C:/main/image_config.txt" r]
#To retrive the values from the config file.
while {![eof $configFile]} {
set part [split [gets $configFile] "="]
set props([string trimright [lindex $part 0]]) [string trimleft [lindex $part 1]]
}
close $configFile
set time [clock format [clock seconds] -format %Y%m%d_%H%M%S]
set date [clock format [clock seconds] -format %Y%m%d]
#create the folder with the current date
set folderPath $props(folderPath)
append folderDate $folderPath "" $date "/"
set FolderCreation [file mkdir $folderDate]
while {0} {
if { [file exists $date] == 1} {
}
break
}
#camera selection to capture image.
set camera "video"
append cctv $camera "=" $props(cctv)
#set the image resolution (XxY).
set resolutionX $props(resolutionX)
set resolutionY $props(resolutionY)
append resolution $resolutionX "x" $resolutionY
#set the name to the save image
set imagePrefix $props(imagePrefix)
set imageFormat $props(imageFormat)
append filename $folderDate "" $imagePrefix "_" $time "." $imageFormat
set logPrefix "Image_log"
append logFile $folderDate "" $logPrefix "" $date ".txt"
#ffmpeg command to capture image in background
exec ffmpeg -f dshow -benchmark -i $cctv -s $resolution $filename >& $logFile &
after 3000
}
}
captureImage
thext file code is :
cctv=Integrated Webcam
resolutionX=1920
resolutionY=1080
imagePrefix=ImageCapture
imageFormat=jpg
folderPath=c:/test/
//camera=video=Integrated Webcam,Logitech HD Webcam C525
This code works for me me accept the code from text file were list of parameters are passed.

Ignoring lines in a file TCL

Suppose I have a file1 with a few queries inside,
Query 1
Query 2
Query 3
And I have a normal text file2 containing a bunch of data
Data 1 Query 1 something something
Data something Query 2 something something
Something Query 3 something something
Data1 continue no query
Data2 continue no query
How do I create a loop such that it ignores the lines containing queries from file1 and prints only lines without the queries in the file? so in this case only these values gets printed
Data1 continue no query
Data2 continue no query
i tried producing the results using this loop script i made
Storing the queries to be ignored from file1 into $wlistItems
set openFile1 [open file1.txt r]
while {[gets openFile1 data] > -1} {
set wlist $data
append wlistItems "{$wlist}\n"
}
close $openFile1
Processing file2 to print lines without ignored queries
set openFile2 [open file2.txt r]
while {[gets $openFile2 data] > -1} {
for {set n 0} {$n < [llength $wListItems]} {incr n} {
if {[regexp -all "[lindex $wListItems $n]" $data all value]} {
continue
}
puts $data
}
}
close $openFile2
However, the script does not skip the lines. It instead prints out repeated data from file2.
while {[gets $openFile2 data] > -1} {
set found 0
for {set n 0} {$n < [llength $wListItems]} {incr n} {
if {[regexp -all "[lindex $wListItems $n]" $data all value]} {
set found 1
break
}
}
if {!$found} {
puts $data
}
}
A simpler solution:
package require fileutil
set queries [join [split [string trim [::fileutil::cat file1]] \n] |]
::fileutil::foreachLine line file2 {
if {![regexp ($queries) $line]} {
puts $line
}
}
The first command (after the package require) reads the file with the queries and packs them up as a set of branches (Query 1|Query 2|Query 3). The second command processes the second file line by line and prints those lines that don't contain any of those branches.
Documentation: fileutil package, if, join, package, puts, Syntax of Tcl regular expressions, regexp, set, split, string
I'd just do this:
puts [exec grep -Fvf file1 file2]

How to do lappend in a while loop using regexp

when I am using the while loop to match a variable using regexp I want the matched variable to form a list by using lappend. The code is
set file_name [open filename.txt r]
set newlist [list]
while {[gets $file_name line] >= 0} {
regexp {cell \(\"(.*)\"} $line match cell_name
if {[info exists cell_name] && $cell_name != ""} {
puts "$cell_name"
lappend $newlist $cell_name
unset cell_name
}
}
foreach item $newlist {puts $item}
close $file_name
The text which it is matching is like cell ("aabbcc") where the values inside the quotes are changing. The values are getting captured by this code but it is not creating a list after appending. I need all the values as a list. Can u pls tell me where I am going wrong.
A $ too much.
Change the line
lappend $newlist $cell_name_matched
to
lappend newlist $cell_name_matched
Otherwise you find the result in ${}.
You should also check if you regexp finds something:
if {[regexp {cell \(\"(.*)\"} $line match cell_name_matched]} {
puts "$cell_name_matched"
lappend newlist $cell_name_matched
}
The unset will probably throw an error too, leave it alone and remove it.