Extract number from a variable in powershell - regex

I am very new to powershell and am trying to extract a number from a variable. For instance, I get the list of ports using this command:
$ports = [system.io.ports.serialport]::getportnames()
The contents of $ports is: COM1, COM2, COM10, COM16 and so on.
I want to extract the numbers from $ports. I looked at this question. It does what I want, but reads from a file.
Please let me know how to resolve this.
Thanks.
Edit: I was able to do what I wanted as follows:
$port=COM20
$port=$port.replace("COM","")
But if there is any other way to do this, I will be happy to learn it.

Well, a quick way would be
$portlist = [System.IO.Ports.SerialPort]::GetPortNames() -replace 'COM'
If you want it to be a list of integers and not numeric strings, then you can use
[int[]] $portlist = ...

Something like this should work:
# initialize the variable that we want to use to store the port numbers.
$portList = #()
# foreach object returned by GetPortNames...
[IO.Ports.SerialPort]::GetPortNames() | %{
# replace the pattern "COM" at the beginning of the string with an empty
# string, just leaving the number. Then add the number to our array.
$portList += ($_ -ireplace "^COM", [String]::Empty)
}
Note that I used [IO.Ports.SerialPort] instead of [System.IO.Ports.SerialPort]. These are the same things - PowerShell implicitly assumes that you're working with the [System] namespace, so you don't need to specify it explicitly, though there's nothing wrong with doing it.
Edit
To answer your questions:
%{...} is shorthand for foreach-object {...}.
$_ indicates the object that is currently in the pipeline. When we're inside a foreach-object block, $_ resolves to the one object out of the entire collection that we're currently dealing with.
If we write my code a bit differently, I think it'll be easier to understand. Between these examples, $_ and $port are the same things.
$portList = #()
foreach ($port in [IO.Ports.SerialPorts]::GetPortNames()) {
$portList += ($port -ireplace "^COM", [String]::Empty)
}
Hope that helps!

This should cover it:
$portnos = $ports | foreach {$_ -replace 'COM',''}

Related

PowerShell Issues with getting string from file

Problem
I am working on an automated version control script in powershell and I hit a snag where I am trying pull a line of text from an AssemblyInfo.cs file. However, I can't seem to get it to work as expected despite all my efforts.
After many trial and errors to achieve the results intended, I have come to find the following to get close to what I'm trying to achieve:
# Constants
$Assembly = Get-Item "C:\generalfilepath\*\Assembly.cs"
$regex = '(?<!\/\/ \[assembly: AssemblyVersion\(")(?<=\[assembly: AssemblyVersion\(")[^"]*'
$CurrentVersion = GC $Assembly
$CurrentVersion = $CurrentVersion -match $regex
Write-Host "CurrentVersion = $CurrentVersion
I am expecting to see: CurrentVersion = 1.0.0.0 or something similar, but what I get is: CurrentVersion = [assembly: AssemblyVersion("1.0.0.0")]
I have been searching all over for examples on how to properly utilize regex with PowerShell, but I have not really found anything that contains more than the regex itself... As such, I was hoping someone here could assist me in either steering me in the right direction or point out what I am doing wrong.
Question
Despite the use of the regex filter, I'm getting the entire line instead of just the value I want. How do I ensure the variable stores ONLY the target value using the lookbehind and lookahead regex filters?
You can try:
C:\> ([string] (Get-Content \AssemblyInfo.cs)) -match 'AssemblyVersion\("([0-9]+(\.([0-9]+|\*)){1,3}){1}"\)'
True
Afterwards the result shall be store in $Matches[1]
C:\> $Matches[1]
6.589.0.123
Be aware to cast the results of Get-Content to [string] otherwise $Matches will be $null. See this answer for a complete description.
If you want to use a more "greedy" regex like ^\[assembly: AssemblyVersion\("([0-9]+(\.([0-9]+|\*)){1,3}){1}"\) which also includes a check (^\[assembly:) that the string starts with [assembly you've to walk through the [string[]] array Get-Content returns:
> gc .\AssemblyInfo.cs | % { $_ -match '^\[assembly: AssemblyVersion\("([0-9]+(\.([0-9]+|\*)){1,3}){1}"\)' }
> $Matches[1]
6.589.0.*
Hope that helps.

Trim or replace PowerShell string variable

I have a string variable that holds the following information
#{EmailAddress=test1#tenant.onmicrosoft.com},#{EmailAddress=test2#tenant.onmicrosoft.com}
I am using this variable for a cmdlet that only accepts data in the format of
test1#tenant.onmicrosoft.com, test2#tenant.onmicrosoft.com
I have tried TrimStart("#{EmailAddress=") but this only removes #{EmailAddress= for the first user and I guess TrimEnd would not be much use as I presume that it is due to the fact it reading the string as one line and not as user1,user2 etc.
Would anyone be able to provide advice on how to remove these unwanted characters.
A possible solution is to use Regex to extract the strings that you want, and combine them into a result:
$str = "#{EmailAddress=test1#tenant.onmicrosoft.com},#{EmailAddress=test2#tenant.onmicrosoft.com}"
$pattern = [regex]'#{EmailAddress=(.+?)}'
$result = ($pattern.Matches($str) | % {$_.groups[1].value}) -join ','
$result then is:
test1#tenant.onmicrosoft.com,test2#tenant.onmicrosoft.com
Another solution would be to use the -replace function. This is just a one-liner:
'#{EmailAddress=test1#tenant.onmicrosoft.com},#{EmailAddress=test2#tenant.onmicrosoft.com}' -replace '#{EmailAddress=([^}]+)}.*?', '$1'
Regex used to replace:

Extract the id from long file path ( regex) perl

I am trying to extract an id ( here eg 11894373690) from a file path that i read int my perl script -
/my/local/projects/Samplename/analysis/test/output/tool1/11894373690_cast/A1/A1a/
and I will further use it create a new path like
/my/local/projects/Samplename/analysis/test/output/tool2/11894373690_NEW/
I am not able to extract just the id from the path, can anyone please suggest an easy method in perl. I should definitely start learning regular expressions!
Thanks.
I am able to get only the last directory name
$file = "/my/local/projects/Samplename/analysis/test/output/tool1/11894373690_cast/A1/A1a/ ";
my ($id) = $file =~ /\.(A1[^]+)/i;
Update - Sorry all I misspelled "not" as "now" earlier! I am not able to extract the id. Thanks!
A simple regex or split are fine, but there are multiple core packages for working with paths.
This uses File::Spec to split the path and to later join the new one. Note that there is no escaping or such, no / counting -- in fact no need to even mention the separator.
use warnings 'all';
use strict;
use File::Spec::Functions qw(splitdir catdir);
my $path_orig = '...';
my #path = splitdir $path_orig;
my ($mark, $dir);
foreach my $i (0..$#path)
{
if ($path[$i] =~ m/(\d+)_cast/)
{
$dir = $1;
$mark = $i;
last;
}
}
my $path_new = catdir #path[0..$mark-1], $dir . '_NEW';
You can manipulate the #path array in other ways, of course -- peel components off of the back of it (pop #path while $path[-1] !~ /.../), or iterate and copy into a new array, etc.
The code above is simple and doesn't need extra data copy nor multiple regex matches.
Apparently the old and new path have another difference (tool1 vs tool2), please adjust. The main point is that once the path is split it is simple to go through the array.
As for a simple regex to fetch the id
my ($id) = $path =~ m{/(\d+)_cast/};
If \d+_cast is certain to be un-ambiguous (only one dir with that in its name) drop the / above.
What so you need to be fixed? and what will be dynamic? for this url, supposing that the posfix will aways be _cast you can use the expression:
(\d+)_cast
so the ID will be in the first selection group
I did find a way to get the id - it may not be efficient but works for now
I did
my $dir_path = "/my/local/projects/Samplename/analysis/test/output/tool1/11894373690_cast/A1/A1a/ ";
my #keys =(split(/[\/_]+/,$dir_path));
print "Key is $keys3[9]\n";
it prints out 11894373690
Thanks all for the suggestions!

PowerShell multiple string replacement efficiency

I'm trying to replace 600 different strings in a very large text file 30Mb+. I'm current building a script that does this; following this Question:
Script:
$string = gc $filePath
$string | % {
$_ -replace 'something0','somethingelse0' `
-replace 'something1','somethingelse1' `
-replace 'something2','somethingelse2' `
-replace 'something3','somethingelse3' `
-replace 'something4','somethingelse4' `
-replace 'something5','somethingelse5' `
...
(600 More Lines...)
...
}
$string | ac "C:\log.txt"
But as this will check each line 600 times and there are well over 150,000+ lines in the text file this means there’s a lot of processing time.
Is there a better alternative to doing this that is more efficient?
Combining the hash technique from Adi Inbar's answer, and the match evaluator from Keith Hill's answer to another recent question, here is how you can perform the replace in PowerShell:
# Build hashtable of search and replace values.
$replacements = #{
'something0' = 'somethingelse0'
'something1' = 'somethingelse1'
'something2' = 'somethingelse2'
'something3' = 'somethingelse3'
'something4' = 'somethingelse4'
'something5' = 'somethingelse5'
'X:\Group_14\DACU' = '\\DACU$'
'.*[^xyz]' = 'oO{xyz}'
'moresomethings' = 'moresomethingelses'
}
# Join all (escaped) keys from the hashtable into one regular expression.
[regex]$r = #($replacements.Keys | foreach { [regex]::Escape( $_ ) }) -join '|'
[scriptblock]$matchEval = { param( [Text.RegularExpressions.Match]$matchInfo )
# Return replacement value for each matched value.
$matchedValue = $matchInfo.Groups[0].Value
$replacements[$matchedValue]
}
# Perform replace over every line in the file and append to log.
Get-Content $filePath |
foreach { $r.Replace( $_, $matchEval ) } |
Add-Content 'C:\log.txt'
So, what you're saying is that you want to replace any of 600 strings in each of 150,000 lines, and you want to run one replace operation per line?
Yes, there is a way to do it, but not in PowerShell, at least I can't think of one. It can be done in Perl.
The Method:
Construct a hash where the keys are the somethings and the values are the somethingelses.
Join the keys of the hash with the | symbol, and use it as a match group in the regex.
In the replacement, interpolate an expression that retrieves a value from the hash using the match variable for the capture group
The Problem:
Frustratingly, PowerShell doesn't expose the match variables outside the regex replace call. It doesn't work with the -replace operator and it doesn't work with [regex]::replace.
In Perl, you can do this, for example:
$string =~ s/(1|2|3)/#{[$1 + 5]}/g;
This will add 5 to the digits 1, 2, and 3 throughout the string, so if the string is "1224526123 [2] [6]", it turns into "6774576678 [7] [6]".
However, in PowerShell, both of these fail:
$string -replace '(1|2|3)',"$($1 + 5)"
[regex]::replace($string,'(1|2|3)',"$($1 + 5)")
In both cases, $1 evaluates to null, and the expression evaluates to plain old 5. The match variables in replacements are only meaningful in the resulting string, i.e. a single-quoted string or whatever the double-quoted string evaluates to. They're basically just backreferences that look like match variables. Sure, you can quote the $ before the number in a double-quoted string, so it will evaluate to the corresponding match group, but that defeats the purpose - it can't participate in an expression.
The Solution:
[This answer has been modified from the original. It has been formatted to fit match strings with regex metacharacters. And your TV screen, of course.]
If using another language is acceptable to you, the following Perl script works like a charm:
$filePath = $ARGV[0]; # Or hard-code it or whatever
open INPUT, "< $filePath";
open OUTPUT, '> C:\log.txt';
%replacements = (
'something0' => 'somethingelse0',
'something1' => 'somethingelse1',
'something2' => 'somethingelse2',
'something3' => 'somethingelse3',
'something4' => 'somethingelse4',
'something5' => 'somethingelse5',
'X:\Group_14\DACU' => '\\DACU$',
'.*[^xyz]' => 'oO{xyz}',
'moresomethings' => 'moresomethingelses'
);
foreach (keys %replacements) {
push #strings, qr/\Q$_\E/;
$replacements{$_} =~ s/\\/\\\\/g;
}
$pattern = join '|', #strings;
while (<INPUT>) {
s/($pattern)/$replacements{$1}/g;
print OUTPUT;
}
close INPUT;
close OUTPUT;
It searches for the keys of the hash (left of the =>), and replaces them with the corresponding values. Here's what's happening:
The foreach loop goes through all the elements of the hash and create an array called #strings that contains the keys of the %replacements hash, with metacharacters quoted using \Q and \E, and the result of that quoted for use as a regex pattern (qr = quote regex). In the same pass, it escapes all the backslashes in the replacement strings by doubling them.
Next, the elements of the array are joined with |'s to form the search pattern. You could include the grouping parentheses in $pattern if you want, but I think this way makes it clearer what's happening.
The while loop reads each line from the input file, replaces any of the strings in the search pattern with the corresponding replacement strings in the hash, and writes the line to the output file.
BTW, you might have noticed several other modifications from the original script. My Perl has collected some dust during my recent PowerShell kick, and on a second look I noticed several things that could be done better.
while (<INPUT>) reads the file one line at a time. A lot more sensible than reading the entire 150,000 lines into an array, especially when your goal is efficiency.
I simplified #{[$replacements{$1}]} to $replacements{$1}. Perl doesn't have a built-in way of interpolating expressions like PowerShell's $(), so #{[ ]} is used as a workaround - it creates a literal array of one element containing the expression. But I realized that it's not necessary if the expression is just a single scalar variable (I had it in there as a holdover from my initial testing, where I was applying calculations to the $1 match variable).
The close statements aren't strictly necessary, but it's considered good practice to explicitly close your filehandles.
I changed the for abbreviation to foreach, to make it clearer and more familiar to PowerShell programmers.
I also have no idea how to solve this in powershell, but I do know how to solve it in Bash and that is by using a tool called sed. Luckily, there is also Sed for Windows. If all you want to do is replace "something#" with "somethingelse#" everywhere then this command will do the trick for you
sed -i "s/something([0-9]+)/somethingelse\1/g" c:\log.txt
In Bash you'd actually need to escape a couple of those characters with backslashes, but I'm not sure you need to in windows. If the first command complains you can try
sed -i "s/something\([0-9]\+\)/somethingelse\1/g" c:\log.txt
I would use the powershell switch statement:
$string = gc $filePath
$string | % {
switch -regex ($_) {
'something0' { 'somethingelse0' }
'something1' { 'somethingelse1' }
'something2' { 'somethingelse2' }
'something3' { 'somethingelse3' }
'something4' { 'somethingelse4' }
'something5' { 'somethingelse5' }
'pattern(?<a>\d+)' { $matches['a'] } # sample of more complex logic
...
(600 More Lines...)
...
default { $_ }
}
} | ac "C:\log.txt"

How could I get this regex statment to capture just a select piece

I am trying to get the regex in this loop,
my $vmsn_file = $snapshots{$snapshot_num}{"filename"};
my #current_vmsn_files = $ssh->capture("find -name $vmsn_file");
foreach my $vmsn (#current_vmsn_files) {
$vmsn =~ /(.+\.vmsn)/xm;
print "$1\n";
}
to capture the filename from this line,
./vmfs/volumes/4cbcad5b-b51efa39-c3d8-001517585013/MX01/MX01-Snapshot9.vmsn
The only part I want is the part is the actual filename, not the path.
I tried using an expression that was anchored to the end of the line using $ but that did not seem to make any difference. I also tried using 2 .+ inputs, one before the capture group and the one inside the capture group. Once again no luck, also that felt kinda messy to me so I don't want to do that unless I must.
Any idea how I can get at just the file name after the last / to the end of the line?
More can be added as needed, I am not sure what I needed to post to give enough information.
--Update--
With 5 minutes of tinkering I seemed to have figured it out. (what a surprise)
So now I am left with this, (and it works)
my $vmsn_file = $snapshots{$snapshot_num}{"filename"};
my #current_vmsn_files = $ssh->capture("find -name $vmsn_file");
foreach my $vmsn (#current_vmsn_files) {
$vmsn =~ /.+\/(\w+\-Snapshot\d+\.vmsn)/xm;
print "$1\n";
}
Is there anyway to make this better?
Probably the best way is using the core module File::Basename. That will make your code most portable.
If you really want to do it with a regex and you are based on Unix, then you could use:
$vmsn =~ m%.*/([^/]+)$%;
$file = $1;
well, if you are going to use find command from the shell, and considering you stated that you only want the file name, why not
... $ssh->capture("find -name $vmsn_file -printf \"%f\n\" ");
If not, the simplest way is to split() your string on "/" and then get the last element. No need regular expressions that are too long or complicated.
See perldoc -f split for more information on usage