Retrieve specific contents of a column in a CSV file - tcl

I have csv file having first column as
shar-02-bc [0.49%]
rip-086-cd [0.56%]
.
.
In every cell there are 8 chars before the square braces.
Is there any method to remove the square braces and it's contents.
The modified csv file should contain the column as
shar-02-bc
rip-086-cd

If each row (not column) of the column is something like this:
shar-02-bc [0.49%] rip-086-cd [0.56%]
then you have a few options.
If you can treat each line like a list, then use lsearch to find the items which aren't inside square brackets (the glob pattern needs to backslash escape the square brackets)
lsearch -all -not -inline $line {\[*\]}
or you could treat each like like a dictionary with alternating key value pairs:
dict keys $line
If you treat each line like a string, then regsub is another option. This replaces any non-whitespace inside square brackets with an empty string.
regsub -all {\[\S+\]} $line ""

Related

lappend adds curly braces to the element

I am using lappend in script, doing something like this:
set element_name a[0]
lappend mylist $element_name
I see that if the $element_name contains a [] in it for e.g. a[0]. The lappend adds curly braces to it. Like if I print $mylist then it gives:
{a[0]}
How do i get rid of those curly braces? I just want a[0].
The braces disappear if you use join to convert the list to a string:
puts [join $mylist]
# => a[0]
or if you print the elements separately:
foreach elem $mylist {puts $elem}
The string representation of a list needs to quote elements that contain characters that are significant to the interpreter and/or interfere with the list structure. Brackets (with a script in between them) are usually interpreted as a command substitution, and to prevent the interpreter from evaluating that, your elements are wrapped in braces. Once the list elements are converted to a string, this ceases to be an issue, and the text you stored in them is no longer wrapped up.
Documentation: foreach, join, puts

How to trim two words from right of a string

I want to remove two words from right of a string.
For example:
set str "sachin is the pride of india"
I need to remove india and of from right and there should be no space after that.
I have tried using string trimright.
The string trimright command is exactly the wrong tool for this; it treats its trim argument as a set of characters to remove, not a literal. The simplest way of doing this is with lreplace, provided the string doesn't contain list metacharacters and you don't care about the number of spaces.
set shortened [lreplace $str end-1 end]
If you need to do it reliably, regular expressions are the tool of choice.
set shortened [regsub {\s*\S+\s+\S+\s*$} $str ""]
Use regsub for this. Please.

regular expression to treat unbalanced braces as a word

I am getting an error message in this regex when line contains unbalanced braces.
set line "a b { c{}"
set lst [regexp -all -inline {^(\s*(\S*)\s*)*(\{(.*)\})?(\s*(\S*)\s*)*$} $line]
set lst [lindex $lst 0]
set firstelement [lindex $lst 0]
How to avoid such cases and treat unbalanced braces as a word?
When you have a string from an arbitrary source (like a user) there's no guarantee at all that it is a well-formed list. Now regexp -inline returns a list of what it matched, but the elements of that list are strings (unless you use the -indices option, of course) and that means that you can't safely use lindex on them to pick out the pieces.
The safe way to get the first “word”, assuming you define “word” to be “sequence of non-whitespace characters” (the usual user definition), is to do this:
set firstWord [lindex [regexp -all -inline {\S+} $item] 0]
It's a bit ugly, but it's totally safe. (In fact, for the first word only, use regexp -inline {\S+} $item on its own, but that won't let you get later words.)
Using split to break a string into words is also possible, but that strongly assumes that the word separator is a single (whitespace-by-default) character and does something that you might not expect if you have multi-whitespace separators, or leading and trailing whitespace. Frankly, it's more useful for dividing up non-whitespace separated strings (e.g., a file into lines, an /etc/passwd record into fields) or for turning a string into the list of its characters (with an empty second argument).
The regexp command returns a list. You then take the first element of the list. But in the final line you then treat that element as a list - but it is not guaranteed to be so - hence the actual string content matters. Instead, if you want to deal with this item as a list you need to use split and convert it into words:
% split "a b {" " "
a b \{
In your case:
set lst [lindex $lst 0]
set firstelement [lindex [split $lst " "] 0]
You may also want to look into subst. It looks like you are trying to read poorly specified tcl lists as input and doing some parsing to get them as a proper tcl list. In which case, subst -nocommands [lindex $lst 0] might be more helpful to you. For example:
% lindex [subst -nocommands [lindex $lst 0]] 2
c{}
Note that this is the content of the braced part of $line.

Grep the word inside double quote

How can I extract a word inside a double quote inside a file?
e.g.
variable "xxx"
Reading a text file into Tcl is just this:
set fd [open $filename]
set data [read $fd] ;# Now $data is the entire contents of the file
close $fd
To get the first quoted string (under some assumptions, notably a lack backslashed double quote characters inside the double quotes), use this:
if {[regexp {"([^""]*)"} $data -> substring]} {
# We found one, it's now in $substring
}
(Doubling up the quote in the brackets is totally unnecessary — only one is needed — but it does mean that the highlighter does the right thing here.)
The simplest method of finding all the quoted strings is this:
foreach {- substring} [regexp -inline -all {"([^""]*)"} $data] {
# One of the substrings is $substring at this point
}
Notice that I'm using the same regular expression in each case. Indeed, it's actually good practice to factor such REs (especially if repeatedly used) into a variable of their own so that you can “name” them.
Combining all that stuff above:
set FindQuoted {"([^""]*)"}
set fd [open $filename]
foreach {- substring} [regexp -inline -all $FindQuoted [read $fd]] {
puts "I have found $substring for you"
}
close $fd
Internal Matching
If you're just looking for a regular expression, then you can use TCL's capture groups. For example:
set string {variable "xxx"}
regexp {"(.*)"} $string match group1
puts $group1
This will return xxx, discarding the quotes.
External Matching
If you want to match data in a file without having to handling reading the file into TCL directly, you can do that too. For example:
set match [exec sed {s/^variable "\(...\)"/\1/} /tmp/foo]
This will call sed to find just the parts of the match you want, and assign them to a TCL variable for further process. In this example, the match variable is set to xxx as above, but is operating on an external file rather than a stored string.
When you just want to find with grep all words in quotes in a file and do something with the words, you do something like this (in a shell):
grep -o '"[^"]*"' | while read word
do
# do something with $word
echo extracted: $word
done

Tcl: Removing the pound sign commented line

Why can't I remove the pound sign commented line?
#!/usr/bin/tclsh
set lines [list file1.bmp { # file2.bmp} file3.bmp ]
# Now we apply the substitution to get a subst-string that
# will perform the computational parts of the conversion.
set out [regsub -all -line {^\s*#.*$} $lines {}]
puts $out
Output:
file1.bmp { # file2.bmp} file3.bmp
-UPDATE-
Expected output:
file1.bmp {} file3.bmp
{} means empty string.
In fact, it's my first step. My ultimate goal is to eliminating all commented line and all empty lines. The above question only changes all comment lines into empty lines. For example, if the input is:
set lines [list file1.bmp { # file2.bmp} {} file3.bmp ]
I want my ultimate results to be
file1.bmp file3.bmp
Note: Stackoverflow mistakenly dim everything from and after the pound (#) sign, thinking that those are comments. Yet in TCL syntax, it should not be comments.
#Tensibai:
I also want to remove empty lines, thus I match any number of spaces before '#'. (since after removing all following '#' included, it's an empty line). In fact, in my data, the comment always appears as a full line by itself. Yet the '#' sign may not appear at the 1st character => the spaces can leads a comment line.
Edit to answer after edit:
#!/usr/bin/tclsh
set lines [list file1.bmp { # file2.bmp } file3.bmp #test ]
puts $lines
# Now we apply the substitution to get a subst-string that
# will perform the computational parts of the conversion.
set out [lsearch -regexp -all -inline -not $lines {^\s*(#.*)?$}]
puts $out
Output:
file1.bmp file3.bmp
You're working on a list, the representation of a list is a simple text so you can regsub it, but it's a single line.
If you want to check elements on this list you have to use list related commands.
Here lsearch will do what you wich, checking each item to see if they match the regex, the -not tells to return the elements no matching with -all -inline
Old answer:
Why: because your regex match any pound preceded only by 0 or unlimited number of spaces. Thus it will only match comment lines and not inline comments.
Have a look to http://regex101.com to test regexes.
A working regex would be:
#!/usr/bin/tclsh
set lines [list file1.bmp { # file2.bmp} file3.bmp ]
# Now we apply the substitution to get a subst-string that
# will perform the computational parts of the conversion.
set out [regsub -all -line {^(.*?)#.*$} $lines {\1}]
puts $out
For the regex (complete details here):
^ Matches start of line
(.*?)# Matches and capture as limited number of chars as possible before the # (non greedy operator ? to limit the match)
.*$ matches any numbe of chars until end of line
And we replace with \1 which is the first capture group (and the only one in this case).
Output:
file1.bmp {
This will also remove full line comments but may leave spaces or tabs if there's some before the pound sign and so leave blank lines.