I have a list of numbers coming in and I need to declare that multiple variables as true based on the first character of a set of items in a list.
This is in no way an actual example, but here's a scenario. Here are a set of items:
A1
A2
A4
B3
B5
I need the script to look through this list and if it any of the first characters start with an A then $A = true. And/or if it finds any starting with B then $B = true. And if it doesn't find any that start with C then $C = false.
I need the script to check for each letter and then pretty much report back if it found any values that started with that letter.
I know this is kind of a confusing post, so please reply back if there are any questions.
Initialize your variables as $false:
$A = $false
$B = $false
$C = $false
...
Then iterate over your list and use a switch statement to set variables to $true if a list item starts with a particular character:
$list | ForEach-Object {
switch -wildcard ($_) {
'a*' { $A = $true }
'b*' { $B = $true }
'c*' { $C = $true }
...
}
}
Or, as Bruce Payette pointed out in the comments, just pass the list into the switch statement directly:
switch -wildcard ($list) {
'a*' { $A = $true }
'b*' { $B = $true }
'c*' { $C = $true }
...
}
Ansgar was faster, so a different approach
$List = ("A1","A2","A4","B3","B5")
"A","B","C" | ForEach-Object {
"Checking {0}" -f $_
if ($List -match "^$_"){
"$($_):True"
Set-Variable -Name "$_" -Value $True
} else {
"$($_):False"
Set-Variable -Name "$_" -Value $False
}
}
Sample output:
Checking A
A:True
Checking B
B:True
Checking C
C:False
I'll give you another option, one where you don't have to manually type out 26 cases.
ForEach($Char in (65..90|%{[char]$_})){
Set-Variable -Name $Char -Value ([bool]($List -match "^$char"))
"$Char found: " + (Get-Variable $Char).Value
}
This iterates A-Z, and checks if anything in the list matches that for the first letter, and evaluates the result as True/False. Then it assigns that result to a variable for that character, and displays the results.
Related
I have to replace multiple strings with the same pattern, and several strings are on the same line. The replacement value should be incremental. I need to match and replace only the pattern as in the example, not requesId, nor messageId.
Input:
<requestId>qwerty-qwer12-qwer56</requestId>Ace of Base Order: Q2we45-Uj87f6-gh65De<something else...
<requestId>zxcvbn-zxcv4d-zxcv56</requestId>
<requestId>1234qw-12qw9x-123456</requestId> Stevie Wonder <messageId>1234qw-12qw9x-123456</msg
reportId>plmkjh8765FGH4rt6As</msg:reportId> something <keyID>qwer1234asdf5678zxcv0987bnml65gh</msgdc
The desired output should be:
<requestId>Request-1</requestId>Ace of Base Order: Request-2<something else...
<requestId>Request-3</requestId>
<requestId>Request-4</requestId> Stevie Wonder <messageId>Request-4</msg
reportId>ReportId-1</msg:reportId> something <keyId>KeyId-1</msg
The regex finds all matching values but I cannot make the loop and replace these values. The code I am trying to make work is:
#'
<requestId>qwerty-qwer12-qwer56</requestId>Ace of Base Order: Q2we45-Uj87f6-gh65De<something else...
<requestId>zxcvbn-zxcv12-zxcv56</requestId>
<requestId>1234qw-12qw12-123456</requestId> Stevie Wonder <messageId>1234qw-12qw12-123456</msg
reportId>plmkjh8765FGH4rt6As</msg:reportId> something <keyID>qwer1234asdf5678zxcv0987bnml65gh</msgdc
'# | Set-Content $log -Encoding UTF8
$requestId = #{
Count = 1
Matches = #()
}
$tmp = Get-Content $log | foreach { $n = [regex]::matches((Get-Content $log),'\w{6}-\w{6}-\w{6}').value
if ($n)
{
$_ -replace "$n", "Request-$($requestId.count)"
$requestId.count++
} $_ }
$tmp | Set-Content $log
You want Regex.Replace():
$requestId = 1
$tmp = Get-Content $log |ForEach-Object {
[regex]::Replace($_, '\w{6}-\w{6}-\w{6}', { 'Request-{0}' -f ($script:requestId++) })
}
$tmp |Set-Content $log
The script block will run once per match to calculate the substitue value, allowing us to resolve and increment the $requestId variable, resulting in the consecutive numbering you need.
You can do this for multiple patterns in succession if necessary, although you may want to use an array or hashtable for the individual counters:
$counters = { requestId = 1; keyId = 1 }
$tmp = Get-Content $log |ForEach-Object {
$_ = [regex]::Replace($_, '\w{6}-\w{6}-\w{6}', { 'Request-{0}' -f ($counters['requestId']++) })
[regex]::Replace($_, '\b\w{32}\b', { 'Key-{0}' -f ($counters['keyId']++) })
}
$tmp |Set-Content $log
If you want to capture and the mapping between the original and the new value, do that inside the substitution block:
$translations = #{}
# ...
[regex]::Replace($_, '\w{6}-\w{6}-\w{6}', {
# capture value we matched
$original = $args[0].Value
# generate new value
$substitute = 'Request-{0}' -f ($counters['requestId']++)
# remember it
$translations[$substitute] = $original
return $substitute
})
In PowerShell 6.1 and newer versions, you can also do this directly with the -replace operator:
$requestId = 0
$tmp = Get-Content $log |ForEach-Object {
$_ -replace '\w{6}-\w{6}-\w{6}', { 'Request-{0}' -f ($requestId++) }
}
$tmp |Set-Content $log
I have the following code:
$myString = "Name=Tony;Fee=10;Account=Premium"
$splitString = "$($myString)".Split(";")
$name = $splitString -match "Name=(?<content>.*)"
$acct = $splitString -match "Account=(?<content>.*)"
$name
$acct
Result:
Name=Tony
Account=Premium
How can I get it to return just the value? Example:
Tony
Premium
Thanks in advance
Combine -split, the string splitting operator, with a switch statement and its -Regex switch:
$name, $acct =
switch -Regex ("Name=Tony;Fee=10;Account=Premium" -split ';') {
'^(?:Name|Account)=(?<content>.*)' { $Matches['content'] }
}
Here's another approach using -Split and taking advantage of the text's format using ConvertFrom-StringData
$name, $acct = "Name=Tony;Fee=10;Account=Premium" -split ';' |
ConvertFrom-StringData | ForEach-Object {$_.name,$_.Account}
I would use -split here to to construct a hashtable of key value pairs:
$myString = "Name=Tony;Fee=10;Account=Premium"
# Create hashtable
$ht = #{}
# Spit string by ';' and loop over each item
foreach ($item in $myString -split ';') {
# Split item by '=' to get key value pair
$pair = $item -split '='
# Set key value pair into hashtable
$ht[$pair[0]] = $pair[1]
}
# Output values you want
$ht.Name
$ht.Account
# Tony
# Premium
Which also is beneficial if you need to lookup other values.
There are about ten lines of data. For each line of data I want to indicate whether that line contains numerals.
How can I print out "yes, this line has numerals" or "no, this line has no numerals" for each and every line, exactly once?
output:
thufir#dur:~/flwor/csv$
thufir#dur:~/flwor/csv$ pwsh import.ps1
no digits
Name
----
people…
thufir#dur:~/flwor/csv$
code:
$text = Get-Content -Raw ./people.csv
[array]::Reverse($text)
$tempAttributes = #()
$collectionOfPeople = #()
ForEach ($line in $text) {
if($line -notmatch '.*?[0-9].*?') {
$tempAttributes += $line
Write-Host "matches digits"
}
else {
Write-Host "no digits"
$newPerson = [PSCustomObject]#{
Name = $line
Attributes = $tempAttributes
}
$tempAttributes = #()
$collectionOfPeople += $newPerson
}
}
$collectionOfPeople
data:
people
joe
phone1
phone2
phone3
sue
cell4
home5
alice
atrib6
x7
y9
z10
The only reason I'm printing "digits" or "no digits" is as a marker to aid in building the object.
You can use the following:
switch -regex -file people.csv {
'\d' { "yes" ; $_ }
default { "no"; $_ }
}
\d is a regex character matching a digit. A switch statement with -regex allows for regex expressions to be used for matching text. The default condition is picked when no other condition is met. $_ is the current line being processed.
switch is generally faster than Get-Content for line by line processing. Since you do want to perform certain actions per line, you likely don’t want to use the -Raw parameter because that will read in all file contents as one single string.
# For Reverse Output
$output = switch -regex -file people.csv {
'\d' { "yes" ; $_ }
default { "no"; $_ }
}
$output[($output.GetUpperBound(0))..0)]
I have a 4.ps1 file that looks like this
#akabradabra
$one = 'o'
#bibi
$two = 't'
$three = 't' #ok thr
#four
$four = 'four'
And a 3.ps1 file that looks like this
#akabradabra
$one = 'one'
#biblibablibo
$two = 'two'
$three = 'three' #ok threer
My goal is to read the key-value pair from 4.ps1 and update the values in 3.ps1 and if new key-value pairs are introduced in 4.ps1, simply append them to the end of 3.ps1.
My idea is to use string functions such as .Split('=') and .Replace(' ', '') to extract the keys and if the keys match, replace the entire line in 3.ps1 with the one found in 4.ps1
I know that using Get-Variable might does the trick and also it will be a lot easier to work with the data if I convert all the key-value pairs into a .xml or a .json file but can anyone please show me how can I make it work in my own silly way?
Here is my code to do so
# Ignore this function, this is used to skip certain key-value pairs
#----------------------------------------------------------------------------
Function NoChange($something) {
switch ($something) {
'$CurrentPath' {return $true}
'$pathToAdmin' {return $true}
'$hostsPathTocompare' {return $true}
'$logs' {return $true}
'$LogFile' {return $true}
default {return $false}
}
}
#----------------------------------------------------------------------------
$ReadFromVARS = Get-Content $PSScriptRoot\4.ps1
$WriteToVARS = Get-Content $PSScriptRoot\3.ps1
foreach ($oldVar in $ReadFromVARS) {
if (('' -eq $oldVar) -or ($oldVar -match '\s*#+\w*')) {
continue
} elseif ((NoChange ($oldVar.Split('=').Replace(' ', '')[0]))) {
continue
} else {
$var = 0
#$flag = $false
while ($var -ne $WriteToVARS.Length) {
if ($WriteToVARS[$var] -eq '') {
$var += 1
continue
} elseif ($WriteToVARS[$var] -match '\s*#+\w*') {
$var += 1
continue
} elseif ($oldVar.Split('=').Replace(' ', '')[0] -eq $WriteToVARS[$var].Split('=').Replace(' ', '')[0]<# -and !$flag#>) {
$oldVar
$WriteToVARS.replace($WriteToVARS[$var], $oldVar) | Set-Content -Path $PSScriptRoot\3.ps1 -Force
break
#$var += 1
#$flag = $true
} elseif (<#!$flag -and #>($var -eq $WriteToVARS.Length)) {
Add-Content -Path $PSScriptRoot\3.ps1 -Value $oldVar -Force
$var += 1
} else {
$var += 1
}
}
}
}
I did not ran into any errors but it only updated one key-value pair ($two = t) and it did not append new key-value pairs at the end. Here is the result I got
#akabradabra
$one = 'one'
#biblibablibo
$two = 't'
$three = 'three' #ok threer
If I understand your question correctly, I think Dot-Sourcing is what you're after.
The PowerShell dot-source operator brings script files into the current session scope. It is a way to reuse script. All script functions and variables defined in the script file become part of the script it is dot sourced into. It is like copying and pasting text from the script file directly into your script.
To make it visible, use Dot-Sourcing to read in the variables from file 3.ps1, show the variables and their values. Next dot-source file 4.ps1 and show the variables again:
. 'D:\3.ps1'
Write-Host "Values taken from file 3.ps1" -ForegroundColor Yellow
"`$one : $one"
"`$two : $two"
"`$three : $three"
"`$four : $four" # does not exist yet
. 'D:\4.ps1'
Write-Host "Values after dot-sourcing file 4.ps1" -ForegroundColor Yellow
"`$one : $one"
"`$two : $two"
"`$three : $three"
"`$four : $four"
The result is
Values taken from file 3.ps1
$one : one
$two : two
$three : three
$four :
Values after dot-sourcing file 4.ps1
$one : o
$two : t
$three : t
$four : four
If you want to write these variables back to a ps1 script file you can:
'one','two','three','four' | Get-Variable | ForEach-Object {
'${0} = "{1}"' -f $_.Name,$_.Value
} | Set-Content 'D:\5.ps1' -Force
Theo's answer provides a easier way to do the same thing
Also, converting your Config files to JSON or XML will make the job lot more easier too
My original idea was to read both 4.ps1 and 3.ps1 ( these are my config files, I only store variables inside and switch statement to help choosing the correct variables ) then overwrite 3.ps1 with all the difference found but I could not get it working so I created a new 5.ps1 and just simply write everything I need to 5.ps1.
Here is my code if you would like to use it for your own project :-)
The obstacles for me were that I had switch statements and certain $variables that I wanted to ignore (in my actual project) so I used some Regex to avoided it.
$ReadFromVARS = Get-Content $PSScriptRoot\4.ps1
$WriteToVARS = Get-Content $PSScriptRoot\3.ps1
New-Item -ItemType File -Path $PSScriptRoot\5.ps1 -Force
Function NoChange($something) {
switch ($something) {
'$CurrentPath' {return $true}
'$pathToAdmin' {return $true}
'$hostsPathTocompare' {return $true}
'$logs' {return $true}
'$LogFile' {return $true}
default {return $false}
}
}
$listOfOldVars = #()
$switchStatementStart = "^switch(\s)*\(\`$(\w)+\)(\s)*(\n)*\{"
$switchStatementContent = "(\s)*(\n)*(\t)*\'\w+(\.\w+)+\'(\s)*\{(\s)*\`$\w+(\s)*=(\s)*\#\((\s)*\'\w+(\.\w+)+\'(\s)*(,(\s)*\'\w+(\.\w+)+\'(\s)*)*\)\}"
$switchStatementDefault = "(\s)*(\n)*(\t)*Default(\s)*\{\`$\w+(\s)*=(\s)*\#\((\s)*\'\w+(\.\w+)+\'(\s)*(,(\s)*\'\w+(\.\w+)+\'(\s)*)*\)\}\}"
$switchStatementEnd = "(\s)*(\n)*(\t)*\}"
foreach ($oldVar in $ReadFromVARS) {
if (('' -eq $oldVar) -or ($oldVar -match '^#+\w*')) {
continue
} elseif ((NoChange $oldVar.Split('=').Replace(' ', '')[0])) {
continue
} else {
$var = 0
while ($var -ne $WriteToVARS.Length) {
if ($WriteToVARS[$var] -eq '') {
$var += 1
continue
} elseif ($WriteToVARS[$var] -match '^#+\w*') {
$var += 1
continue
} elseif ($oldVar -match $switchStatementStart -or $oldVar -match $switchStatementContent -or $oldVar -match $switchStatementDefault -or $oldVar -match $switchStatementEnd) {
Add-Content -Path "$PSScriptRoot\5.ps1" -Value $oldVar -Force
$listOfOldVars += ($oldVar)
break
} elseif ($oldVar.Split('=').Replace(' ', '')[0] -eq $WriteToVARS[$var].Split('=').Replace(' ', '')[0]) {
Add-Content -Path "$PSScriptRoot\5.ps1" -Value $oldVar -Force
$listOfOldVars += ($oldVar.Remove(0,1).Split('=').Replace(' ', '')[0])
break
} else {
$var += 1
}
}
}
}
foreach ($newVar in $WriteToVARS) {
if ($newVar.StartsWith('#') -or $newVar -eq '') {
continue
} elseif ($newVar -match $switchStatementStart -or $newVar -match $switchStatementContent -or $newVar -match $switchStatementDefault -or $newVar -match $switchStatementEnd) {
} elseif (($newVar.Remove(0,1).Split('=').Replace(' ', '')[0]) -in $listOfOldVars) {
continue
} else {
Add-Content -Path "$PSScriptRoot\5.ps1" -Value $newVar -Force
}
}
I have this perl code I am trying to convert to powershell.
foreach my $f ( #file_list ) {
if( $f =~ /^([\d]+)_${base_ver}_([\d]{4})\.zip$/i ){
$pfile = $f;
( $pnum, $pdate ) = ( $pfile =~ /^([\d]+)_${base_ver}_([\d]{4})\.zip$/i ); # need to convert
( $pmon, $pyear ) = ( $pdate =~ /^(\d\d)(\d\d)$/ ); # need to convert
if( ($patch_nbr == 0) || ($patch_nbr == $pnum) ) {
$fcnt++;
}
}
}
I've converted most of it here..
$file_list = Get-ChildItem -force $base_dir
$file_list | foreach-object {
if($_ -match "/^([\d]+)_${base_ver}_([\d]{4})\.zip$/i"){
$pfile = $_
if($patch_nbr -eq 0 -or $pacth_nbr -eq $pnum){
$fcnt++
}
}
}
Not quite sure how to convert the two variables that equal the regex or if there is a better way to convert the perl code to powershell than what I already have.
The [mode]/pattern/[replace/][options] syntax from perl doesn't apply to regex in PowerShell.
Thus, your pattern
/^([\d]+)_${base_ver}_([\d]{4})\.zip$/i
becomes
^([\d]+)_${base_ver}_([\d]{4})\.zip$
(i is unnecessary, -match resolves to -imatch (case-insensitive match) by default)
To capture the number prefix and date, you can use a named capture group ((?<name>pattern)):
^(?<num>[\d]+)_${base_ver}_(?<date>[\d]{4})\.zip$
You can then grab the match from $Matches["name"]:
if($f -match "^(?<num>[\d]+)_${base_ver}_(?<date>[\d]{4})\.zip$"){
$pfile = $f
$pnum = $Matches["num"]
$pdate = $Matches["date"]
$pmon = -join $pdate[0..1]
$pyear = -join $pdate[2..3]
}
You could also change the regex pattern to capture the month and year individually:
if($f -match "^(?<num>[\d]+)_${base_ver}_(?<month>[\d]{2})(?<year>[\d]{2})\.zip$"){
$pfile = $f
$pnum = $Matches["num"]
$pmon = $Matches["month"]
$pyear = $Matches["year"]
}
I would put a Where-Object filter first. That allows you to use the $matches collection in the subsequent ForEach-Object without a second -match in a nested if statement. If you also change the date pattern from (\d{4}) to (\d{2})(\d{2}) you can assign $pnum, $pmon, and $pyear in a single statement. You could also simplify the condition for incrementing $fcnt. Instead of checking if $patch_nbr equals one of two values you could check if it's contained in an array of the two values.
Get-ChildItem -Force $base_dir |
Where-Object { $_ -match '^(\d+)_${base_ver}_(\d{2})(\d{2})\.zip$' } |
ForEach-Object {
$pnum, $pmon, $pyear = $matches[1..3]
if (0, $pnum -contains $patch_nbr) { $fcnt++ }
}
}
Of course, if all you want to do is count the number of files matching a given patch number, you could just do something like this:
$f = Get-ChildItem -Force $base_dir | Where-Object {
$_ -match '^(\d+)_${base_ver}_\d{4}\.zip$' -and
0, $matches[1] -contains $patch_nbr
}
$fcnt = #($f).Count