Loop over array - powershell

I need a piece of powershell-code to search and replace a certain string inside a text-file. In my example, I want to replace 23-06-2016' with '24-06-2016'. The script below does this job:
$original_file = 'file.old'
$destination_file = 'file.new'
(Get-Content $original_file) | Foreach-Object {
$_ -replace '23-06-2016', '24-06-2016' `
} | Out-File -encoding default $destination_file
As the search / replace string change I want to loop over an array of dates which might look like this:
$dates = #("23-06-2016","24-06-2016","27-06-2016")
I tried use the
$original_file = 'file.old'
$destination_file = 'file.new'
foreach ($date in $dates) {
(Get-Content $original_file) | Foreach-Object {
$_ -replace 'date', 'date++' `
} | Out-File -encoding default $destination_file
}
In a first step, the date '23-06-2016' should be replaced by '24-06-2016' and in a second step, the date '24-06-2016' should be replaced by '27-06-2016'.
As my script is not working I am seeking for some advice.

You are using $date as your instance variable in your foreach loop but then referencing it as 'date', which is just a string. Even if you used '$date' it would not work because single-quoted strings do not expand variables.
Further, $date is not a number, so date++ would not do anything even it were referenced as a variable $date++. Further still, $var++ returns the original value before incrementing, so you would be referencing the same date (as opposed to the prefix version ++$var).
In a foreach loop, it's not very practical to refer to other elements, in most cases.
Instead, you could use a for loop:
for ($i = 0; $i -lt $dates.Count ; $i++) {
$find = $dates[$i]
$rep = $dates[$i+1]
}
This isn't necessarily the most clear way to do it.
You might be better off with a [hashtable] that uses the date to find as a key, and the replacement date as the value. Sure, you'd be duplicating some dates as value and key, but I think I'd rather have the clarity:
$dates = #{
"23-06-2016" = "24-06-2016"
"24-06-2016" = "27-06-2016"
}
foreach ($pair in $dates.GetEnumerator()) {
(Get-Content $original_file) | Foreach-Object {
$_ -replace $pair.Key, $pair.Value
} | Out-File -encoding default $destination_file
}

Related

How do I add 1 to a variable each time it replaces a specific string

What I am trying to do is add 1 to $b each time it replaces the word hello with modal$b so it should look like modal1, modal2 etc.
$a=1
$b=1
$original_file = 'C:\Users\me\Desktop\2.txt'
$destination_file = 'C:\Users\me\Desktop\4.txt'
do {
(Get-Content $original_file) | ForEach-Object {
$_ -replace "hello", "modal$b"`
} | add-Content $destination_file
$a++
}
until ($a -gt 1)
Any help would be greatly appreciated!
thanks,
Sarash
You could do it with just a ForEach-Object loop, there doesn't seem to be a need for the Do loop. Following what you already have, you can increase your variable like below.
$original_file = 'C:\Users\me\Desktop\2.txt'
$destination_file = 'C:\Users\me\Desktop\4.txt'
$b = 0
Get-Content $original_file | ForEach-Object {
$_ -replace 'hello', "modal$(($b++))"
} | Out-File $destination_file
The following would work too, using a Regex.Replace with a Script Block, this example would require to use the -Raw switch on Get-Content. It's important to note that using this method, the pattern is case sensitive (i.e.: 'Hello' will not match 'hello'), if you want it to be case-insensitive you could use the (?i) flag: '(?i)hello'.
[ref]$ref = 0
[regex]::Replace((Get-Content $original_file -Raw), 'hello', {
"modal$(($ref.Value++))"
}) | Out-File $destination_file
Replacement with a script block was implemented in PowerShell 6 (Core), thanks Mathias R. Jessen for the hard work :)
Above example, if you have PS Core, would be replaced by:
[ref]$ref = 0
(Get-Content $original_file -Raw) -replace 'hello', {
"modal$(($ref.Value++))"
} | Out-File $destination_file
And there wouldn't be a need for the (?i) flag since -replace is already case-insensitive.

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

Dynamically replacing file content

I have to read one properties file (let's say prop.txt) and update it dynamically.
Content looks like this.
server.names=xyz[500],server2[500],test[500]
I wanted to replace the content anything after server.names= with correct values, e.g.:
server1.company.com[500],server2.company.com[500],server3.company.com[500]
I tried below command but it is replacing server.names=. I want to replace the values of server.names=
(Get-Content $path).Replace("server.names=",$NewServerNames) | Set-Content $path
Any idea how to replace the value of server.names=?
You were close, but your syntax is off. This solution utilizes regex to capture the original key:
$Pattern = 'server\.names='
Get-Content -Path $Path |
ForEach-Object {
If ($_ -match $Pattern)
{
$_ -replace "($Pattern).*","$1$NewServerNames"
}
Else
{
$_
}
} |
Set-Content -Path $Path

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

Using Context in Powershell Select-String

I have a script that searches for a series of strings (stored in a txt file) in the contents of files in a directory. I would like to modify it to also list the text around the string found (these are regular strings, not regex expressions). I played around a lot and it seems like I need to use -Context, but I am not sure how to get the text from that.
Also, the files I am searching may not have linefeeds, so if it could just get the xx characters before and after the search term, that would be better.
Here's what I have so far (I omitted the looping though files parts):
$result = Get-Content $file.FullName | Select-String $control -quiet
If ($result -eq $True)
{
$match = $file.FullName
"Match on string : $control in file : $match" | Out-File $output -Append
Write-host "Match on string : $control in file : $match"
}
If it could write the context, that would be perfect. Seems like I need to use $_Matches, but not sure how.
If $control is just a regular string, can you turn it into a regular expression?
$n = 3
$re = "(.{0,$n})(" + [Regex]::Escape($control) + ")(.{0,$n})"
$result = (Get-Content $file.FullName) -match $re
With this, the $matches hashtable should give you access to the $n characters before and after the match:
if ($result.Length -gt 0) {
echo "Before: $($matches[1])"
echo "After: $($matches[3])"
}
Here is what I have now and it seems to work:
$regex = "[\s\S]{0,$ContextChars}$SearchTerm[\s\S]{0,$ContextChars}"
$results = Get-Content $file.FullName | Select-String -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value }
if ($results)
{
foreach($result in $results)
{
$display = $result
"File: $file Match ---$display---"
}
}
The only thing I wish I had but don't know how to get it is the line number the match is found on.