I have a generated string like this:
$x = jhgkjh**May**-**JUN**ojklkjh-jkl
I want to substitute every occurrence in $x of a month abbreviation with the number of the month. So the result will look like this:
$x = jhgkjh**05**-**06**ojklkjh-jkl
For that I created 2 arrays:
$months = "Jan", "Feb", etc...
$Nmonths = "01", "02" , etc...
This loop doesn't work:
$i = 0
do {
$x = $x.replace('$months[$i]','$Nmonths[$i]')
$i = $i+1
}
until ("$i" -eq "12")
$x
Please help !
The sample data looks a little odd with respect to the case of the month strings. Can't really tell if it's case sensitive or not, so here's two slightly different solutions.
Starting whe a hash table of month abbreviations and numbers:
$Months=#{
Jan = '01'
Feb = '02'
Mar = '03'
Apr = '04'
May = '05'
Jun = '06'
Jul = '07'
Aug = '08'
Sep = '09'
Oct = '10'
Nov = '11'
Dec = '12'
}
Using string.replace()
$string = '$x = jhgkjh**May**-**Jun**ojklkjh-jkl'
foreach ($Month in $Months.Keys)
{ $string = $string.Replace($Month,$Months.$Month)}
Using the -Replace operator
$string = '$x = jhgkjh**May**-**JUN**ojklkjh-jkl'
foreach ($Month in $Months.Keys)
{ $string = $string -Replace $Month,$Months.$Month }
The string.replace() method is faster, but the -Replace operator will be more flexible with regard to case sensitivity.
Either way, the foreach loop should be faster than foreach-object
Powershell does not have a do until construct. Try this one liner instead of your loop.
0..11 | % { $x = $x -replace $months[$_],$Nmonths[$_] }
The .. Operator creates a range of integers between the first number and the last.
If you still want to use a traditional flow control structure you can replace the until line with this
while($i -lt 12)
Related
So i have an excel file like this
Document Number, Qty, Price
1111-01,1,3.00
1112-00A,2,4.00
And what I am doing is importing it into powershell, the going line by line.
If the quantity is ever greater than 1, I have to duplicate that line that many times whlie changing the quantity to 1 each time and updateing the document number so its unique on each line. I am then adding to an array so i can at the very end export as an excel file.
$report = Import-Excel "pathToFile.xlsx"
$report2 = #()
foreach($line in $report){
$report2+=$line.PSObject.Copy()
}
$template = #()
foreach($line in $report2)
...
some irrelevant code
...
if($line.Qty -gt 1){
$line2 = $line.PSObject.Copy()
$ogInvoice = $line2.'Document Number'.Split("-")[0]
$invoiceAfter = $line2.'Document Number'.Split("-")[1]
if($invoiceAfter -match "^*[A-Z]$"){
$letter = $invoiceAfter.Substring($invoiceAfter.Length-1,1)
}else{
$letter = ""
}
$qty = $line2.Qty
$line2.Qty = 1
$counterQty = 0
while($counterQty -lt $qty){
$invoiceLastTwoNumber = [int]('{0:d2}' -f[int] $invoiceAfter.Substring(0,2)) + $counter
$line2.'Document Number' = (-join($ogInvoice,"-",$invoiceLastTwoNumber.ToString(),$letter))
$counter = $counter + 1
$template+=$line2
$counterQty = $counterQty + 1
}
}
The problem is that after checking the progress, the first time i add the line, the document number is 1112-50A like it should be, then the next time I add the line into $template, the document number is 1112-51A but it updates the previously added line.
So i get
1111-01,1,3.00
1112-51A,1,4.00
1112-51A,1,4.00
Instead of what i want which is:
1111-01,1,3.00
1112-50A,1,4.00
1112-51A,1,4.00
NOTE: the extra coding like PSObject.Copy is other stuff i found online because apparently iterating over the $report is more like a pointer.
If I understand correctly, you're looking to repeat the current object as many times as .Qty only if .Qty is greater than 1 and in addition, update the property Value to 1.
In addition, seems like you're looking to increment the last digits of the property values of Document Number.
Leaving aside the extra code you are currently showing us and focusing only on the question being asked, this is how you could accomplish it, using $csv as an example of your source data.
$csv = #'
Document Number,Qty,Price
1111-01,1,3.00
1112-00A,2,4.00
1113-15A,4,5.00
'# | ConvertFrom-Csv
$re = [regex] '(\d+)(?=[A-Z]$)'
$output = foreach($line in $csv) {
if($line.Qty -gt 1) {
$loopCount = $line.Qty
$line.Qty = 1
for($i = 0; $i -lt $loopCount; $i++) {
$newLine = $line.PSObject.Copy()
$docNumber = $newLine.'Document Number'
$newLine.'Document Number' = $re.Replace($docNumber, {
param($s)
($i + $s.Groups[1].Value).ToString('D2')
})
$newLine
}
continue
}
$line
}
The expected output from the example $csv would be:
Document Number Qty Price
--------------- --- -----
1111-01 1 3.00
1112-00A 1 4.00
1112-01A 1 4.00
1113-15A 1 5.00
1113-16A 1 5.00
1113-17A 1 5.00
1113-18A 1 5.00
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
I get this value
$select_month_year = 12/2017
Trying this:
date('m', strtotime($select_month_year))
But output is:
01
But output should be :
12
$select_month_year = "12/2017";
$month = explode('/', $select_month_year)[0];
echo $month;
Or
$date = date_create_from_format('m/Y', '12/2017');
echo $date->format('m');
How do I convert $var = "000000000" to $var = "0_0000_0000" in Perl ?
If the string is always 9 characters long, you can just use substr:
my $var = '000000000';
substr($var, 5, 0) = '_';
substr($var, 1, 0) = '_';
For formatting strings of arbitrary length you could use a function like this:
sub format_str {
my $str = reverse $_[0];
$str =~ s/(.{4})(?=.)/$1_/g;
return scalar reverse $str;
}
my $var = "000000000";
print format_str $var; # "0_0000_0000"
$var = "000000000";
$var2 = substr($var,0,1)."_".substr($var,1,4)."_".substr($var,5);
print $var2;
Assuming you're asking how to insert a _ after the first and fifth characters of a string, the following are a variety of straightforward solutions:
my $in = '000000000';
my $out = substr($var,0,1) . '_' . substr($var,1,4) . '_' . substr($var,5);
my $in = '000000000';
my $out = join('_', substr($var,0,1), substr($var,1,4), substr($var,5));
my $in = '000000000';
my $out = join('_', unpack('a1 a4 a4*', $in));
my $in = '000000000';
my $out = $in =~ s/^(.)(.{4})/${1}_${2}_/sr; # 5.14+
my $in = '000000000';
( my $out = $in ) =~ s/^(.)(.{4})/${1}_${2}_/s;
In-place:
my $var = '000000000';
$var =~ s/^(.)(.{4})/${1}_${2}_/s;
my $var = '000000000';
substr($var, 5, 0) = '_';
substr($var, 1, 0) = '_';
For a solution for any-length string and considering efficiency issues that arise for very-long strings, please see my previous question&answer: How to chunk text "from the back" in perl.
Per suggestion in comment, here is code using the idea in the linked question/answer which answers the OP question:
use integer;
my $la = length($var);
my $r = $la % 4;
my $q = $la / 4;
my $tr = $r ? "a$r" : "";
$var = join "_", unpack "$tr(a4)$q", $var;
Note: change all three 4s for a different grouping size.
If this is a commify problem that is solved in "How can I output my numbers with commas added?", available as perldoc -q 'commas added', then a similar solution will suffice, with extra parameters to define the separator and the size of the interval
You will want to read the perlfaq entry for other alternatives
use strict;
use warnings 'all';
print group_characters(1234567), "\n";
print group_characters('000000000', '_', 4), "\n";
print group_characters('0123456789ABCDEF', ' ', 4), "\n";
sub group_characters {
my ($s, $sep, $n) = #_;
$sep //= ',';
$n //= 3;
1 while $s =~ s/[^$sep]+\K(?=[^$sep]{$n})/$sep/;
$s;
}
output
1,234,567
0_0000_0000
0123 4567 89AB CDEF
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.