tcl check key value format - regex

I would like to check if the line is in key-value format, so I do:
set index [string first "=" $line]
if { $index == -1 } {
#error
}
set text [string range $line [expr $index + 1] end]
if { [string first "=" $text ] != -1 } {
#error
}
How can I write this check as a regexp?

You could also split the string using = as a separator and check the number of resulting fields
set fields [split $line =]
switch [llength $fields] {
1 {error "no = sign"}
2 {lassign $fields key value}
default {error "too many = signs"}
}

Your code is a bit confusing for the last if statement.
Through regex, you can use:
% regexp {=(.*)$} $line - text
1 # If there's no "=", it will be zero and nothing will be stored in $text,
# as $text will not exist
In an if block, you can use:
if {[regexp {=(.*)$} $line - text]} {
puts $text
} else {
# error
}
EDIT: To check if the string contains only one = sign:
if {[regexp {^[^=]*=[^=]*$} $line]} {
return 1
} else {
return 0
}
^ means the beginning of a string.
[^=] means any character except the equal sign.
[^=]* means any character except the equal sign occurring 0 or more times.
= matches only one equal sign.
$ matches the end of the string.
So, it checks whether the string has only one equal sign.
1 means that the line contains only 1 equal sign, 0 means there are no equal sign, or more than 1 equal sign.

Related

Validate password using regex

I am writing a regex to validate the password.
Below are the password policies that i want to cover :
Password can only contain numbers,letters and special character .
Minimum length of the password is 10 and maximum length of the password is 32.
Same character should not appear consecutively 10 or more times.
First character can not be special character.
At least 2 character classes are required.(letters, numbers or special characters)
Special characters allowed - !#+,-./:=#_
Regex that will satisfy first 4 conditions except 5th point :
^(?!.*(.)\1{7})[A-Za-z0-9][\w!#+,./:=#-]{7,23}
How i can validate all the policies together in java ?
The best way to do this is not to use a regex.
A subroutine with separate conditions is much easier to read and maintain:
sub is_password_valid {
my ($pw) = #_;
$pw =~ m{[^a-zA-Z0-9!\#+,\-./:=\#_]}
and return 0;
length($pw) >= 10 && length($pw) <= 32
or return 0;
$pw =~ /(.)\1{9}/s
and return 0;
$pw =~ /^[a-zA-Z0-9]/
or return 0;
($pw =~ /[a-zA-Z]/ + $pw =~ /[0-9]/ + $pw =~ /[^a-zA-Z0-9]/) >= 2
or return 0;
return 1;
}
Or alternatively, since this is basically just one big condition:
sub is_password_valid {
my ($pw) = #_;
return
$pw !~ m{[^a-zA-Z0-9!\#+,\-./:=\#_]} &&
length($pw) >= 10 &&
length($pw) <= 32 &&
$pw !~ /(.)\1{9}/s &&
$pw =~ /^[a-zA-Z0-9]/ &&
($pw =~ /[a-zA-Z]/ + $pw =~ /[0-9]/ + $pw =~ /[^a-zA-Z0-9]/) >= 2
;
}
If this isn't a toy validator for a homework exercise, you should change your requirements. It doesn't make sense to "validate" passwords with a regex.
You should instead require a minimum length, have a much higher maximum length (maybe 255 characters or so), and not restrict the character set used.
If you want to protect against weak passwords, check against haveibeenpwned and let a password cracker (e.g. hashcat) have a go at it.
print "Enter your password please: ";
$p=<STDIN>;chomp $p;
if ( $p =~ /(?!^[\s!#+,-./:=#_])(?=^[\w!#+,-./:=#]{10,32}$)(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!#+,-./:=#_])(?!.*(.)\1{9,}).{10,32}/ ) {print "Welcome"; f=1}
Instead of creating a sub returning true for valid password, an opposite sub can instead return zero or more error messages.
The advantage is of course that the error messages could be presented to the user to pinpoint exactly which rules are broken if any.
sub pwerr {
local $_=pop;
my $s='!#+,-./:=#_'; #allowed special chars
grep $_,
/^[a-z\d$s]+$/i ? 0 : "Password must be just nums, letters and special chars $s",
length()>=10 ? 0 : "Minimum length of the password is 10",
length()<=32 ? 0 : "Maximum length of the password is 32",
!/(.)\1{9}/ ? 0 : "Same char 10 or more in a row",
/^[a-zA-Z0-9]/ ? 0 : "First character can not be special character",
1</[a-z]/i+/\d/+/[$s]/ ? 0 : "At least 2 char classes of letters, numbers or special $s";
}
use strict; use warnings; use Test::More tests => 7;
sub _test_string { join("+",map{/^(\S+)/;$1}pwerr(shift()))||undef }
is(_test_string($$_[0]), $$_[1]) for map[split],grep/\w/,split/\n/,q(
1A!~ Password+Minimum
abc Minimum+At
abcd12345-
abcd12345.
-abcd12345 First
abcd4444444444 Same
abcd12345.abcd12345.abcd12345.xyz Maximum
);

Matching a file line by line

$text_file = '/homedir/report';
open ( $DATA,$text_file ) || die "Error!"; #open the file
#ICC2_array = <$DATA>;
$total_line = scalar#ICC2_array; # total number of lines
#address_array = split('\n',$address[6608]); # The first content is what I want and it is correct, I have checked using print
LABEL2:
for ( $count=0; $count < $total_line; $count++ ) {
if ( grep { $_ eq "$address_array[0]" } $ICC2_array[$count] ) {
print "This address is found!\n";
last LABEL2;
}
elsif ( $count == $total_line - 1 ) { # if not found in all lines
print "No matching is found for this address\n";
last LABEL2;
}
}
I am trying to match the 6609th address in #ICC2_array line by line. I am certain that this address is in $text_file but it is exactly the same format.
Something like this:
$address[6608] contains
Startpoint: port/start/input_output (triggered by clock3)
Endpoint: port/end/input_output (rising edge-triggered)
$address_array[0] contains
Startpoint: port/start/input_output (triggered by clock3)
There's a line in $text_file that is
Startpoint: port/start/input_output (triggered by clock3)
However the output is "no matching found for this address", can anybody point out my mistakes?
All of the elements in #ICC2_array will have new-line characters at the end.
As $address_array[0] is created by splitting data on \n it is guaranteed not to contain a new-line character.
A string that ends in a new-line can never be equal to a string that doesn't contain a new-line.
I suggest replacing:
#ICC2_array = <$DATA>;
With:
chomp(#ICC2_array = <$DATA>);
Update: Another problem I've just spotted. You are incrementing $count twice on each iteration. You increment it in the loop control code ($count++) and you're also incrementing it in the else clause ($count += 1). So you're probably only checking every other element in #ICC2_array.
I think your code should look like this
The any operator from core module List::Util is like grep except that it stops searching as soon as it finds a match, so on average should be twice as fast. Early iterations of List::Util did not contain any, and you can simply use grep instead if that applies to you
I've removed the _array from your array identifier as the # indicates that it's an array and it's just unwanted noise
use List::Util 'any';
my $text_file = '/homedir/report';
my #ICC2 = do {
open my $fh,'<', $text_file or die qq{Unable to open "$text_file" for input: $!};
<$fh>;
};
chomp #ICC2;
my ( $address ) = split /\n/, $address[6608], 2;
if ( any { $_ eq $address } #ICC2 ) {
print "This address is found\n"
}
else {
print "No match is found for this address\n";
}

Parsing Values in columns using TCL

I have an output which looks like below
A B C
0 1 2
I have lot of coloumns due to which the o/p looks to have wrapped around. I am wondering a way to get the respective value if I parse the keys (A B or C)
Considering each key (i.e. alphabet) will have one value (i.e. numeral), then we can use the following way. (This might be a workaround to get what we need)
set input "A B C
0 1 2"
set alpha [ regexp -all -inline {[a-zA-Z]} $input]; #Match all alphabets
set numeric [ regexp -all -inline {\d} $input]; #Match all numeric values
#Using 'foreach' to loop both list at a same time.
foreach a $alpha n $numeric {
puts "$a : $n"
}
If the pair is not equally distributed, (i.e either alphabet or numeric value is missing) then they will be assigned with empty string during the course of foreach loop execution.
If you want to get them in Key-Value pair then, we can make use of dict or array in tcl.
Dictionary Implementation
foreach a $alpha n $numeric {
dict append test $a $n
}
puts "Whole dictionary : [ dict get $test ]"
puts "Value of key A : [ dict get $test A ]"; #Getting value of key 'A'
dict for { a n } $test {
puts "Key : $a ==> Value : $n "
}
Array Implementation
foreach a $alpha n $numeric {
set result($a) $n
}
puts "Whole array : [ array get result ]"
puts "Value of key A : $result(A) "#Getting value of key 'A'
foreach index [array names result] {
puts "Key : $index ==> Value : $result($index)"
}
Reference : dict, array

How do I get the text that present under the matched string in tcl

I have a string value in tcl as
set out " ABC CDE EFG
123 456"
I want to get the text that is present below text "EFG".
As right now it is "456", but it can be anything so I need a way though which I can grep for "EFG" and get the text below it.
This answer takes some inspiration from Johannes Kuhn's answer, but I use regexp to get the word indices from the "keys" line.
# this is as close as I can get to a here-doc in Tcl
set out [string trim {
ABC DEF GHI
123 456
}]
# map the words in the first line to the values in the 2nd line
lassign [split $out \n] keys values
foreach range [regexp -all -inline -indices {\S+} $keys] {
set data([string range $keys {*}$range]) [string range $values {*}$range]
}
parray data
outputs
data(ABC) = 123
data(DEF) =
data(GHI) = 456
I Suggest splitting the string into the keys and values with
lassign [split $out \n] keys values
and then look for the string position in the keys and get the same range in the values
set start [string first "EFG" $keys]
set value [string range $values $start [expr {${start}+[string length "EFG"]-1}]]
wraping it in a proc and we get
proc getValue {input lookFor} {
lassign [split $input \n] keys values
set start [string first $lookfor $keys]
set value [string range $values $start \
[expr {${start}+[string length $lookfor]-1}]]
}
invoke it like that:
getValue $out "EFG"
Edit: how is the 2nd line aligned? With a tabulator (\t), spaces?
In this case what you actually have is two lines with groups of 3 alphanumeric characters separated by spaces with a large amount of leading whitespace prefixing the second line ("\x20ABC\x20CDE\x20EFG\n[string repeat \x20 10]123[string repeat \x20 5]456" will reproduce what you posted). In your example [string range end-2 end] would give you what you need. I'd suggest reading the file line by line and each time you see the EFG, on the next line extract the part you need (maybe using string range) and emit it.
For example (untested):
set state 0
set f [open $inputfile r]
while {[gets $f line] != -1} {
if {$state} {
puts [string range $line end-2 end]
set state 0
} else {
if {[string match "*EFG" $line]} { set state 1 }
}
}
close $f

perl match multiple numbers from a variable similar to egrep

Want to match the number exactly from the variable which has multiple numbers seperated by pipe symbol similar to egrep.below is the code which i tried.
#!/usr/bin/perl
my $searchnum = $ARGV[0];
my $num = "148|1|0|256";
print $num;
if ($searchnum =~ /$num/)
{
print "found";
}
else
{
print "not-found";
}
Expected o/p
perl number_match.pl 1
found
perl number_match.pl 1432
not-found
The regex /148|1|0|256/ matches if the string that this regex is bound to contains a substring that is either 148, 1, 0 or 256. This means that the option 148 is superfluous, as this matches a subset of strings that match 1.
You probably want to test if the given string is equal to one of these options. If you want to use regexes, you have to anchor the regex at the start and the end of the string:
/^ (?:148|1|0|256) $/x
You could also use the grep builtin:
my $number = ...;
if (grep {$number eq $_} qw/148 1 0 256/) { say "found" }
else { say "not-found" }
The grep function takes a block that has to return a boolean value. It returns all elements from the list on the right where the condition returns true. If at least one element matches, then the whole expression evaluates to true.
You could also use a hash that contains all possible options:
my $number = ...;
my %options = map { $_ => undef } qw/148 1 0 256/;
if ( exists $options{$number} ) { say "found" }
else { say "not-found" }
This is more efficient than grep.
Use:
my $num = '^(148|1|0|256)$';
Here is a oneliner:
perl -e "$_=$ARGV[0]; exit if !/^\d+$/; print \"not-\" unless /^(14|156|0|89)$/;print \"found\n\";"