I want to compare two strings in TCL and replace the unmatched character with asterisk.
Core_cpuss_5/loop2/hex_reg[89]cpu_ip[45]_reg10/D[23]
Core_cpuss_5/loop2/hex_reg[56]cpu_ip[12]_reg5/D[33]
Output Required : Core_cpuss_5/loop2/hex_reg[ * ]cpu_ip[ * ]_reg*/D[*]
I tried above using regsub but not working as expected.
foreach v {string1 string2} {
regsub {\[[0-9]+\]$} $v {[*]} v_modified
}
To replace the integers inside the square brackets with * (and also change reg10 and reg5 to reg*)
set string1 {Core_cpuss_5/loop2/hex_reg[89]cpu_ip[45]_reg10/D[23]}
set string2 {Core_cpuss_5/loop2/hex_reg[56]cpu_ip[12]_reg5/D[33]}
foreach v "$string1 $string2" {
regsub -all {\[\d+\]} $v {[*]} v_modified
regsub -all {reg\d+} $v_modified {reg*} v_modified
puts $v_modified
}
You had a couple problems in your code which I fixed:
Change {string1 string2} to "$string1 $string2"
Add -all to the regexp command find all matches.
Remove the $ from the regular expression because that only matches the final one.
Add another regsub to change reg10 and reg5 to reg*.
If you need a more general purpose solution, this will find a sequence of integers in each string and replace with a * if they are different:
set string1 {Core_cpuss_5/loop2/hex_reg[89]cpu_ip[45]_reg10/D[23]}
set string2 {Core_cpuss_5/loop2/hex_reg[56]cpu_ip[12]_reg5/D[33]}
# Initialize start positions for regexp for each string.
set start1 0
set start2 0
# Incrementally search for integers in each string.
while {1} {
# Find a match for an integer in each string and save the {begin end} indices of the match
set matched1 [regexp -start $start1 -indices {\d+} $string1 indices1]
set matched2 [regexp -start $start2 -indices {\d+} $string2 indices2]
if {$matched1 && $matched2} {
# Use the indices to get the matched integer
set value1 [string range $string1 {*}$indices1]
set value2 [string range $string2 {*}$indices2]
# Replace the integer with *
if {$value1 ne $value2} {
set string1 [string replace $string1 {*}$indices1 "*"]
set string2 [string replace $string2 {*}$indices2 "*"]
}
} else {
break
}
# Increment the start of the next iteration.
set start1 [expr {[lindex $indices1 1]+1}]
set start2 [expr {[lindex $indices2 1]+1}]
}
puts "String1 : $string1"
puts "String2 : $string2"
The above will only work if the two strings are similar enough (like they each have the same number of integers in a similar order)
Related
I'm attempting to use Perl's gethostnamebyaddr function. The annoying thing is that it returns the entire domain name in scalar format. I want to parse out only the hostname and discard the rest.
I'm using split to divide the domain name into an array and then taking only the first value but this doesn't seem to work.
#!/usr/bin/perl
use Socket;
my $name;
my $hostname;
my #tmpStr;
$name = gethostbyaddr(inet_aton("192.168.2.3"), AF_INET);
print "$name\n";
#tmpStr = split ".", $name;
$hostname = $tmpStr[0];
print "Host name is $hostname\n";
When the above code is executed, I get the following:
dc1-ent.ent.ped.local
Host name is
According to this website the return value is not a string but is rather a scalar value and so my attempt at splitting it doesn't work.
I can't figure out how to convert it to a string before I can split it or parse out the hostname by itself.
The dot character has special meaning for regular expressions in Perl, and the 1st argument to split is a regular expression. You need to escape the dot:
use warnings;
use strict;
my $name = 'dc1-ent.ent.ped.local';
print "$name\n";
my #tmpStr = split /\./, $name;
my $hostname = $tmpStr[0];
print "Host name is $hostname\n";
This outputs:
dc1-ent.ent.ped.local
Host name is dc1-ent
I would write it like this
my $name = gethostbyaddr(inet_aton('192.168.2.3'), AF_INET);
my ($host) = $name =~ /([^.]+)/;
say $host;
Your problem is not related to gethostbyaddr() but by what follows.
Proof:
DB<1> $name = 'dc1-ent.ent.ped.local';
DB<2> #tmpStr = split ".", $name;
DB<3> print #tmpStr;
(nothing printed)
Try instead using split that way:
DB<8> $name = 'dc1-ent.ent.ped.local';
DB<9> #tmpStr = split(/\./, $name);
DB<10> print #tmpStr;
dc1-ententpedlocal
DB<11> print join(' ', #tmpStr);
dc1-ent ent ped local
DB<12> x #tmpStr;
0 'dc1-ent'
1 'ent'
2 'ped'
3 'local'
Or if you absolutely want a string and not a regex, protect the dot also as your string is still parsed as a regular expression (which is why being explicit with / / has its merits, it forces you to remember that some character have special meaning there, like the dot):
DB<1> $name = 'dc1-ent.ent.ped.local';
DB<2> #tmpStr = split('.', $name);
DB<3> print #tmpStr;
DB<4> #tmpStr = split('\.', $name);
DB<5> x #tmpStr
0 'dc1-ent'
1 'ent'
2 'ped'
3 'local'
I'm writing a Perl programm and I've got the following problem:
I have a large list of start and end positions in a string. This positions correspond to substrings in this string. I now want to transfer this positions to a second string. This second string is identical to the first string, except that it has additional hyphen.
Example for original String: "ABCDEF" and one Substring "BCDE"
What I have:
Positions of substring in this original string: Start = 1, End =
4
The original string with additional hyphen: "-AB---CD--E-F---"
What I want:
Position of the substring in the hyphen-string: Start=2, End=10
I have a large list of this substring positions.
I strongly suspect that you have shown a reduced version of the problem, in which case any solution may not work for the real situation.
However, it seems simplest to build a regex by interspersing -* (i.e. zero or more hyphens) between characters.
This program works that way, building a regex of B-*C-*D-*E and comparing it to both of your sample strings.
use strict;
use warnings;
my #strings = qw/ ABCDEF -AB---CD--E-F--- /;
my ($start, $end) = (1, 4);
my $substr = substr $strings[0], $start, $end-$start + 1;
my $regex = join '-*', split //, $substr;
$regex = qr/$regex/;
for my $string (#strings) {
if ($string =~ $regex) {
printf "Substring found at %d to %d in string %s\n", $-[0], $+[0]-1, $string;
}
}
output
Substring found at 1 to 4 in string ABCDEF
Substring found at 2 to 10 in string -AB---CD--E-F---
Does this work for you? It just searches for the characters specified by start and end in the hyphenated string and returns their indices.
sub hyphen_substrings {
my $original = shift;
my $hyphenated = shift;
my #substrings = #_;
my #return;
for my $substring (#substrings) {
my ($start, $end) = #{$substring}[0, 1];
my $start_h = index $hyphenated, substr $original, $start, 1;
my $end_h = index $hyphenated, substr $original, $end, 1;
push #return, [$start_h, $end_h];
}
return #return;
}
use strict;
use warnings;
my $theStringGivenAsAnInputExample="-AB---CD--E-F---";
my $start=1;
my $end=4;
my $theStringGivenAsAnotherInput="ABCDEF";
my $regexp=join("-*",split("",substr($theStringGivenAsAnotherInput,$start,$end))
);
$theStringGivenAsAnInputExample =~ /$regexp/p;
print ${^PREMATCH},"\n";
print ${^POSTMATCH},"\n";
print ${^MATCH},"\n";
my $startPosition = length(${^PREMATCH});
my $finishPosition = length(${^PREMATCH})+length(${^MATCH})-1;
print "start, $startPosition finish, $finishPosition\n";
I have a snippet of code that I use for a program that I have [Thus some of the app specific code] ...Anyway I am trying to capitalize the first letter of each word unless the word is in caps.
for example: >>this is text THAT would be CHANGED.
The code that i have thus far is as follows.
Again some of this is app specific I am not able to use "puts," the result has to be returned as return "" this is the reason that I create a var and add to it word by word.
proc ToTitle {} {
set Input [sh_set clipboard]
set CleanedInput [string map {" " |} [string trimright [string trimleft $Input]]]
set InputList [split $CleanedInput "|"]
set wresult ""
set item 0
foreach line $InputList {
set List_Item [lindex $InputList $item];
if {[string is upper $List_Item] == 1} {
set newline $List_Item
set wresult "$wresult $newline"
incr item
} else {
set newline [string totitle $List_Item]
set wresult "$wresult $newline"
incr item
}
}
regsub -all {\u0020{2,}} $wresult " " wresult; #REMOVE ALL EXCESSIVE SPACE CHARACTERS
set $wresult [string trimright [string trimleft $wresult]]; # TRIM ALL OF THE WHITESPACE BEFORE AND AFTER THE STRING
return "$wresult"}
This is currently working the output would be:
This Is Text THAT Would Be Changed.
The issue is the "Changed." because of the "."
The question is What can I use to only read the word character on items that have special characters or word characters?
{[string is upper $List_Item] == 1}
I know there is something that I can add to that to check it...
Thankyou in advance for all the help.
I think there's a simpler solution. Try this:
set a "this is text THAT would be CHANGED."
set out ""
foreach word $a {
append out "[string toupper $word 0 0] "
}
puts $out
Running it gives this output:
% % % This Is Text THAT Would Be CHANGED.
I have a variable that is entered at a prompt:
my $name = <>;
I want to append a fixed string '_one'to this (in a separate variable).
E.g. if $name = Smith then it becomes 'Smith_one'
I have tried several various ways which do not give me the right results, such as:
my $one = "${name}_one";
^ The _one appears on the next line when I print it out and when I use it, the _one is not included at all.
Also:
my $one = $name."_one";
^ The '_one' appears at the beginning of the string.
And:
my $end = '_one';
my $one = $name.$end;
or
my $one = "$name$end";
None of these produce the result I want, so I must be missing something related to how the input is formatted from the prompt, perhaps. Ideas appreciated!
Your problem is unrelated to string appending: When you read a line (e.g. via <>), then the record input separator is included in that string; this is usually a newline \n. To remove the newline, chomp the variable:
my $name = <STDIN>; # better use explicit filehandle unless you know what you are doing
# now $name eq "Smith\n"
chomp $name;
# now $name eq "Smith"
To interpolate a variable into a string, you usually don't need the ${name} syntax you used. These lines will all append _one to your string and create a new string:
"${name}_one" # what you used
"$name\_one" # _ must be escaped, else the variable $name_one would be interpolated
$name . "_one"
sprintf "%s_one", $name
# etc.
And this will append _one to your string and still store it in $name:
$name .= "_one"
Perl usually converts numeric to string values and vice versa transparently. Yet there must be something which allows e.g. Data::Dumper to discriminate between both, as in this example:
use Data::Dumper;
print Dumper('1', 1);
# output:
$VAR1 = '1';
$VAR2 = 1;
Is there a Perl function which allows me to discriminate in a similar way whether a scalar's value is stored as number or as string?
A scalar has a number of different fields. When using Perl 5.8 or higher, Data::Dumper inspects if there's anything in the IV (integer value) field. Specifically, it uses something similar to the following:
use B qw( svref_2object SVf_IOK );
sub create_data_dumper_literal {
my ($x) = #_; # This copying is important as it "resolves" magic.
return "undef" if !defined($x);
my $sv = svref_2object(\$x);
my $iok = $sv->FLAGS & SVf_IOK;
return "$x" if $iok;
$x =~ s/(['\\])/\\$1/g;
return "'$x'";
}
Checks:
Signed integer (IV): ($sv->FLAGS & SVf_IOK) && !($sv->FLAGS & SVf_IVisUV)
Unsigned integer (IV): ($sv->FLAGS & SVf_IOK) && ($sv->FLAGS & SVf_IVisUV)
Floating-point number (NV): $sv->FLAGS & SVf_NOK
Downgraded string (PV): ($sv->FLAGS & SVf_POK) && !($sv->FLAGS & SVf_UTF8)
Upgraded string (PV): ($sv->FLAGS & SVf_POK) && ($sv->FLAGS & SVf_UTF8)
You could use similar tricks. But keep in mind,
It'll be very hard to stringify floating point numbers without loss.
You need to properly escape certain bytes (e.g. NUL) in string literals.
A scalar can have more than one value stored in it. For example, !!0 contains a string (the empty string), a floating point number (0) and a signed integer (0). As you can see, the different values aren't even always equivalent. For a more dramatic example, check out the following:
$ perl -E'open($fh, "non-existent"); say for 0+$!, "".$!;'
2
No such file or directory
It is more complicated. Perl changes the internal representation of a variable depending on the context the variable is used in:
perl -MDevel::Peek -e '
$x = 1; print Dump $x;
$x eq "a"; print Dump $x;
$x .= q(); print Dump $x;
'
SV = IV(0x794c68) at 0x794c78
REFCNT = 1
FLAGS = (IOK,pIOK)
IV = 1
SV = PVIV(0x7800b8) at 0x794c78
REFCNT = 1
FLAGS = (IOK,POK,pIOK,pPOK)
IV = 1
PV = 0x785320 "1"\0
CUR = 1
LEN = 16
SV = PVIV(0x7800b8) at 0x794c78
REFCNT = 1
FLAGS = (POK,pPOK)
IV = 1
PV = 0x785320 "1"\0
CUR = 1
LEN = 16
There's no way to find this out using pure perl. Data::Dumper uses a C library to achieve it. If forced to use Perl it doesn't discriminate strings from numbers if they look like decimal numbers.
use Data::Dumper;
$Data::Dumper::Useperl = 1;
print Dumper(['1',1])."\n";
#output
$VAR1 = [
1,
1
];
Based on your comment that this is to determine whether quoting is needed for an SQL statement, I would say that the correct solution is to use placeholders, which are described in the DBI documentation.
As a rule, you should not interpolate variables directly in your query string.
One simple solution that wasn't mentioned was Scalar::Util's looks_like_number. Scalar::Util is a core module since 5.7.3 and looks_like_number uses the perlapi to determine if the scalar is numeric.
The autobox::universal module, which comes with autobox, provides a type function which can be used for this purpose:
use autobox::universal qw(type);
say type("42"); # STRING
say type(42); # INTEGER
say type(42.0); # FLOAT
say type(undef); # UNDEF
When a variable is used as a number, that causes the variable to be presumed numeric in subsequent contexts. However, the reverse isn't exactly true, as this example shows:
use Data::Dumper;
my $foo = '1';
print Dumper $foo; #character
my $bar = $foo + 0;
print Dumper $foo; #numeric
$bar = $foo . ' ';
print Dumper $foo; #still numeric!
$foo = $foo . '';
print Dumper $foo; #character
One might expect the third operation to put $foo back in a string context (reversing $foo + 0), but it does not.
If you want to check whether something is a number, the standard way is to use a regex. What you check for varies based on what kind of number you want:
if ($foo =~ /^\d+$/) { print "positive integer" }
if ($foo =~ /^-?\d+$/) { print "integer" }
if ($foo =~ /^\d+\.\d+$/) { print "Decimal" }
And so on.
It is not generally useful to check how something is stored internally--you typically don't need to worry about this. However, if you want to duplicate what Dumper is doing here, that's no problem:
if ((Dumper $foo) =~ /'/) {print "character";}
If the output of Dumper contains a single quote, that means it is showing a variable that is represented in string form.
You might want to try Params::Util::_NUMBER:
use Params::Util qw<_NUMBER>;
unless ( _NUMBER( $scalar ) or $scalar =~ /^'.*'$/ ) {
$scalar =~ s/'/''/g;
$scalar = "'$scalar'";
}
The following function returns true (1) if the input is numeric and false ("") if it is a string. The function also returns true (-1) if the input is a numeric Inf or NaN. Similar code can be found in the JSON::PP module.
sub is_numeric {
my $value = shift;
no warnings 'numeric';
# string & "" -> ""
# number & "" -> 0 (with warning)
# nan and inf can detect as numbers, so check with * 0
return unless length((my $dummy = "") & $value);
return unless 0 + $value eq $value;
return 1 if $value * 0 == 0; # finite number
return -1; # inf or nan
}
I don't think there is perl function to find type of value. One can find type of DS(scalar,array,hash). Can use regex to find type of value.