Parse string using the Where-Object cmdlet - powershell

So I have this simple collection:
$str = "bla_sdf_r1_vrr_1.0.1", "bla_sdf_r1_trr_1.0.2"
If this string contains the word vrr i want to take the version number 1.0.1 and if this string contains the word trr i want to take the version number 1.0.2
This is what I have tried and it works fine:
$fld = $str | Where { $_.Contains("vrr") }
$ver = $fld.Split("_")[4]
But I want to do that in one line.
So I tried:
$fld = $str | Where { $_.Contains("vrr") } | -split[4]
But this probably not the right way.
Any suggestions?

You could do that with a regex. In case it doesn't contain vrr, the output is empty:
[regex]::Match('bla_sdf_r1_vrdr_1.0.1', '.*vrr_(.*)').Groups[1].Value

You can use Regex.
"bla_sdf_r1_vrr_1.0.1" -match "[\d\.]*$"
It searches your string for digits and . as many times as possible in the end of the string.
Your result is in the automatic variable $matches[0]
Edit:
Given your collection, you could do something like this:
$str | ForEach-Object { if($_ -match "[\d\.]*$") { $matches[0] } }
If the current string matches the regex I wrote, print the match.

you can group the matches and access them by number (or also name them)
$str = "bla_sdf_r1_vrr_1.0.1", "bla_sdf_r1_trr_1.0.2"
$str | %{
if ($_ -match '(vrr|trr)_([0-9.]+)') {
$matches[1] + " is " + $matches[2]
}
}
Output
vrr is 1.0.1
trr is 1.0.2
The output will be the same with this variant:
$str = "bla_sdf_r1_vrr_1.0.1", "bla_sdf_r1_trr_1.0.2"
$str | %{
if ($_ -match '(?<xrr>vrr|trr)_(?<version>[0-9.]+)') {
$matches.xrr + " is " + $matches.version
}
}

$str = "bla_sdf_r1_vrr_1.0.1", "bla_sdf_r1_trr_1.0.2"
$str | where {($_ -split "_")[3] -in ("vrr", "trr") } | select #{N="Type";E={($_ -split "_")[3]}}, #{N="Version";E={($_ -split "_")[4]}}

Related

Powershell overwriting file contents with match instead of editing single line

I have a text file that contains a string I want to modify.
Example text file contents:
abc=1
def=2
ghi=3
If I run this code:
$file = "c:\test.txt"
$MinX = 100
$MinY = 100
$a = (Get-Content $file) | %{
if($_ -match "def=(\d*)"){
if($Matches[1] -gt $MinX){$_ -replace "$($Matches[1])","$($MinX)" }
}
}
$a
The result is:
def=100
If I omit the greater-than check like so:
$a = (Get-Content $file) | %{
if($_ -match "def=(\d*)"){
$_ -replace "$($Matches[1])","$($MinX)"
}
}
$a
The result is correct:
abc=1
def=100
ghi=3
I don't understand how a simple integer comparison before doing the replace could screw things up so badly, can anyone advise what I'm missing?
The comparison operator -gt will never get you a value of $true because you need to
cast the $matches[1] string value to int first so it compares two integer numbers
2 is never greater than 100.. Change the operator to -lt instead.
Your code outputs only one line, because you forgot to also output unchanged lines that do not match the regex
$file = 'c:\test.txt'
$MinX = 100
$MinY = 100
$a = (Get-Content $file) | ForEach-Object {
if ($_ -match '^def=(\d+)'){
if([int]$matches[1] -lt $MinX){ $_ -replace $matches[1],$MinX }
}
else {
$_
}
}
$a
Or use switch (is also faster than using Get-Content):
$file = 'c:\test.txt'
$MinX = 100
$MinY = 100
$a = switch -Regex -File $file {
'^def=(\d+)' {
if([int]$matches[1] -lt $MinX){ $_ -replace $matches[1],$MinX }
}
default { $_ }
}
$a
Output:
abc=1
def=100
ghi=3
That's because the expression ($Matches[1] -gt $MinX) is a string comparison. In Powershell, the left-hand side of a comparison dictates the comparison type and since that is of type [string], Powershell has to cast/convert the right-hand side of the expression to [string] also. You expression, therefore, is evaluated as ([string]$Matches[1] -gt [string]$MinX).

How do I loop through a string read from a file against a fixed set of characters individually and if they match print the string in powershell

I currently have a foreach loop, that gets content from a small dictionary file (only strings over 3 characters). I am looking to compare each character in the $line against my other characters, in this case "b" "i" "n" "g" "o" so that if all the characters in $line are in bingo, then it prints the word. If not it loops to the next word.
So far I have:
foreach($line in Get-Content Desktop/dict.txt | Sort-Object Length, { $_ })
The bit I can't get (not too familiar with powershell) is this:
if($line.length -gt 3){
if( i in $line == 'b')
if( i in $line == 'i')
if( i in $line == 'n')
if( i in $line == 'g')
if( i in $line == 'o')
write-output $line
}
}
If I understood correctly, if you want to check if $line is contained in bingo you could use the -match for case insensitive and -cmatch for case sensitive operators. See Comparison Operators.
For example:
PS /> 'bingo' -match 'ing'
True
PS /> 'bingo' -match 'bin'
True
PS /> 'bingo' -match 'ngo'
True
The code could look like this:
foreach($line in Get-Content Desktop/dict.txt | Sort-Object Length, { $_ })
{
if($line.length -gt 3 -and 'bingo' -match $line)
{
$line
# you can add break here to stop this loop if the word is found
}
}
Edit
If you want to check if 3 or more characters in bingo (in any order) are contained in $line, there are many ways to do this, this is the approach I would take:
# Insert magic word here
$magicWord = 'bingo'.ToCharArray() -join '|'
foreach($line in Get-Content Desktop/dict.txt | Sort-Object Length, { $_ })
{
# Remove [Text.RegularExpressions.RegexOptions]::IgnoreCase if you want it to be Case Sensitive
$check = [regex]::Matches($line,$magicWord,[Text.RegularExpressions.RegexOptions]::IgnoreCase)
# If 3 or more unique characters were matched
if(($check.Value | Select-Object -Unique).count -ge 3)
{
'Line is: {0} || Characters Matched: {1}' -f $line,-join $check.Value
}
}
Demo
Given the following words:
$test = 'ngob','ibgo','gn','foo','var','boing','ingob','oubingo','asdBINGasdO!'
It would yield:
Line is: ngob || Characters Matched: ngob
Line is: ibgo || Characters Matched: ibgo
Line is: boing || Characters Matched: boing
Line is: ingob || Characters Matched: ingob
Line is: oubingo || Characters Matched: obingo
Line is: asdBINGasdO! || Characters Matched: BINGO
So you want to get back any words that are the same length and have the same characters no matter the order?
$dict = #(
'bingo'
'rambo'
'big'
'gobin'
'bee'
'ebe'
'been'
'ginbo'
)
$word = 'bingo'
$dict |
Where-Object { $_.length -eq $word.Length } |
ForEach-Object {
$dictwordLetters = [System.Collections.Generic.List[char]]::new($_.ToCharArray())
$word.ToCharArray() | ForEach-Object {
$dictwordLetters.Remove($_) | Out-Null
}
if (-not $dictwordLetters.Count) {
$_
}
}
The following will be the output
bingo
gobin
ginbo
By taking parts of both answers I was able to get the result I was after. As I am new to this, not sure how to thank #martin and #santiago for their work.
This was the code that was put together, which was pretty much taking the dictionary file and then rather than a fixed string size made it greater than 3:
$dict = #(Get-Content Desktop/dict.txt | Sort-Object Length, { $_ })
$word = 'bingo'
$dict |
Where-Object { $_.length -gt 2 } |
ForEach-Object {
$dictwordLetters = [System.Collections.Generic.List[char]]::new($_.ToCharArray())
$word.ToCharArray() | ForEach-Object {
$dictwordLetters.Remove($_) | Out-Null
}
if (-not $dictwordLetters.Count) {
$_
}
}
Your assistance was greatly appreciated.
Here's my two cents:
$dict = 'apple', 'brown', 'green', 'cake', 'bin', 'pear', 'big', 'milk', 'bio', 'bong', 'bingo', 'bodings', 'gibson'
# the search term as string
$term = 'bingo'
# merge the unique characters into a regex like '[bingo]+'
$chars = '[{0}]+' -f (($term.ToCharArray() | Select-Object -Unique) -join '')
# loop over the array (lines in the text file)
$dict | ForEach-Object {
# get all matched characters, uniqify and join if there are more matches.
$found = (($_ | Select-String -Pattern $chars -AllMatches).Matches.Value | Select-Object -Unique ) -join '' | Where-Object { $_.Length -ge 3 }
if ($found) {
# outputting an object better explains what is matched in which line
[PsCustomObject]#{
Line = $_
CharactersMatched = $found
}
# of course, you can also simply output the found matching characters
# $found
}
}
Output:
Line CharactersMatched
---- -----------------
brown bon
bin bin
big big
bio bio
bong bong
bingo bingo
bodings boing
gibson gibon
The previous answers all seem overly complicated. If you are trying to match strings then that sounds like a problem that requires a regular expression, and if that is the case then Select-String would be a better option than Get-Content. Below is an example, I am not sure if it is exactly right for your needs but should point you in the right direction:
Select-String 'Desktop/dict.txt' -pattern '^[bingo]{3,}$'

How can I transpose and parse a large vertical text file into a CSV file with headers?

I have a large text file (*.txt) in the following format:
; KEY 123456
; Any Company LLC
; 123 Main St, Anytown, USA
SEC1 = xxxxxxxxxxxxxxxxxxxxx
SEC2 = xxxxxxxxxxxxxxxxxxxxx
SEC3 = xxxxxxxxxxxxxxxxxxxxx
SEC4 = xxxxxxxxxxxxxxxxxxxxx
SEC5 = xxxxxxxxxxxxxxxxxxxxx
SEC6 = xxxxxxxxxxxxxxxxxxxxx
This is repeated for about 350 - 400 keys. These are HASP keys and the SEC codes associated with them. I am trying to parse this file into a CSV file with KEY and SEC1 - SEC6 as the headers, with the rows being filled in. This is the format I am trying to get to:
KEY,SEC1,SEC2,SEC3,SEC4,SEC5,SEC6
123456,xxxxxxxxxx,xxxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx
456789,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx,xxxxxxxxxx
I have been able to get a script to export to a CSV with only one key in the text file (my test file), but when I try to run it on the full list, it only exports the last key and sec codes.
$keysheet = '.\AllKeys.txt'
$holdarr = #{}
Get-Content $keysheet | ForEach-Object {
if ($_ -match "KEY") {
$key, $value = $_.TrimStart("; ") -split " "
$holdarr[$key] = $value }
elseif ($_ -match "SEC") {
$key, $value = $_ -split " = "
$holdarr[$key] = $value }
}
$hash = New-Object PSObject -Property $holdarr
$hash | Export-Csv -Path '.\allsec.csv' -NoTypeInformation
When I run it on the full list, it also adds a couple of extra columns with what looks like properties instead of values.
Any help to get this to work would be appreciated.
Thanks.
Here's the approach I suggest:
$output = switch -Regex -File './AllKeys.txt' {
'^; KEY (?<key>\d+)' {
if ($o) {
[pscustomobject]$o
}
$o = #{
KEY = $Matches['key']
}
}
'^(?<sec>SEC.*?)\s' {
$o[$Matches['sec']] = ($_ | ConvertFrom-StringData)[$Matches['sec']]
}
default {
Write-Warning -Message "No match found: $_"
}
}
# catch the last object
$output += [pscustomobject]$o
$output | Export-Csv -Path './some.csv' -NoTypeInformation
This would be one approach.
& {
$entry = $null
switch -Regex -File '.\AllKeys.txt' {
"KEY" {
if ($entry ) {
[PSCustomObject]$entry
}
$entry = #{}
$key, $value = $_.TrimStart("; ") -split " "
$entry[$key] = [int]$value
}
"SEC" {
$key, $value = $_ -split " = "
$entry[$key] = $value
}
}
[PSCustomObject]$entry
} | sort KEY | select KEY,SEC1,SEC2,SEC3,SEC4,SEC5,SEC6 |
Export-Csv -Path '.\allsec.csv' -NoTypeInformation
Lets leverage the strength of ConvertFrom-StringData which
Converts a string containing one or more key and value pairs to a hash table.
So what we will do is
Split into blocks of text
edit the "; Key" line
Remove an blank lines or semicolon lines.
Pass to ConvertFrom-StringData to create a hashtable
Convert that to a PowerShell object
$path = "c:\temp\keys.txt"
# Split the file into its key/sec collections. Drop any black entries created in the split
(Get-Content -Raw $path) -split ";\s+KEY\s+" | Where-Object{-not [string]::IsNullOrWhiteSpace($_)} | ForEach-Object{
# Split the block into lines again
$lines = $_ -split "`r`n" | Where-Object{$_ -notmatch "^;" -and -not [string]::IsNullOrWhiteSpace($_)}
# Edit the first line so we have a full block of key=value pairs.
$lines[0] = "key=$($lines[0])"
# Use ConvertFrom-StringData to do the leg work after we join the lines back as a single string.
[pscustomobject](($lines -join "`r`n") | ConvertFrom-StringData)
} |
# Cannot guarentee column order so we force it with this select statement.
Select-Object KEY,SEC1,SEC2,SEC3,SEC4,SEC5,SEC6
Use Export-CSV to your hearts content now.

Retrieving second part of a line when first part matches exactly

I used the below steps to retrieve a string from file
$variable = 'abc#yahoo.com'
$test = $variable.split('#')[0];
$file = Get-Content C:\Temp\file1.txt | Where-Object { $_.Contains($test) }
$postPipePortion = $file | Foreach-Object {$_.Substring($_.IndexOf("|") + 1)}
This results in all lines that contain $test as a substring. I just want the result to contain only the lines that exactly matches $test.
For example, If a file contains
abc_def|hf#23$
abc|ohgvtre
I just want the text ohgvtre
If I understand the question correctly you probably want to use Import-Csv instead of Get-Content:
Import-Csv 'C:\Temp\file1.txt' -Delimiter '|' -Header 'foo', 'bar' |
Where-Object { $_.foo -eq $test } |
Select-Object -Expand bar
To address the exact matching, you should be testing for equality (-eq) rather than substring (.Contains()). Also, there is no need to parse the data multiple times. Here is your code, rewritten to to operate in one pass over the data using the -split operator.
$variable = 'abc#yahoo.com'
$test = $variable.split('#')[0];
$postPipePortion = (
# Iterate once over the lines in file1.txt
Get-Content C:\Temp\file1.txt | foreach {
# Split the string, keeping both parts in separate variables.
# Note the backslash - the argument to the -split operator is a regex
$first, $second = ($_ -split '\|')
# When the first half matches, output the second half.
if ($first -eq $test) {
$second
}
}
)

powershell split keeping only the 2 first part of string

i'm new in powershell
i have a file that contains OU paths separates by "/"
i'd like to only keep the 2 first element of each string
example:
paris/sales/salers/14tharrdt
london/comptability/office1
would give
paris/sales
london/comptability
i googled and found many things, but no way to do this simple thing
thanks for help
You could use -split to split the string on the \ character, select the first two elements and join them together:
$content = Get-Content 'your_file_path'
$content | foreach {
($_ -split '/')[0, 1] -join '/'
}
$content | Set-Content 'your_file_path'
Or, if you prefer using regex here a solution without split (demo here):
$content = Get-Content 'your_file_path'
$content | foreach {
$_ -replace '(.*?\/[^\/]+).*', '$1'
}
$content | Set-Content 'your_file_path'
Like:
$a = "paris/sales/salers/14tharrdt"
$b = $a.Split("/")[0] + "/" + $a.Split("/")[1]
$b
Improvement would be if call Split once and save it to a variable