I have a directory that contains another directory named ABC_<version number>
I'd like to set my path to whatever ABC_<version number> happens to be (in a modulefile)
How do I use glob in TCL to get the name of the directory I want and put it into a TCL variable?
Thanks!
The glob command expands wildcards, but produces a Tcl list of everything that matches, so you need to be a bit careful. What's more, the order of the list is “random” — it depends on the raw order of entries in the OS's directory structure, which isn't easily predicted in general — so you really need to decide what you want. Also, if you only want a single item of the list, you must use lindex (or lassign in a degenerate operation mode) to pick it out: otherwise your code will blow up when it encounters a user who puts special characters (space, or one of a small list of other ones) in a pathname. It pays to be safe from the beginning.
For example, if you want to only match a single element and error out otherwise, you should do this:
set thePaths [glob -directory $theDir -type d ABC_*]
if {[llength $thePaths] != 1} {
error "ambiguous match for ABC_* in $theDir"
}
set theDir [lindex $thePaths 0]
If instead you want to sort by the version number and pick the (presumably) newes, you can use lsort -dictionary. That's pretty magical internally (seriously; read the docs if you want to see what it really does), but does the right thing with all sane version number schemes.
set thePaths [glob -directory $theDir -type d ABC_*]
set theSortedPaths [lsort -dictionary -decreasing $thePaths]
set theDir [lindex $theSortedPaths 0]
You could theoretically make a custom sort by the actual date on the directories, but that's more complex and can sometimes surprise when you're doing system maintenance.
Notice the use of -type d in glob. That's a type filter, which is great in this case where you're explicitly only wanting to get directory names back. The other main useful option there (in general) is -type f to get only real files.
Turns out the answer was:
set abc_path [glob -directory $env(RELDIR) ABC_*]
No need for quotes around the path. The -directory controls where you look.
Later in the modulefile
append-path PATH $abc_path
Related
I want to set the first file in my directory to a variable, how do I do it?
Here is what I have tried so far:
set files [glob "./programming/.business_files/*.txt"]
set first_file $files[0]
Your line with glob is right, but the order is not determined (it's whatever the OS system calls spit it out in, and those guarantee nothing about order in general). Thus, you'll probably want to lsort the list of filenames. The -dictionary option is highly recommended for this; it produces the order that users usually think of as the natural order of filenames.
set files [lsort -dictionary [glob "./programming/.business_files/*.txt"]]
Then you can get the first one with lindex (because Tcl uses […] for command substitutions, not indexing).
set first_file [lindex $files 0]
If you want to keep (for some reason) the array approach, a slightly less than ideal solution is:
set n 0
foreach file [lsort -dictionary [glob "./programming/.business_files/*.txt"]] {
set files($n) $file
incr n
}
set first_file $files(0)
That said, the lindex solution above is the cleanest.
Bye
I capture a list of pictures in a repository folder like this:
foreach image [lsort [glob -nocomplain -directory $image_path -type f *]] {
puts $image
}
All the images come back sorted because of the lsort, but a few images simply doesn't get sorted, and I haven't been able to figure out why.
The order returned from the folder is:
Repository/Unsorted/3.jpg
Repository/Unsorted/30.jpg
Repository/Unsorted/33.jpg
Repository/Unsorted/6.jpg
Repository/Unsorted/9.jpg
Expected:
Repository/Unsorted/3.jpg
Repository/Unsorted/6.jpg
Repository/Unsorted/9.jpg
Repository/Unsorted/30.jpg
Repository/Unsorted/33.jpg
Update:
When I use the -dictionary switch it returns the correct order. Can someone elaborate on why?
foreach image [lsort -dictionary [glob -nocomplain -directory $image_path -type f *]] {
puts $image
}
The lsort command has several ways in which it can decide the ordering of elements.
-ascii (the default mode for various reasons, slightly misnamed) just uses the numeric ordering of each pair characters in the two strings, in the order of the characters in the string. It's the sort of thing you'd expect with the C strcmp()… if that was Unicode-aware.
-dictionary is the same… except that digit sequences in the string (so just the characters 0 through 9; not - so no negative numbers) are compared as numbers. This gives an ordering that feels a lot more like what you get in a dictionary; it was specifically made to produce a pleasing order of filenames in Tk's file selection dialogs.
and, for completeness, -integer and -real parse the strings as integers and floating-point numbers respectively and then sort those, and -command lets you provide your own ordering command (slower, but totally gives you control).
The only reason that Tcl knows in what order to sort values is because you tell it.
Thus, when comparing Repository/Unsorted/3.jpg and Repository/Unsorted/30.jpg, in -ascii mode the . (Unicode U+00002E) comes before 0 (Unicode U+000030), whereas in -dictionary mode the 3 and the 30 digit sequences are parsed as integers and compared that way (because the non-numeric part before that was identical).
I am trying to move a large number of files using Tcl and came across the expression :
file rename {*}[glob *tcl] dir/ which works perfectly.
Can anyone explain how this command works or what this feature is called?
It's a compound of two commands and some useful syntax.
glob returns a list of filenames that match the pattern, *tcl in your case, or an error if nothing matches. There's a bunch of options you could use to modify what it returns, but you're not using any of them; that's great for your use case.
file rename will rename files or move files around. In particular, when the final argument is an existing directory name, the other arguments are files (or directories) that will be moved into that directory. (That it moves things around is sensible if you're familiar with how POSIX system calls work.)
The final piece of the puzzle is {*}[…], i.e., command expansion, which runs a command (which is glob *tcl in your case) and uses the elements of the list it returns as a sequence of arguments to the command call within which it is used. Which is useful; we want a list of filenames at that point of the call to file rename. There's no real limit on the number of arguments that can be moved around that way, other than basic things like memory and so on.
The {*} prefix (it's only special at the start of a word) can be used with other well-formed ways of producing a Tcl word (e.g., a read from a variable with $ or a literal with {…}) or even with a compound word, though use with compound words is usually a sign that what you're doing is probably unwise.
If you have old Tcl code, written for Tcl 8.4 or before, you won't see {*}. Instead, you'd see something like this:
eval file rename [glob *tcl] dir/
# Or, more properly, one of these horrors:
eval {file rename} [glob *tcl] {dir/}
eval [list file rename] [glob *tcl] [list dir/]
eval [linsert [linsert [glob *tcl] 0 file rename] end dir/]
These were notoriously awkward to get right in tricky cases (causing many subtle bugs). The expansion syntax was added in Tcl 8.5 exactly to get rid of this whole class of trouble. eval still exists in modern Tcl, but it is now thankfully rarely used.
I'm trying to simply process a list of files in a directory using Tcl, but want to process them in age order (oldest mtime to newest). I expected some sort of argument in glob or lsort to sort by file mtime, but I don't see such option.
I am trying to avoid creating a custom function to do this
Is there an option which I am missing that will do this built-in?
None that I know of, but you could of course exec your system's file listing command with the appropriate options.
Taking the mtime is a fairly expensive operation, so applications that use it typically take shortcuts to avoid querying for it. Making it portable also adds overhead.
Anyway, it's easy to implement it:
set files [glob x*]
set fileAndMTime [lmap name $files {list $name [file mtime $name]}]
lmap item [lsort -integer -index 1 $fileAndMTime] {lindex $item 0}
The last line gives you a list of filenames, sorted in order of least mtime to greatest mtime (use -decreasing to reverse order, and note that the sort is stable).
Documentation:
file,
glob,
lindex,
lmap (for Tcl 8.5),
lmap,
lsort
Could you please help me with the following script?
It is a Tcl script which Synopsys IC Compiler II will source.
set_dont_use [get_lib_cells */*CKGT*0P*] -power
set_dont_use [get_lib_cells */*CKTT*0P*] -setup
May I know how to take only */*CKGT*0P* and */*CKTT*0P* and assign these to a variable.
Of course you can treat a Tcl script as something you search through; it's just a file with text in it after all.
Let's write a script to select the text out. It'll be a Tcl script, of course. For readability, I'm going to put the regular expression itself in a global variable; treat it like a constant. (In larger scripts, I find it helps a lot to give names to REs like this, as those names can be used to remind me of the purpose of the regular expression. I'll call it “RE” here.)
set f [open theScript.tcl]
# Even with 10 million lines, modern computers will chew through it rapidly
set lines [split [read $f] "\n"]
close $f
# This RE will match the sample lines you've told us about; it might need tuning
# for other inputs (and knowing what's best is part of the art of RE writing)
set RE {^set_dont_use \[get_lib_cells ([\w*/]+)\] -\w+$}
foreach line $lines {
if {[regexp $RE $line -> term]} {
# At this point, the part you want is assigned to $term
puts "FOUND: $term"
}
}
The key things in the RE above? It's in braces to reduce backslash-itis. Literal square brackets are backslashed. The bit in parentheses is the bit we're capturing into the term variable. [\w*/]+ matches a sequence of one or more characters from a set consisting of “standard word characters” plus * and /.
The use of regexp has -> as a funny name for a variable that is ignored. I could have called it dummy instead; it's going to have the whole matched string in it when the RE matches, but we already have that in $term as we're using a fully-anchored RE. But I like using -> as a mnemonic for “assign the submatches into these”. Also, the formal result of regexp is the number of times the RE matched; without the -all option, that's effectively a boolean that is true exactly when there was a match, which is useful. Very useful.
To assign the output of any command <command> to a variable with a name <name>, use set <name> [<command>]:
> set hello_length [string length hello]
5
> puts "The length of 'hello' is $hello_length."
The length of 'hello' is 5.
In your case, maybe this is what you want? (I still don't quite understand the question, though.)
set ckgt_cells [get_lib_cells */*CKGT*0P*]
set cktt_cells [get_lib_cells */*CKTT*0P*]