I want to check if an element exist in an array - powershell

I want to check if a element exist in an array.
$data = "100400296 676100 582"
$i = "18320-my-turn-582"
if ($data -like $i) { Write-Host "Exist" }
else { Write-Host "Didn't exist" }
This example doesn't work like I want it. $i contains 582, so I want it to be Exist in result.

Your string "18320-my-turn-582" doesn't exist in $data, even though both strings contain the substring 582.
PowerShell treats your strings as a whole, and 18320-my-turn-582 is not present in 100400296 676100 582. To work around this you can:
Use Regex:
$i -match '\d+$'
$data -match $Matches[0]
Split the $i at hyphens so you will have:
$i = $i -split '-'
# turns $i into a array with the elements:
# 18320
# my
# turn
# 582
$data -match $i[-1]
# Output: 100400296 676100 582
Check out Get-Help about_Comparison_Operators to understand the differences between -Contains, -Match and -Like operators.

Related

Powershell - Matching text from array and string?

I have an array that simply pulls a list of numbers in one long column. I am trying to match it with some data in a string and if it matches, I am wanting it to state Down otherwise it will state Up in the output CSV.
Here is my code: `
IF($RESULTS -like $TEST)
{$Output = "DOWN"
}ELSE{
$OUtput = "UP"
}
`
$RESULTS is the array, and $TEST is the string. If I do -match it works, but -match only pulls single digits so it gives false positives. For example, if there is a 3 in the list as well as 638 it will mark them both as down. None of the other switches seem to work like -eq, -like, etc.
What am I missing please?
Thanks much for any assistance!
EDIT:
Sample of Data in $TEST
2
3
5
Sample of Output of $RESULTS
5
628
Since 5 exists in both, my expected output would be DOWN and everything else would be UP.
It sounds like you have two arrays of numbers, and you want to test if the input array contains at least one of the values in the test array.
You can use Compare-Object, which compares two arrays and indicates which elements are different and, with -IncludeEqual, also which ones are the same:
if (
(Compare-Object -IncludeEqual $RESULTS $TEST).
Where({ $_.SideIndicator -eq '==' }).
Count -gt 0
) {
$Output = "DOWN"
}
else {
$Output = "UP"
}
As an aside:
You can use an if statement as an expression, which means you only need to specify $Output once:
$Output =
IF (<# conditional #>) {
"DOWN"
}
ELSE {
"UP"
}
In PowerShell (Core) 7+, you can use ?:, the ternary conditional operator for a more concise solution:
$Output = <# conditional #> ? 'DOWN' : 'UP'
I would do it by using "foreach". Hope this might be helpful
foreach ($result in $RESULTS){
if ($result -like $Test){
$OUTPUT = "Down"}
else{
$OUTPUT= "UP"}
}
In your edited question you show that variable $TEST is also an array, so in that case you can do
$TEST = 2,3,5
$RESULTS = 5,628
# compare both arrays for equal items
$Output = if ($TEST | Where-Object { $RESULTS -contains $_ }) {"DOWN"} else {"UP"}
$Output
In this case, $Output will be DOWN because both arrays have the number 5
If however variable $TEST contains a multiline string, then first create an array out of that like
$TEST = #'
2
3
5
'#
# convert the string to array by splitting at the newline
$TEST = $TEST -split '\r?\n' -ne ''
$RESULTS = 5,628
# compare both arrays for equal items
$Output = if ($TEST | Where-Object { $RESULTS -contains $_ }) {"DOWN"} else {"UP"}
$Output

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).

Surrounding a string variable with quotes

I am writing an IIS log parser and having trouble wrapping a variable value in quotes while doing some string processing.
Here is a truncated log file, as an example:
#Fields: date time s-ip cs-method ...
2021-08-09 19:00:16.367 0.0.0.0 GET ...
2021-08-09 19:01:42.184 0.0.0.0 POST ...
Here is how I am executing the code below:
.\Analyse.ps1 cs-method -eq `'POST`'
If the line marked with #PROBLEM is executed as is, the output looks like this:
> .\Analyse.ps1 cs-method -eq `'POST`'
"""""G""E""T""""" ""-""e""q"" ""'""P""O""S""T""'""
"""""P""O""S""T""""" ""-""e""q"" ""'""P""O""S""T""'""
But if I replace $quoted with $value, so that the code reads like this:
$thisInstruction = $thisInstruction -replace $key , $value #PROBLEM
The output looks like this:
> .\Analyse.ps1 cs-method -eq `'POST`'
GET -eq 'POST'
POST -eq 'POST'
The problem is that I want the first value on each line of the output (the GET and the POST before the -eq) to be wrapped in quotes.
How can I achieve this?
Here is my code:
# compile cli args into single line instruction
$instruction = $args -join " "
# define key array
$keys = #('date','time','s-ip','cs-method','cs(Host)','cs-uri-stem','cs-uri-query','s-computername','s-port','cs-username','c-ip','s-sitename','cs(User-Agent)','cs(Referer)','sc-status','sc-substatus','sc-win32-status','TimeTakenMS','x-forwarded-for')
# <#
# get current execution folder
$currentFolder = Get-Location
# define string splitter regex https://www.reddit.com/r/PowerShell/comments/2h5elx/split_string_by_spaces_unless_in_quotes/ckpkydh?utm_source=share&utm_medium=web2x&context=3
$splitter = ' +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)'
# process *.log files in folder
Get-Childitem -Path $currentFolder *.log1 | ForEach-Object {
# process each line in the file
Get-Content $_.Name | ForEach-Object {
# duplicate instruction
$thisInstruction = $instruction
# exclude comment lines
if (!$_.StartsWith('#')) {
# split line into array
$logEntryArr = $_ -Split $splitter
# populate dictionary with contents of array
For ($i=0; $i -le $keys.length; $i++) {
# get key
$key = $keys[$i]
# get value
$value = $logEntryArr[$i]
$quoted = "`""+$value+"`""
# replace mention of key in instruction with dictionary reference
$thisInstruction = $thisInstruction -replace $key , $quoted #PROBLEM
}
# process rule from command line against dictionary
echo $thisInstruction
}
}
}
#>
I do know why, thanks to #mathias-r-jessen commenting
I don't know why, but altering the For loop to iterate one fewer fixed the problem and does not appear to leave out any keys. The only significant change is this:
For ($i=0; $i -le $keys.length-1; $i++) {
This PowerShell script can be used to query a folder of log files echo out matching rows, eg:
.\Analyse.ps1 cs-method -eq 'GET'
The above would print out all log entries with a cs-method value of GET.
Here's the code:
# compile cli args into single line instruction
$instruction = $args -join " "
# define key array
$keys = #('date','time','s-ip','cs-method','cs(Host)','cs-uri-stem','cs-uri-query','s-computername','s-port','cs-username','c-ip','s-sitename','cs(User-Agent)','cs(Referer)','sc-status','sc-substatus','sc-win32-status','TimeTakenMS','x-forwarded-for')
# get current execution folder
$currentFolder = Get-Location
# define string splitter regex https://www.reddit.com/r/PowerShell/comments/2h5elx/split_string_by_spaces_unless_in_quotes/ckpkydh?utm_source=share&utm_medium=web2x&context=3
$splitter = ' +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)'
# process *.log files in folder
Get-Childitem -Path $currentFolder *.log | ForEach-Object {
# process each line in the file
Get-Content $_.Name | ForEach-Object {
# duplicate instruction
$thisInstruction = $instruction
# exclude comment lines
if (!$_.StartsWith('#')) {
# split line into array
$logEntryArr = $_ -Split $splitter
# populate dictionary with contents of array
For ($i=0; $i -lt $keys.length; $i++) {
# get key
$key = $keys[$i]
# get value
$quoted = "`'"+$logEntryArr[$i]+"`'"
# replace mention of key in instruction with dictionary reference
$thisInstruction = $thisInstruction -replace $key , $quoted
}
# process rule from command line against dictionary
$answer = Invoke-Expression $thisInstruction
if ($answer) {
echo $_
}
}
}
}

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,}$'

Check if a string contains any substring in an array in PowerShell

I am studying PowerShell. I want to know how to check if a string contains any substring in an array in PowerShell. I know how to do the same in Python. The code is given below:
any(substring in string for substring in substring_list)
Is there similar code available in PowerShell?
My PowerShell code is given below.
$a = #('one', 'two', 'three')
$s = "one is first"
I want to validate $s with $a. If any string in $a is present in $s then return True. Is it possible in PowerShell?
Using the actual variables in the question for simplicity:
$a = #('one', 'two', 'three')
$s = "one is first"
$null -ne ($a | ? { $s -match $_ }) # Returns $true
Modifying $s to not include anything in $a:
$s = "something else entirely"
$null -ne ($a | ? { $s -match $_ }) # Returns $false
(That's about 25% fewer characters than chingNotCHing's answer, using the same variable names of course :-)
($substring_list | %{$string.contains($_)}) -contains $true
should strictly follow your one-liner
For PowerShell ver. 5.0+
Instead of,
$null -ne ($a | ? { $s -match $_ })
try this simpler version:
$q = "Sun"
$p = "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
[bool]($p -match $q)
This returns $True if substring $q is in the array of string $p.
Another Example:
if ($p -match $q) {
Write-Host "Match on Sun !"
}
Michael Sorens' code answer works best to avoid the pitfall of partial substrings matching. It just needs a slight regex modification. If you have the string $s = "oner is first", the code would still return true since 'one' would match 'oner' (a match in PowerShell means the second string contains the first string.
$a = #('one', 'two', 'three')
$s = "oner is first"
$null -ne ($a | ? { $s -match $_ }) # Returns $true
Add some regex for word boundary '\b' and the r on 'oner' will now return false:
$null -ne ($a | ? { $s -match "\b$($_)\b" }) # Returns $false
(I know it's an older thread but at least I might help people looking at this in the future.)
Any response given that uses -match will produce incorrect answers.
Example: $a -match $b will produce false negatives if $b is "."
A better answer would be to use .Contains - but it's case sensitive so you'd have to set all strings to upper or lower case before comparing:
$a = #('one', 'two', 'three')
$s = "one is first"
$a | ForEach-Object {If ($s.toLower().Contains($_.toLower())) {$True}}
Returns $True
$a = #('one', 'two', 'three')
$s = "x is first"
$a | ForEach-Object {If ($s.toLower().Contains($_.toLower())) {$True}}
Returns nothing
You could tweak it to return $True or $False if you'd want, but IMO the above is easier.
I'm amazed that in 6 years nobody has given this more simple and readable answer
$a = #("one","two","three")
$s = "one1 is first"
($s -match ($a -join '|')) #return True
So simply implode the array into a string using vertical bar "|" , as this is the alternation (the "OR" operator) in regex.
https://www.regular-expressions.info/alternation.html
https://blog.robertelder.org/regular-expression-alternation/
Also keep in mind that the accepted answer will not search for exact match. If you want exact match you can use the \b (word boundary)
https://www.regular-expressions.info/wordboundaries.html
$a = #("one","two","three")
$s = "one1 is first"
($s -match '\b('+($a -join '|')+')\b') #return False
It is possible to select a subset of strings containing any of the strings like this:
$array = #("a", "b")
$source = #("aqw", "brt", "cow")
$source | where {
$found = $FALSE
foreach($arr in $array){
if($_.Contains($arr)){
$found = $TRUE
}
if($found -eq $TRUE){
break
}
}
$found
}
One way to do this:
$array = #("test", "one")
$str = "oneortwo"
$array|foreach {
if ($str -match $_) {
echo "$_ is a substring of $str"
}
}