1

I have a list of strings structured like:

C:/Users/scott-filter1.pgm C:/Users/scott-filter2.pgm C:/Users/scott-filter3.pgm

Essentially, what I want to do is remove C:/Users/scott- and .pgm leaving me with just filter1 for example.

So, this is my regular expression:

regsub -nocase {.pgm} [regsub -nocase {C:/Users/scott-} $list ""] ""

Which works fine, albeit a little clunky. Now, when I replace the inner regular expression with a regular expression that contains a variable, such as:

set myname scott 
{C:/Users/$myname-}

It no longer works. Any ideas on how to achieve what I want to achieve?

Thanks!

3 Answers 3

2

You will need to remove the braces as they prevent substitution (that is you won't have the variable replaced by the value of that variable and instead, you will have the literal string $myname in the regex -- also might be worth noting that $ in regex matches at the end of the string):

regsub "C:/Users/$myname-" $in "" out

Or you can do it with a single regsub:

set list "C:/Users/scott-filter1.pgm"
set myname "scott"
regsub -nocase -- "C:/Users/$myname-(.*)\\.pgm" $list {\1} out
puts $out
# => filter1

Notes:

  • If you remove the braces and use quotes, you need to double escape things you would otherwise escape once.
  • I'm using a capture group when I use parens and .* matches any character(s). The captured part is then put back using \1 in the replacement part, into the variable called out.
  • Strictly speaking, you need to escape . because this is a wildcard in regex and matches any 1 character. Because I'm using quotes, I need to double escape it with two backslashes.
  • Matching might be easier and more straightforward than substitution:

    regexp -nocase -- "C:/Users/$myname-(.*)\\.pgm" $list - out
    puts $out
    # => filter1
    
  • If the 'name' can be anything, then you can use a more generic regex to avoid having to place the name in the regex... For instance, if $myname can never have a dash, you can use the negated class [^-] which matches anything except dash and you won't have to worry about double escapes:

    regexp -nocase -- {C:/Users/[^-]+-(.*)\.pgm} $list - out
    puts $out
    # => filter1
    
Sign up to request clarification or add additional context in comments.

2 Comments

The only issue I experienced is with a list that contains more than 1 entry. For me, it treated the entire list as one string, removing "C:/Users/$myname-" from the first entry, and ".pgm" from the last entry. I just had a simple foreach loop handle each entry individually. Thanks!
@ScottJamesWalter Ah, I didn't give it a thought that $list was an actual list! Are all your strings of the same format? (i.e. like C:/Users/$myname-{something here}.pgm where $myname and {something here} cannot contain dashes/hyphens). If so, you can use a single regex like: set results [regexp -all -inline -- {[^-]+(?=\.pgm)} $list] and receive all of them at once.
1

There is another way to do this, assuming the part you want is always in a file name between a dash and the last dot before the extension.

set foo C:/Users/scott-filter1.pgm
# => C:/Users/scott-filter1.pgm
set bar [file rootname [file tail $foo]]
# => scott-filter1
set baz [split $bar -]
# => scott filter1
set qux [lindex $baz end]
# => filter1

or

lindex [split [file rootname [file tail $foo]] -] end
# => filter1

The file commands work on any string that is recognizable as a file path. file tail yields the file path minus the part with the directories, i.e. only the actual file name. file rootname yields the file name minus the extension. split converts the string into a list, splitting it at every dash. lindex gets one item from the list, in this case the last item.

An even more ad-hoc-ish (but actually quite generic) solution:

lindex [split [lindex [split $foo -] end] .] 0
# => filter1

This invocation splits the file path at every dash and selects the last item. This item is again split at every dot, and the first item of the resulting list is selected.

Documentation: file, lindex, set, split

Comments

0

Since this is a list of filenames, we can use lmap (to apply an operation to each of the elements of a list, requires 8.6) and file (specifically file tail and file rootname) to do most of the work. A simple string map will finish it off, though a regsub could also have been used.

set filenames {C:/Users/scott-filter1.pgm C:/Users/scott-filter2.pgm C:/Users/scott-filter3.pgm}
set filtered [lmap name $filenames {
    string map {"scott-" ""} [file rootname [file tail $name]]
    # Regsub version:
    #regsub {^scott-} [file rootname [file tail $name]] ""
}]

Older versions of Tcl will need to use foreach:

set filtered {}
foreach name $filenames {
    lappend filtered [string map {"scott-" ""} [file rootname [file tail $name]]]
    # Regsub version:
    #lappend filtered [regsub {^scott-} [file rootname [file tail $name]] ""]
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.