I have the following script which I try to run on various html files
$files = $args[0];
$string1 = $args[1];
$string2 = $args[2];
Write-Host "Replace $string1 with $string2 in $files";
gci -r -include "$files" |
foreach-object { $a = $_.fullname; ( get-content $a ) |
foreach-object {
$_ -replace "%string1" , "$string2" |
set-content $a
}
}
in an attempt to edit this line found in all the files.
<tr><td>TestCase</td></tr>
I call the script from powershell like this (it's called replace.ps1)
./replace *.html sampleTest myNewTest
but instead of changing sampleTest.html to myNewTest.html
it deletes everything in the doc except for the last line,
leaving all of the files like so:
/html
in fact, no matter what arguments I pass in this seems to happen.
Can anyone explain this/help me understand why it's happening?
Your loop structure is to blame here. You need to have the Set-Content located outside the loop. Your code is overwriting the file at every pass.
....
foreach-object { $a = $_.fullname; ( get-content $a ) |
foreach-object {
$_ -replace "$string1" , "$string2" |
} | set-content $a
}
It also might have been a typo but you had "%string1" before which, while syntactically correct, what not what you intended.
Could also have used Add-Content but that would mean you have to erase the file first. set-content $a used at the end of the pipe is more intuitive.
Your example is not one that uses regex. You could have used $_.replace($string1,$string2) with the same results.
Related
I've searched all over including here at StackOverFlow and I cannot seem to find the solution I am needing help with. Here is my issue.
Lets say in File1.txt I have the following (no spaces between each line)
\\Serv02\LOC6\Client\726C30\032383\2200018023.pdf
\\Serv02\LOC6\Client\726C30\032383\2200718091.pdf
\\Serv02\LOC6\Client\726C30\030684\2300309040.pdf
\\Serv02\LOC6\Client\726C30\031274\2300429971.pdf
File2.txt will have the same information, however, I am needing to add a 1 right before the .pdf for each one (within file2.txt)
Example:
\\Serv02\LOC6\Client\726C30\032383\22000180231.pdf
I can easily update file2.txt using a RegEx statement, however it's only updating the contents based on that RegEx statement.
File2.txt will have a lot more data in it than file1.txt (more of the exact type of information). I am only needing to update file2.txt adding in the 1 right before .pdf BASED on what is in file1.txt
Here is the code I am using but as you can see it does not read file1.txt at all, I'm just using a RegEx statement to update file2.txt adding in the 1 before .pdf (the code below works to add in the 1 before .pdf, but I'm not iterating through file1.txt)
clear-host
set-location c:\temp
$File = "C:\Temp\file1.txt"
$FileZ = "C:\Temp\file2.txt"
$File2 = (Get-ChildItem $fileZ) | Select -ExpandProperty BaseName
$regex01 = '(\\Serv02\LOC6\Client\726C30\\d{1,6}\\d{1,10})(.pdf)$'
get-content $fileZ | % { $_ -replace $regex01, '${1}1${2}' -join "`r`n" } | out-file -Encoding default "c:\Temp\$File2.txt"
start-sleep -Seconds 2
$NewMRC = Get-ChildItem "$file2.txt" | Select -ExpandProperty Name
Get-ChildItem $NewMRC | rename-item -NewName {$_.Name -replace ".txt",".MRC2"}
If file1.txt had another line that didn't match up to the RegEx as shown above, file2.txt would not be updated with that line
\\Serv03\LOC7\Client\780D30\031456\8675309123.pdf
I hope I have explained this well enough. I'm not new to PowerShell but I am far from an expert. Any assistance is greatly appreciated.
I've modified your code as follows. The approach is read the content of File1.txt and store it in a variable. Then iterate on each line of File2.txt to check it against the regex as well as if that line is present in file1 content. If yes then replace it with whatever you want. Output this to a .tmp file in append mode. Once all the lines in File2.txt are processed, then replace it with .tmp file.
clear-host
set-location c:\temp
$File = "file1.txt"
$FileZ = "file2.txt"
# PS2
$File1 = get-content $File | Out-String
# PS3
# $File1 = get-content $File -Raw
$File2 = (Get-ChildItem $fileZ) | Select -ExpandProperty BaseName
if( test-path "$File2.tmp" ) { remove-item "$File2.tmp" }
$regex01 = '(\\\\Serv02\\LOC6\\Client\\726C30\\\d{1,6}\\\d{1,10})(.pdf)$'
get-content $fileZ |% {
$line = $_
$find = $line -replace '\\','\\'
if ( ($line -match $regex01) -AND ( $File1 -match $find ) ) {
$line -replace $regex01,'${1}1${2}' -join "`r`n"
} else {
$line
}
} | out-file "$File2.tmp" -append
remove-item "$File2.txt"
rename-item "$File2.tmp" "$File2.txt"
#start-sleep -Seconds 2
#$NewMRC = Get-ChildItem "$file2.txt" | Select -ExpandProperty Name
#Get-ChildItem $NewMRC | rename-item -NewName {$_.Name -replace ".txt",".MRC2"}
Notes:
The last 3 lines of your code doesn't seem to be related to your problem statement. So I've commented those lines.
$find = $line -replace '\\','\\': We are replacing single backslash \ with double backslash \\. But in the first parameter to -replace it must be escaped and in second param it must NOT be. So, even though they look same, they are interpreted differently.
One way to do this: Retrieve file content of first file into an array, then retrieve content of second file. For each line in second file: If first file's content has a line matching the current line, output modified line; otherwise, just output the current line.
$pattern = '(\\{2}(?:[^\\]+\\)+)([^\\\.]+)(\.pdf)'
$file1Content = Get-Content "file1.txt"
Get-Content "file2.txt" | ForEach-Object {
if ( $file1Content -contains $_ ) {
$_ | Select-String $pattern | ForEach-Object {
"{0}{1}1{2}" -f
$_.Matches[0].Groups[1].Value,
$_.Matches[0].Groups[2].Value,
$_.Matches[0].Groups[3].Value
}
}
else {
$_
}
}
First match group ($_.Matches[0].Groups[1].Value) is \\servername\sharename\path, second match group is filename without extension, and third match group is the file extension.
For every file in a directory I wish to remove lines that match a regular expression (beginning with |B for example) using powershell.
I think I can do this via Get-ChildItem on the directory, foreach-object, get-content and some sort of if -match but I'm really struggling to fit it all together.
Any help would be massively appreciated. This is the first time I've ever written a powershell script.
Something like the below should get you in the right direction
$files = Get-ChildItem "C:\your\dir"
foreach ($file in $files) {
$c = Get-Content $file.fullname | where { $_ -notmatch "^\|B" }
$c | Set-Content $file.fullname
}
I looked into doing this for one file and I really like the solution powershell offers:
Get-Content test.txt | ForEach-Object { $_ -replace "foo", "bar" } | Set-Content test2.txt
Is there a way I can do this to get the content of a list of files, perform the same search and replace, and produce a second set of files?
This accomplishes what I want:
(Get-Content *.txt) | ForEach-Object { $_ -replace "foo", "bar" } | Set-Content *.txt
Give this a try --
dir *.txt | % {[IO.File]::ReadAllText($_).Replace('bar', 'test') | sc $_}
I use the below pipeline to read a file and replace a line in it and save it to another file, but found that the string in target file is not replaced, it's still the old one.
original line is : name-1a2b3c4d
new line should be: name-6a5e4r3h
(Get-Content "test1.xml") | ForEach-Object {$_ -replace '^name-.*$', "name-6a5e4r3h"} | Set-Content "test2.xml"
Anything missing there?
One thing you're missing is that the -replace operator works just fine on an array, which means you don't need that foreach-object loop at all:
(Get-Content "test1.xml") -replace '^name-.*$', 'name-6a5e4r3h' | Set-Content test2.xml
You're not changing the $_ variable.
You might try:
$lines = Get-Content $file
$len = $lines.count
for($i=0;$i-lt$len;$i++){
$lines[$i] = $lines[$i] -replace $bad, $good
}
$lines > $outfile
I have a text file containing lines of data. I can use the following powershell script to extract the lines I'm interested in:
select-string -path *.txt -pattern "subject=([A-Z\.]+),"
Some example data would be:
blah blah subject=THIS.IS.TEST.DATA, blah blah blah
What I want is to be able to extract just the actual contents of the subject (i.e. the "THIS.IS.TEST.DATA" string). I tried this:
select-string -path *.txt -pattern "subject=([A-Z\.]+)," | %{ $_.Matches[0] }
But the "Matches" property is always null. What am I doing wrong?
I don't know why your version doesn't work. It should work. Here is an uglier version that works.
$p = "subject=([A-Z\.]+),"
select-string -path *.txt -pattern $p | % {$_ -match $p > $null; $matches[1]}
Explanation:
-match is a regular expression matching operator:
>"foobar" -match "oo.ar"
True
The > $null just suppresses the True being written to the output. (Try removing it.) There is a cmdlet that does the same thing whose name I don't recall at the moment.
$matches is a magic variable that holds the result of the last -match operation.
In PowerShell V2 CTP3, the Matches property is implemented. So the following will work:
select-string -path *.txt -pattern "subject=([A-Z\.]+)," | %{ $_.Matches[0].Groups[1].Value }
Yet another option
gci *.txt | foreach { [regex]::match($_,'(?<=subject=)([^,]+)').value }
There is a much simpler alternative to select-string that will work better.
In powershell,
$sample="blah blah subject=THIS.IS.TEST.DATA, blah blah blah"
$sample -match "subject=([A-Z\.]+),"
$matches[1] will have the substring you are looking for.
This works on Windows 10.0.16299 version
Having learnt a lot from all the other answers I was able to get what I want using the following line:
gci *.txt | gc | %{ [regex]::matches($_, "subject=([A-Z\.]+),") } | %{ $_.Groups[1].Value }
This felt nice as I was only running the regex once per line and as I was entering this at the command prompt it was nice not to have multiple lines of code.
The problem with the code you are typing is that select-string does not pass down the actual Regex object. Instead it passes a different class called MatchInfo which does not have the actual regex matches information.
If you only want to run the regex once, you will have to roll you're own function which isn't too difficult.
function Select-Match() {
param ($pattern = $(throw "Need a pattern"),
$filePath = $(throw "Need a file path") )
foreach ( $cur in (gc $filePath)) {
if ( $cur -match $pattern ) {
write-output $matches[0];
}
}
}
gci *.txt | %{ Select-Match "subject=([A-Z\.]+)," $_.FullName }
The Select-String command seems to return a MatchInfo variable and not a "string" variable.
I spent several hours finding this out on forums and official website with no luck.
I'm still gathering info.
A way around this is to declare explicitly a string variable to hold the result returned from the Select-String, from your example:
[string] $foo = select-string -path *.txt -pattern "subject=([A-Z.]+),"
The $foo variable is now a string and not a MatchInfo object.
Hope this helps.
ps5 powershell version 5 string strings manipulation
Another variation, matching 7 digits in a string
echo "123456789 hello test" | % {$_ -match "\d{7}" > $null; $matches[0]}
returns: 1234567