I am parsing a tcl file line line by line and searching for lines with open curly braces so that I can merge them with the next line and read them.
I am struggling to get a single regex to do this. My concern is lines with with a closing } which can be skipped.
Example:
MATCH: test_command -switch1 {
NO MATCH: single_command
NO MATCH: test_tcl -switch2 {arg1 }
Please help with the regex to get the result. I tried this:
% set a "test_command -swithc1 {bye }"
test_command -swithc1 {bye }
% regexp "{" $a match
1
#0 is expected
This is not my intention. I want match only for lines with open curly brace
% set b "test_command -swithc1 {hi"
test_command -swithc1 {hi
% regexp "{" $a match
1
#1 was expected
I'm looking for a regex that will give 0 for the $a and 1 for $b
You really shouldn't be using a regular expression for that; there's a Tcl command specifically for this sort of thing: info complete. Here's how to use it:
set accumulator ""
while {![eof $inputChannel]} {
# Note well: you *must* add the newline
append accumulator [gets $inputChannel] "\n"
if {[info complete $accumulator]} {
handleCompleteChunk $accumulator
set accumulator ""
}
}
This handles various types of bracket matching and the intricacies of backslash sequences, but just to check whether the “line” is complete. (It's also the core of how Tcl's REPL works, except that uses the Tcl C API equivalents.)
You could try a couple "lookarounds", one to say "I see a left bracket" and one to say "I don't see a right bracket":
(?!.*\})(?=.*\{)
https://regex101.com/r/p8bbsF/1/
Related
(You'd think this would be easy, but I'm stumped.)
I'm converting an iOS note to a text file, and the note contains "0." and "?" whenever there is a list or bullet.
This was a bulleted list
? item 20
? Item 21
? Item 22
I'm having so much problem replacing the "?"
I don't want to replace a legitimate question mark at the end of a sentence,
but I want to replace the "?" bullets with "-" (preferably anywhere in the line, not just at the beginning)
I tried these searches - no luck
set line "? item 20"
set index_bullet [string first "(\s|\r|\n)(\?)" $line]
set index_bullet [string first "(!\w)(\?)" $line]
set index_bullet [string first ^\? $line]
This works, but it would match any question mark
set index_bullet [string first \? $line]
Does anyone know what I'm doing wrong?
How do I find and replace only question mark bullets with a "-"?
Thank you very much in advance
If you're really wanting to replace a question mark where you've got a regular expression that describes the rule, the regsub command is the right way. (The string first command finds literal substrings only. The string match command uses globbing rules.) In this case, we'll use the -all option so that every instance is replaced:
set line "? item 20"
set replaced [regsub -all {(\s|^)\?(\s)} $line {\1-\2}]
puts "'$line' --> '$replaced'"
# Prints: '? item 20' --> '- item 20'
The main tricks to using regular expressions in Tcl are, as much as possible, to keep REs and their replacements in braces so that the you can use Tcl metacharacters (e.g., backslash or square brackets) without having to fiddle around a lot.
Also, \s by default will match a newline.
It seems likely that a character used to indicate a list item is the first character on the line or the first character after optional whitespace. To match a question mark at the beginning of a line:
string match {\?*} $line
or
string match \\?* $line
The braces or doubled backslash keeps the question mark from being treated as a string match metacharacter.
To find a question mark after optional whitespace:
string match {\?*} [string trimleft $line]
The command returns 1 if it finds a match, and 0 if it doesn't.
To do this with string first, use
if {[string first ? [string trimleft $line]] eq 0} ...
but in that case, keep in mind that the index returned from string first isn't the true location of the question mark. (Use
== instead of eq if you have an older Tcl).
When you have determined that the line contains a question mark in the first non-whitespace position, a simple
set line [regsub {\?} $line -]
will perform a single substitution regardless of where it is.
Documentation:
regsub,
string,
Syntax of Tcl regular expressions
I figured it out.
I did it in two steps:
1) First find the "?"
set index_bullet [string first "\?" $line]
2) Then filter out "?" that is not a bullet
set index_question_mark [string first "\w\?" $line]
I have a solution, but please post if you have a better way of doing this.
Thanks!
my $book = Spreadsheet::Read->new();
my $book = ReadData
('D:\Profiles\jmahroof\Desktop\Scheduled_Build_Overview.xls');
my $cell = "CD7";
my $n = "1";
my $send = $book->[$n]{$cell};
The above code gets data from a spreadsheet, then prints the content of a cell that I know has text in. It has text of exactly the following format: text(text)
I need to replace the open bracket with a empty space and I need to remove the close bracket. I have tried the below code to substitute the open bracket for an empty space however it does not seem to work.
$send =~ s/(/ /g;
print $send;
The bracket is seen as part of the code, just escape it.
$send =~ s/\(/ /;
print $send;
Since you only replace one char with another, you don't want a substitution, but a transliteration. That's the tr/// function in Perl. Since the pattern is just a list of chars, and not an actual regex, you don't need to escape the open parenthesis (. There is also no /g flag. It just substitutes all occurrences.
$send =~ tr/(/ /;
The main difference to a regular expression substitution is that the transliterations get compiled at compile time, not at run time. That makes the tr/// faster than a s///, especially in a loop.
See the full documentation in perlop.
I finally know how to use regular expressions to replace one substring with another every place where it occurs within a string. But what I need to do now is a bit more complicated than that.
A string I must transform will have many instances of the newline character ('\n'). If those newline character are enclosed within fish-tags (between '<' and '>') I need to replace it with a simple whitespace character (' ').
However, if a newline character occurs anywhere else in the string, I need to leave that newline character alone.
There will be several places in the string that are enclosed in fish-tags, and several places that aren't.
Is there a way to do this in PERL?
I honestly don't recommend doing this with regular expressions. Besides the fact that you should never parse html with a regular expression, it's also a pain to do negative matches with regular expressions and anyone reading the code will honestly have no idea what you just did. Doing it manually on the other hand is really easy to understand.
This code assumes well formed html that doesn't have tags starting inside the definition of other tags (otherwise you would have to track all the instances and increment/decrement a count appropriately) and it does not handle < or > inside quoted strings which isn't the most common thing. And if you're doing all that I really recommend you use a real html parser, there are many of them.
Obviously if you're not reading this from a filehandle, the loop would be going over an array of lines (or the output of splitting the whole text, though you would instead be appending ' ' or "\n" depending on the inside variable if you split since it would remove the newline)
use strict;
use warnings;
# Default to being outside a tag
my $inside = 0;
while(my $line = <DATA>) {
# Find the last < and > in the string
my ($open, $close) = map { rindex($line, $_) } qw(< >);
# Update our state accordingly.
if ($open > $close) {
$inside = 1;
} elsif ($open < $close) {
$inside = 0;
}
# If we're inside a tag change the newline (last character in the line) with a space. If you instead want to remove it you can use the built-in chomp.
if ($inside) {
# chomp($line);
substr($line, -1) = ' ';
}
print $line;
}
__DATA__
This is some text
and some more
<enclosed><a
b
c
> <d
e
f
>
<g h i
>
Given:
$ echo "$txt"
Line 1
Line 2
< fish tag line 1
and line 2 >
< line 3 >
< fish tag line 4
and line 5 >
You can do:
$ echo "$txt" | perl -0777 -lpe "s/(<[^\n>]*)\n+([^>]*>)/\1\2/g"
Line 1
Line 2
< fish tag line 1 and line 2 >
< line 3 >
< fish tag line 4 and line 5 >
I will echo that this only works in limited cases. Please do not get in the general habit of using a regex for HTML.
This solution uses zdim's data (thanks, zdim)
I prefer to use an executable replacement together with the non-destructive option of the tr/// operator
This solution finds all occurrences of strings enclosed in angle brackets <...> and alters all newlines within each one to single spaces
Note that it would be simple to allow for quoted substrings containing any characters by writing this instead
$data =~ s{ ( < (?: "[^"]+" | [^>] )+ > ) }{ $1 =~ tr/\n/ /r }gex;
use strict;
use warnings 'all';
use v5.14; # For /r option
my $data = do {
local $/;
<DATA>;
};
$data =~ s{ ( < [^<>]+ > ) }{ $1 =~ tr/\n/ /r }gex;
print $data;
__DATA__
start < inside tags> no new line
again <inside, with one nl
> out
more <inside, with two NLs
and more text
>
output
start < inside tags> no new line
again <inside, with one nl > out
more <inside, with two NLs and more text >
The (X)HTML/XML shouldn't be parsed with regex. But since no description of the problem is given here is a way to go at it. Hopefully it demonstrates how tricky and involved this can get.
You can match a newline itself. Together with details of how linefeeds may come in text
use warnings;
use strict;
my $text = do { # read all text into one string
local $/;
<DATA>;
};
1 while $text =~ s/< ([^>]*) \n ([^>]*) >/<$1 $2>/gx;
print $text;
__DATA__
start < inside tags> no new line
again <inside, with one nl
> out
more <inside, with two NLs
and more text
>
This prints
start < inside tags> no new line
again <inside, with one nl > out
more <inside, with two NLs and more text >
The negated character class [^>] matches anything other than >, optionally and any number of times with *, up to an \n. Then another such pattern follows \n, up to the closing >. The /x modifier allows spaces inside, for readability. We also need to consider two particular cases.
There may be multiple \n inside <...>, for which the while loop is a clean solution.
There may be multiple <...> with \n, which is what /g is for.
The 1 while ... idiom is another way to write while (...) { }, where the body of the loop is empty so everything happens in the condition, which is repeatedly evaluated until false. In our case the substitution keeps being done in the condition until there is no match, when the loop exits.
Thanks to ysth for bringing up these points and for the 1 while ... solution.
All of this necessary care for various details and edge cases (of which there may be more) hopefully convinces you that it is better to reach for an HTML parsing module suitable for the particular task. For this we'd need to know more about the problem.
I'm kinda a novice on Expect, but I can't get over a problem I have with a logging-monitoring script i'm writing.
I've spent hours googling on why I can't get this to work:
puts $redirect [concat "${time}\t" "${context}\t" "$id\t" "${eventtype}" "${eventstatus}\t" "${eventcontext}" ]
The \t char ( it does not work even with other \chars ) is not showing up. No matter how and where I place it, I've tried different stuff:
puts $redirect [concat "${time}" "\t" "${context}" [...] ]
puts $redirect [concat "${time}\t" "${context}" [...] ]
puts $redirect [concat "${time}" "\t${context}" [...] ]
puts $redirect [concat "${time}" \t "${context}" [...] ]
*where redirect is set redirect [open $logfile a]
*where [...] are other strings I'm concatenating, in the same way.
From http://tcl.tk/man/tcl8.5/TclCmd/Tcl.htm#M10
[5] Argument expansion.
If a word starts with the string “{}” followed by a non-whitespace character, then the leading “{}” is removed and the
rest of the word is parsed and substituted as any other word. After
substitution, the word is parsed as a list (without command or
variable substitutions; backslash substitutions are performed as is
normal for a list and individual internal words may be surrounded by
either braces or double-quote characters), and its words are added to
the command being substituted. For instance, “cmd a {}{b [c]} d
{}{$e f "g h"}” is equivalent to “cmd a b {[c]} d {$e} f "g h"”.
[6] Braces.
If the first character of a word is an open brace (“{”) and rule [5] does not apply, then the word is terminated by the matching close
brace (“}”). Braces nest within the word: for each additional open
brace there must be an additional close brace (however, if an open
brace or close brace within the word is quoted with a backslash then
it is not counted in locating the matching close brace). No
substitutions are performed on the characters between the braces
except for backslash-newline substitutions described below, nor do
semi-colons, newlines, close brackets, or white space receive any
special interpretation. The word will consist of exactly the
characters between the outer braces, not including the braces
themselves.
Ironically, I can get this to work:
puts $redirect [concat "${time}\n" "-\t${context}" [...] ]
If I put a char before the TAB, it works, but I can't use it.
Ex output: 2016-06-01 15:43:12 - macro
Wanted output: 2016-06-01 15:43:12 macro
I've tried on building the string with append but it's like it is eating pieces of string due to max buffer char, is it possible?
Am I missing something?
Thanks in advice.
That is what concat does. It eats whitespace.
From the documentation for concat:
This command joins each of its arguments together with spaces after trimming leading and trailing white-space from each of them. If all the arguments are lists, this has the same effect as concatenating them into a single list. It permits any number of arguments; if no args are supplied, the result is an empty string.
#Etan gave you why it's not working for you.
An alternate way to code that is to use format
puts $redirect [format "%s\t%s\t%s\t%s%s\t%s" $time $context $id $eventtype $eventstatus $eventcontext]
I have a variable from which I have to grep the which in middle of %% adn the word which starts with $$. I used split it works... but for only some scenarios.
Example:
#!/usr/bin/perl
my $lastline ="%Filters_LN_RESS_DIR%\ARC\Options\Pega\CHF_Vega\$$(1212_GV_DATE_LDN)";
my #lastline_temp = split(/%/,$lastline);
print #lastline_temp;
my #var=split("\\$\\$",$lastline_temp[2]);
print #var;
I get the o/p as expected. But can i get the same using Grep command. I mean I dont want to use the array[2] or array[1]. So that I can replace the values easily.
I don't really see how you can get the output you expect. Because you put your data in "busy" quotes (interpolating, double, ...), it comes out being stored as:
'%Filters_LN_RESS_DIR%ARCOptionsPegaCHF_Vega$01212_GV_DATE_LDN)'
See Quote and Quote-like Operators and perhaps read Interpolation in Perl
Notice that the backslashes are gone. A backslash in interpolating quotes simply means "treat the next character as literal", so you get literal 'A', literal 'O', literal 'P', ....
That '0' is the value of $( (aka $REAL_GROUP_ID) which you unwittingly asked it to interpolate. So there is no sequence '$$' to split on.
Can you get the same using a grep command? It depends on what "the same" is. You save the results in arrays, the purpose of grep is to exclude things from the arrays. You will neither have the arrays, nor the output of the arrays if you use a non-trivial grep: grep {; 1 } #data.
Actually you can get the exact same result with this regular expression, assuming that the single string in #vars is the "result".
m/%([^%]*)$/
Of course, that's no more than
substr( $lastline, rindex( $lastline, '%' ) + 1 );
which can run 8-10 times faster.
First, be very careful in your use of quotes, I'm not sure if you don't mean
'%Filters_LN_RESS_DIR%\ARC\Options\Pega\CHF_Vega\$$(1212_GV_DATE_LDN)'
instead of
"%Filters_LN_RESS_DIR%\ARC\Options\Pega\CHF_Vega\$$(1212_GV_DATE_LDN)"
which might be a different string. For example, if evaluated, "$$" means the variable $PROCESS_ID.
After trying to solve riddles (not sure about that), and quoting your string
my $lastline =
'%Filters_LN_RESS_DIR%\ARC\Options\Pega\CHF_Vega\$$(1212_GV_DATE_LDN)'
differently, I'd use:
my ($w1, $w2) = $lastline =~ m{ % # the % char at the start
([^%]+) # CAPTURE everything until next %
[^(]+ # scan to the first brace
\( # hit the brace
([^)]+) # CAPTURE everything up to closing brace
}x;
print "$w1\n$w2";
to extract your words. Result:
Filters_LN_RESS_DIR
1212_GV_DATE_LDN
But what do you mean by replace the values easily. Which values?
Addendum
Now lets extract the "words" delimited by '\'. Using a simple split:
my #words = split /\\/, # use substr to start split after the first '\\'
substr $lastline, index($lastline,'\\');
you'll get the words between the backslashes if you drop the last entry (which is the $$(..) string):
pop #words; # remove the last element '$$(..)'
print join "\n", #words; # print the other elements
Result:
ARC
Options
Pega
CHF_Vega
Does this work better with grep? Seems to:
my #words = grep /^[^\$%]+$/, split /\\/, $lastline;
and
print join "\n", #words;
also results in:
ARC
Options
Pega
CHF_Vega
Maybe that is what you are after? What do you want to do with these?
Regards
rbo