I am trying to make multiple directories and also trying to search and list all files found in a specific path.
proc filesearch {indir1 indir2 indir3 indir4 indir5} {
set infile1 [glob -nocomplain -type f $indir1$indir2/*txt*]
puts $infile1
}
When I puts $infile1, it puts all the file found into 1 long single line (as below). How can I split each file up into single line (put in $infile1).
The above puts $infile1, puts all into single line
a/b/c/d/a.txt a/b/c/d/b.xt a/b/c/d/c.txt a/b/c/d/d.txt
How do I puts out every file found into multiple lines?
a/b/c/d/a.txt
a/b/c/d/b.txt
a/b/c/d/c.txt
a/b/c/d/d.txt
Print all files found into individual line. Current output I see, list all the files in a single line separated with a space.
You just need to loop through the list.
foreach elem $infile1 {
puts $elem
}
Reference : foreach
Just join the list of files using newline as a separator:
puts [join $infile1 \n]
Related
Below code, append can add all file paths to a single line, but there is no space between them.
How to add a space between each path?
set all_path ""
foreach line $lines {
set filepath [proc_get_file_path $line]
...
#some commands
...
append ::all_path $filepath
}
Expected output:
../path/a ../path/b ../path/c ...
How do you want to use all_path later on?
From the distance, this is where you would like to use a Tcl list:
set all_path [list]
foreach line $lines {
set filepath [proc_get_file_path $line]
# ...
lappend all_path $filepath
}
The string representation of Tcl lists would also match your expectation re a whitespace delimiter. You can also assemble such a string manually, with append introducing a whitespace explicitly: append all_path " " $filepath. But maybe, this is not what you want to begin with ...
I have two variables, let's say $a with the list name & $b with age, when I try to redirect into csv, it creates csv but both $a & $b are falling into same same row instead of different, How can i separate them into two different rows?
puts $outfile "$b_1 \t$c"
set outfile [open "result_table_sort.csv" w+]
The recommended way of creating a CSV file is with the csv package in Tcllib. It handles all sorts of tricky edge cases for you so you don't have to.
package require csv
set outfile [open "thing.csv" w]
foreach aRow $a bRow $b {
puts $outfile [csv::join [list $aRow $bRow]]
}
close $outfile
You can switch to producing tab-separated output by passing \t as the separator character to csv::join:
puts $outfile [csv::join [list $aRow $bRow] "\t"]
Also consider using the csv::joinlist and csv::joinmatrix commands. They both let you build an entire table in one go, the first as a list of lists (list of rows, each row containing a list of columns) and the second as a matrix (structure defined in struct::matrix package in Tcllib).
Found the solution by putting comma in between two variable solved my issue....
puts $outfile "$b_1,\t$c"
set outfile [open "result_table_sort.csv" w+]
First time poster and new to TCL so please pardon my knowledge.
I've found a few examples on stackoverflow and with that help created a script.
I need to modify few lines of a file, I've tried the following (see code). I can seem to add the line of interest but it does not write it in the correct location e.g. if I want to replace line 3 it adds line after line 3
and moreover deletes subsequent lines if there is more than one line operation.
Lastly could some one kindly suggest the best way to identify the line of interest with name rather than line number. Name is always in the form Filter.HpOrd_n =
where n is 0...k
Data in info.dat
AA
BB
Filter.HpOrd_1 = 2
Filter.HpOrd_2 = 2
Filter.HpOrd_3 = 0.1
Filter.HpOrd_4 = 0.2
CC
DD
EE
FF
Code:
set fd [open "info.dat" r+]
set i 0
while { [gets $fd line] != -1 } {
set line [split $line "\n"]
incr i
if {$i == 3} {
set nLine [lreplace $line 0 0 Filter.LoPass]
puts $fd [join $nLine "\n"]
}
if {$i == 6} {
set nLine [lreplace $line 0 0 Filter.Butterworth]
puts $fd [join $nLine "\n"]
}
}
close $fd
With plain Tcl:
# the input and output file handles
set fin [open info.dat r]
set fout [file tempfile fname]
# process the file
while {[gets $fin line] != -1} {
puts $fout [string map {
"Filter.HpOrd_1" "Filter.LoPass"
"Filter.HpOrd_4" "Filter.Butterworth"
} $line]
}
close $fin
close $fout
# backup the original and overwrite it
file link -hard info.dat.bak info.dat
file rename -force -- $fname info.dat
TCL is just a meta language and set fd [open "info.dat" r+] is related to general file descriptor handling. If you open a file descriptor "r+" you can read and write to that file descriptor, but one file descriptor always points to one point in a file.
With "r+" your file descriptor initially points to the start of the file. Then you gets $fd line a line from the file, so $fd points to the start of the second line afterwards. Now you puts $fs [join $nline "\n"] blindly overwriting from the start of the second line and so on.
Generally you cannot replace lines in one file, but you will write a second file and move that after you closed both files. You can overwrite with seek, but you overwrite from a point in the file. So what you put should always have the same size, of you have read before.
Plain files (in basically all programming languages) are byte/character oriented rather than line oriented. This means 1) that you need to use a seek operation to get back to the beginning of the line you want to overwrite, and 2) unless the new line is exactly the same length as the old one, you will experience stub lines around it.
You have other problems as well. set line [split $line "\n"] doesn't do anything: you've just read line from gets, so it's guaranteed not to have any newlines in it. [join $nLine "\n"] doesn't do what you probably think it does: it will replace any sequences of whitespace in $line with single newlines, but it will not place any newline at the end of the string.
Unless your files are insanely large, I recommend something like this:
Replace by line number
proc lineReplace args {
set lines [split [lindex $args end] \n]
foreach {n line} [lrange $args 0 end-1] {
set index [incr n -1]
if {$index > 0} {
lset lines $index $line
}
}
join $lines \n
}
package require fileutil
fileutil::updateInPlace info.dat {
lineReplace
3 Filter.LoPass
6 Filter.Butterworth
}
In the "front end" you only specify the command to use and thereafter pairs of line number / new line text.
In the "back end" (the lineReplace command) the parameter args will contain those number / line pairs and at the end, as a single item, the complete contents of the file. The file contents are then split into a list of lines, and for every number / line pair you replace one of the items in that list. Finally, the list of lines are joined back into a string with newlines between each line. This string is returned by lineReplace to fileutil::updateInPlace, which replaces the old contents in the file with the returned string.
Replace by name
proc lineReplaceByName args {
set lines [split [lindex $args end] \n]
foreach {name line} [lrange $args 0 end-1] {
set index [lsearch $lines $name*]
if {$index > 0} {
lset lines $index $line
}
}
join $lines \n
}
fileutil::updateInPlace info.dat {
lineReplaceByName
Filter.HpOrd_1 Filter.LoPass
Filter.HpOrd_4 Filter.Butterworth
}
In this case the "back end" calculates the line number by searching for the given name at the beginning of each line. If the name isn't found, the replacement operation is skipped. Otherwise it's the same as before.
Replacing just the name
If you don't want to replace the complete line, but just the name part of it, some changes are necessary. If you are 100% sure that 1) the name never has any whitespace in it, and 2) there is always whitespace between the name and the =, you can just replace lset lines $index $line with lset lines $index 0 $line. If you want to play it safer, you can replace the line with
lset lines $index [regsub {.+(?=\s*=\s*)} [lindex $lines $index] $line]
which uses a regular expression to find the character region that precedes the = character (optionally with whitespace around it) and then replaces that with the text you provided.
The fileutil package is a part of the Tcllib companion library to Tcl.
Documentation: fileutil package, foreach, if, incr, join, lindex, lrange, lsearch, lset, package, proc, regsub, seek, set, split
I'm trying to write some data from iperf to a file using tcl script.The file has more than 100 lines. Now i need to parse the first 10 lines, neglect it and consider the next set of 10 lines and print it, again i need to neglect the next set of 10 lines and print the next 10 lines and keep continuing until i reach the end of file. How could i do it programmatic ally?
exec c:\\iperf_new\\iperf -c $REF_WLAN_IPAddr -f m -w 2M -i 1 -t $run_time > xx.txt
set fp [open "xx.txt" r ]
set file_data [read $fp]
set data [split $file_data "\n"]
foreach line $data {
if {[regexp {(MBytes) +([0-9\.]*)} $line match pre tput]==1 } {
puts "Throughput: $tput Mbps"
}
Well, as your example shows, you have found out how to split a (slurped) file into lines and process them one-by-one.
Now what's the problem with implementing "skip ten lines, process ten lines, skip another ten lines etc"? It's just about using a variable which counts lines seen so far plus selecting a branch of code based on its value. This approach has nothing special when it comes to Tcl: there are commands available to count, conditionally select branches of code and control looping.
If branching based on the current value of a line counter looks too lame, you could implement a state machine around that counter variable. But for this simple case it looks like over-engeneering.
Another approach would be to pick the necessary series of lines out of the list returned by split using lrange. This approach might use a nice property of lrange which can be told to return a sublist "since this index and until the end of the list", so the solution really boils down to:
set lines [split [read $fd] \n]
parse_header [lrange $lines 0 9]
puts [join [lrange $lines 10 19] \n]
parse_something_else [lrange 20 29]
puts [join [lrange $lines 30 end] \n]
For a small file this solution looks pretty compact and clean.
If I understood you correctly, you want to print lines 11-20, 31-40, 51-60,... The following will do what you want:
package require Tclx
set counter 0
for_file line xxx.txt {
if {$counter % 20 >= 10} { puts $line }
incr counter
}
The Tclx package provides a simple way to read lines from a file: the for_file command.
The following code is skipping odd lines like 1,3,5,7,9......wat has to be done to get all lines from a file using this code
set in [filename r]
seek $in 0 start
while { [gets $in line] != -1 } {
gets $in line
puts $line
}
You're doing gets $in line once in the condition and once inside the loop body; the line read in the condition gets lost as a result. You probably want to remove the one in the loop body.
You have used gets twice that is why you are getting only the odd lines
Other solution:
Instead of using gets I prefer using read function to read the whole contents of the file and then process those line by line. So we are in complete control of operation on file by having it as list of lines
set fileName [lindex $argv 0]
catch {set fptr [open $fileName r]} ;
set contents [read $fptr] ;#Read the file contents
close $fptr ;Close the file since it has been read now
set splitCont [split $contents "\n"] ;#Split the files contents on new line
splitCont is the list which has has lines of the file as individual elements