i'm working on a script in perl.
This script read a DB and generate config file for other devices.
I have a problem with "0".
From my database, i get a 0 (int) and i want this 0 become a "0" in the config file. When i get any other value (1,2,3, etc), the script generate ("1","2","3", etc). But the 0 become an empty string "".
I know, for perl:
- undef
- 0
- ""
- "0"
are false.
How can i convert a 0 to "0" ? I try qw,qq,sprintf, $x = $x || 0, and many many more solutions.
I juste want to make a explicit conversion instead of an implicite conversion.
Thank you for your help.
If you think you have zero, but the program thinks you have an empty string, you are probably dealing with a dualvar. A dualvar is a scalar that contains both a string and a number. Perl usually returns a dualvar when it needs to return false.
For example,
$ perl -we'my $x = 0; my $y = $x + 1; CORE::say "x=$x"'
x=0
$ perl -we'my $x = ""; my $y = $x + 1; CORE::say "x=$x"'
Argument "" isn't numeric in addition (+) at -e line 1.
x=
$ perl -we'my $x = !1; my $y = $x + 1; CORE::say "x=$x"'
x=
As you can see, the value returned by !1 acts as zero when used as a number, and acts as an empty string when used as a string.
To convert this dualvar into a number (leaving other numbers unchanged), you can use the following:
$x ||= 0;
Related
I am developing a script that convert collected HEX ( I don't know the Bit format of them) values to Decimal Values.
One of the example is the hex value: fef306da
If I convert it, I receive 4277339866.
Website where I found the expected value (Decimal from signed 2's complement:):
https://www.rapidtables.com/convert/number/hex-to-decimal.html
Do you guys have a solution how can I convert hex fef306da to decimal -17627430.
Note: I get wrong value conversion when I convert hex that have (-)negative sign when decimal.
Thanks all!
Look at pack and use modifiers for unsigned and signed values.
my $hex_value = "fef306da";
my $output_num = unpack('l', pack('L', hex($hex_value)));
print $output_num; ## -17627430
Perform a test on each hex value to determine if it is a 16bit or 32bit value.
Then use the correct modifier with pack for long or short values.
it seems that you expect your decimal to be 32bit signed integer, but HEX($n) returns a 64bit one
so you may try repack it
perl -e 'print unpack "l", pack "L", hex( "fef306da" )'
If you interested in binary conversion then check following code (fef306da is 32bit number)
use strict;
use warnings;
use feature 'say';
my $input = 'fef306da';
my $hex = hex($input);
my $dec;
if( $hex & 0x80000000 ) {
$dec = -1 * ((~$hex & 0x7fffffff)+1);
} else {
$dec = $data;
}
say $dec;
Output
-17627430
Tip: Two's complement
You could use pack
my $hex = "fef306da";
my $num = hex($hex);
$num = unpack("l", pack("L", $num));
say $num; # -17627430
or
my $hex = "fef306da";
$hex = substr("00000000$hex", -8); # Pad to 8 chars
my $num = unpack("l>", pack("H8", $hex));
say $num; # -17627430
But simple arithmetic will do.
my $hex = "fef306da";
my $num = hex($hex);
$num -= 0x1_0000_0000 if $num >= 0x8000_0000;
say $num; # -17627430
I am trying to create a string from a variable in PowerShell. I want to display numbers as strings and not as integers.
I have tried putting [str] before my variable, like for string to integer ([int]), but that gave me an error. I also searched my issue but no one has asked a question like this.
The code in question is the "$jstr" line.
for ($j = 0; $j -lt 1000; $j++)
{
$jstr = [str]$j
$dot = "."
$num = $jstr + $dot
Write-Host $num, Get-Random -SetSeed $j
}
I want the output to be something like "1. Random number", with 1 being the seed number and Random number being the number mapped to that seed,
A couple ways...
If you have a number expressed as a string, you can call string's toInt32() method:
You can assign it to a variable with the type identifier:
However, this ONLY works if the string is ALL numbers (no non-numeric/non-hex characters).
If you want to go the other way (express an integer as a string), use the toString() method:
$integer.toString()
Like this?
for ($j = 0; $j -lt 6; $j++)
{
$rand = Get-Random -SetSeed $j
"$j.$rand"
}
0.1866861594
1.42389573
2.365335408
3.688215708
4.1011161543
5.1334173171
here's one way to get a list with the seed on the left, a dot, and then the random number. it uses the -f string format operator to place the numbers and to align the numbers in the allotted space. [grin]
$Start = 0
$End = 10
$SLength = ([string]$Count).Length
$RMin = 0
$RMax = 1e3
$RLength = ([string]$RMax).Length
foreach ($Number in $Start..$End)
{
"{0,$SLength}. {1,$RLength}" -f $Number, (Get-Random -SetSeed $Number -InputObject ($RMin..$RMax))
}
output ...
0. 607
1. 602
2. 997
3. 636
4. 143
5. 839
6. 6
7. 404
8. 536
9. 394
10. 124
With the aid of this question, I can find out if a string holds a specific character. I want to be able to find out where the character actually is. For example for the string banana, how would I be able to determine the letter n is the 3rd and 5th letter, or for the letter a is the 2nd,4th and 6th letter. and b is the first letter.
Q: For a given string, how can I find the location of a given character in that string?
You can do it with a for loop.
char=a
string=banana
len=${#string}
for (( i=0; i < len; i++ )); do
if [[ $char == ${string:$i:1} ]]
then echo $i
fi
done
The positions printed are zero-based. You could echo $((i+1)) to get 1-based positions instead.
${string:$i:1} extracts the ith character of the string, using bash's substring operator, as explained in Shell Parameter Expansion:
${parameter:offset:length}
This is referred to as Substring Expansion. It expands to up to length characters of the value of parameter starting at the character specified by offset.
Here's a fancy way to do it:
#!/usr/bin/env bash
findChar(){
string="${1}"
char="${2}"
length=${#string}
offset=0
r=()
while true; do
string="${string#*${char}}"
length_new="${#string}"
if [[ "${length}" == "${length_new}" ]]; then
echo "${r[#]}"
return
fi
offset=($(( $offset + $length - $length_new )))
r+=("${offset}")
length="${length_new}"
done
}
findChar banana b
findChar banana a
Here's my take on this:
#!/usr/bin/env bash
[[ ${BASH_VERSINFO[0]} < 4 ]] && { echo "Requires bash 4."; exit 1; }
string="${1:-banana}"
declare -A result=()
for ((i=0; i<${#string}; i++)); do
result[${string:$i:1}]="${result[${string:$i:1}]} $i"
done
declare -p result
The idea is that we walk through the string, adding character positions to strings that are values in an array whose subscripts are the letters you're interested in. It's quick & easy, and gives you a result set you can manipulate afterwards, rather than just sending things to stdout.
My result with this is:
$ ./foo
declare -A result='([a]=" 1 3 5" [b]=" 0" [n]=" 2 4" )'
$ ./foo barber
declare -A result='([a]=" 1" [b]=" 0 3" [e]=" 4" [r]=" 2 5" )'
Results are zero-based (i.e. "b" is in position 0).
Note an interesting side-effect of this method is that every position is preceded by a space, so if you want to count the number of occurrences of a character, you can just count the spaces:
$ declare -A result
$ result[a]=" 1 3 5"
$ count="${result[a]//[0-9]/}"
$ echo "${#count}"
3
$
I don't know what you're planning to do with this data, but if you like, you could easily turn these string results into arrays of their own for easier handling within bash.
Note that associative arrays were introduced with bash version 4.
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.
I am very new to Perl, and I am trying to write a word frequency counter as a learning exercise.
However, I am not able to figure out the error in my code below, after working on it. This is my code:
$wa = "A word frequency counter.";
#wordArray = split("",$wa);
$num = length($wa);
$word = "";
$flag = 1; # 0 if previous character was an alphabet and 1 if it was a blank.
%wordCount = ("null" => 0);
if ($num == -1) {
print "There are no words.\n";
} else {
print "$length";
for $i (0 .. $num) {
if(($wordArray[$i]!=' ') && ($flag==1)) { # start of a new word.
print "here";
$word = $wordArray[$i];
$flag = 0;
} elsif ($wordArray[$i]!=' ' && $flag==0) { # continuation of a word.
$word = $word . $wordArray[$i];
} elsif ($wordArray[$i]==' '&& $flag==0) { # end of a word.
$word = $word . $wordArray[$i];
$flag = 1;
$wordCount{$word}++;
print "\nword: $word";
} elsif ($wordArray[$i]==" " && $flag==1) { # series of blanks.
# do nothing.
}
}
for $i (keys %wordCount) {
print " \nword: $i - count: $wordCount{$i} ";
}
}
It's neither printing "here", nor the words. I am not worried about optimization at this point, though any input in that direction would also be much appreciated.
This is a good example of a problem where Perl will help you work out what's wrong if you just ask it for help. Get used to always adding the lines:
use strict;
use warnings;
to the top of your Perl programs.
Fist off,
$wordArray[$i]!=' '
should be
$wordArray[$i] ne ' '
according to the Perl documentation for comparing strings and characters. Basically use numeric operators (==, >=, …) for numbers, and string operators for text (eq, ne, lt, …).
Also, you could do
#wordArray = split(" ",$wa);
instead of
#wordArray = split("",$wa);
and then #wordArray wouldn't need to do the wonky character checking and you never would have had the problem. #wordArray will be split into the words already and you'll just have to count the occurrences.
You seem to be writing C in Perl. The difference is not just one of style. By exploding a string into a an array of individual characters, you cause the memory footprint of your script to explode as well.
Also, you need to think about what constitutes a word. Below, I am not suggesting that any \w+ is a word, rather pointing out the difference between \S+ and \w+.
#!/usr/bin/env perl
use strict; use warnings;
use YAML;
my $src = '$wa = "A word frequency counter.";';
print Dump count_words(\$src, 'w');
print Dump count_words(\$src, 'S');
sub count_words {
my $src = shift;
my $class = sprintf '\%s+', shift;
my %counts;
while ($$src =~ /(?<sequence> $class)/gx) {
$counts{ $+{sequence} } += 1;
}
return \%counts;
}
Output:
---
A: 1
counter: 1
frequency: 1
wa: 1
word: 1
---
'"A': 1
$wa: 1
=: 1
counter.";: 1
frequency: 1
word: 1