I saw some post already to do this but I didn't make it work. I would like to replace a string by an other at a specific line number in txt/csv file.
I have one file (that is supposed to update the second one with the new date)
"field1","date1","date2"
****,23/01/2018,03/04/2018
****,22/01/2018,03/04/2018
The second one (that is supposed to be older) :
"field1","date1","date2"
****,20/01/2018,03/04/2018
****,20/01/2018,03/04/2018
My script do the following :
Ask the user for a file to use to update the second asked file.So if there is a line match between some fields of the two files, I would like to update the "date1" by the "date1" of the first file.
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
function Select-FileDialog
{
param([string]$Title,[string]$Directory,[string]$Filter="CSV File (*.csv)|*.csv")
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
$objForm = New-Object System.Windows.Forms.OpenFileDialog
$objForm.InitialDirectory = $Directory
$objForm.Filter = $Filter
$objForm.Title = $Title
$Show = $objForm.ShowDialog()
If ($Show -eq "OK")
{
Return $objForm.FileName
}
Else
{
Write-Error "Operation cancelled by user."
}
}
##Définition des variables
$ErrorActionPreference = "SilentlyContinue"
$dir = split-path -parent $MyInvocation.MyCommand.Definition
$base_file = Select-FileDialog -Title "Select a file" -Directory "$dir"
$bkp_file = "$dir\base.csv.bak"
##Make a backup of initial file
Copy-Item -Path "$final_file" -Destination "$bkp_file"
$final_file = Select-FileDialog -Title "Select a file" -Directory "$dir"
$import = Import-csv $base_file -Delimiter ";"
$tmpFile = "$dir\lineNumberOfMatches.txt"
$tmpFile2 = "$dir\newDates.txt"
foreach ($item in $import){
$source_base = $item.SOURCE
$pays_base = $item.PAYS
$symbo_base = $item.'SYMBO RZO'
$freq_base = $item.FREQUENCE
$periode_base = $item.PERIODE
$affectation_base = $item.AFFECTATION
$rang_base = $item.RANG
$indic_base = $item.INDICATIF
$ancienneSymbo_base = $item.'ANCIENNE SYMBO'
$modulation_base = $item.MODULATION
$datePerception_base = $item.'DATE PERCEPTION'
$dateMAJ_base = $item.'DATE MAJ'
$string = "$source_base;$pays_base;$symbo_base;$freq_base;$periode_base;$affectation_base;$rang_base;$indic_base;$ancienneSymbo_base;$modulation_base"
$match = Get-Content $final_file | Select-String -SimpleMatch "$string"
$new_date = $datePerception_base
echo $match.LineNumber >> $tmpFile #On met les lignes qui matchent et donc à remplacer dans le fichier de base pour changer la valeur plus tard
echo $new_date >> $tmpFile2
}
$i=0
##That's here that I need help
foreach ($line in Get-Content $tmpFile |sort -Unique){
$lineNumber = $line -1
$lineToChange = Get-Content $final_file | Select -Index ($lineNumber)
$old_date = echo $lineToChange | %{ $_.Split(';')[10]; }
$date = Get-Content $tmpFile2 | Select -Index $i
## I have the good results but can't place it at the right line number or overwrite the line
$lineToChange -replace "$old_date", "$date"
$i++
}
rm $tmpFile
rm $tmpFile2
It drives me crazy because I know with shell and sed the problem would be already solved... Thanks for reading and sorry for the long post !
PS : Don't look at the first import, I just take each field of the base file and put it in a variable to concatenate and make my string match with the other file
I finally succeed. I hope it could help someone...
##That's here that I need help
foreach ($line in Get-Content $tmpFile |sort -Unique){
$lineNumber = $line -1
$lineToChange = Get-Content $final_file | Select -Index ($lineNumber)
$old_date = echo $lineToChange | %{ $_.Split(';')[10]; }
$date = Get-Content $tmpFile2 | Select -Index $i
## I have the good results but can't place it at the right line number or overwrite the line
$lineToChange -replace "$old_date", "$date"
$i++
}
Replaced by :
$i=0
foreach ($line in Get-Content $tmpFile |sort -Unique){
$content = Get-Content $final_file
$lineNumber = $line -1
$lineToChange = Get-Content $final_file | Select -Index ($lineNumber)
#echo $lineToChange
$old_date = echo $lineToChange | %{ $_.Split(';')[10]; }
$date = Get-Content $tmpFile2 | Select -Index $i
$new_line = $lineToChange -replace "$old_date", "$date"
echo $new_line
for ($y = 0; $y -le ($content.Length -1); $y++){
if ($y -eq $lineNumber){
$content[$y] = $new_line
Set-Content $final_file $content
}
}
$i++
}
Related
How do I pass $dir and $file, then concatenate them to a single path?
Output:
posh>
posh> pwsh ./worker.ps1
dir is /home/nicholas/powershell/regex a.log
file is
full is /home/nicholas/powershell/regex a.log/
posh>
library:
function SearchFile($dir,$file)
{
"dir is $dir"
"file is $file"
$full = [string]::Concat("full is ",$dir,"/",$file)
$full
#$pattern='(?=.*?foo)(?=.*?bar)'
#$string = Get-Content $full
#$result = $string | Select-String $pattern
#$result
}
worker:
. /home/nicholas/powershell/functions/library.ps1
$dir = "/home/nicholas/powershell/regex"
$file = "a.log"
SearchFile($dir,$file)
I seem to be passing an array of sorts for $dir and nothing is getting assigned for $file in SearchFile as expected.
Can be done in a simpler way :
function SearchFile ($var_1, $var_2) {
$full_path = "$var_1/$var_2"
$full_path # Output --> C:/temp/test.txt
}
$my_dir = "C:/temp"
$my_file = "test.txt"
SearchFile $my_dir $my_file # Do not use coma when calling a Function
I'm currently working on a piece of code that assembles a string to identify an object based on various pieces of information. Some of that information might not always be available and I was wondering if there was clever way to make that assembly easier?
As an example we have the pieces $a, $b and $c that build the final identifier. Out of those $b might be empty and the final string should contain each components separated by a space. One option would be to add the additional space for $b to the string itself like this:
$a = "FirstPart"
$b = " SecondPart"
$c = "FinalPart"
Write-Output "$a$b $c"
#FirstPart SecondPart FinalPart
$b = ""
Write-Output "$a$b $c"
#FirstPart FinalPart
Another option would be to have a conditional (which can get rather complex and lengthy):
$a = "FirstPart"
$b = "SecondPart"
$c = "FinalPart"
if($b -eq ""){
Write-Output "$a $c"
}else{
Write-Output "$a $b $c"
#FirstPart SecondPart FinalPart
}
$b = ""
if($b -eq ""){
Write-Output "$a $c"
#FirstPart FinalPart
}else{
Write-Output "$a $b $c"
}
What actually would be quite need would be to use -join or maybe -f to get that conditional space if $b is not empty. Is there any way to do this or maybe another alternative? ($a,$b,$c) -join ' ' results in a double space if $b is empty.
$a = "FirstPart"
$b = "SecondPart"
$c = ""
$e = "last"
#put in array, and filter empty
$arr = ($a, $b, $c, $e) | ? { $_ }
#print in space separed
Write-Output "$arr"
I have written a Powershell script that receives N number of characters that have to be replaced by another given character from a user. For example, the user wants to replace (, ), and -. The user enters the characters that have to be replaced through this loop:
#Declare array for special characters to replace
$replaceChars = #( )
#Instruct the user about entering characters to replace
Write-Host
Write-Host "Please enter what you would like to replace in your names. This can contain one or more characters. Example: (2) or ^ or _" -ForegroundColor Cyan
Write-Host "Enter -1 to end your input and continue." -ForegroundColor Cyan
#Get values until sentinal is passed
$input = "" #input from user declared to blank
#loop for special characters
while($input -ne -1) {
#get the input from the user
$input = Read-Host -Prompt "Enter what you would like to replace (-1 to finish)"
#if the input isn't sentinal, put it in the replaceChars array
if($replaceChars.Length -gt 0 -and $input -eq -1) {
Write-Host
Write-Host "Your entries have been stored." -ForegroundColor Green
} #end-if
elseif($replaceChars.Length -eq 0 -and $input -eq -1) {
Write-Host
Write-Host "ERROR: You must enter at least one character to continue." -ForegroundColor Red
$input = $NULL
}
elseif($input -eq '') {
Write-Host
Write-Host "Invalid entry. Entry cannot be blank, please enter a value." -ForegroundColor Red
}#end-elseif
elseif($input.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -ge 0) {
Write-Host
Write-Host "Invalid entry. File names cannot contain / \ : * ? `" < > |" -ForegroundColor Red
}#end-elseif
else {
$replaceChars += $input
} #end-else
}#end-while
The user then enters the replacement character through this code:
#Get the char to replace to
Write-Host
Write-Host "Please enter what you want to replace the selected characters with. Leave blank and hit enter to delete the old characters." -ForegroundColor Cyan
$newChar = Read-Host -Prompt "Please enter new character(s)"
while($newChar.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -ge 0) {
Write-Host
Write-Host "The entry is invalid for file names. File names cannot contain / \ : * ? `" < > |" -ForegroundColor Red
$newChar = Read-Host -Prompt "Please enter new character(s)"
}
Write-Host
Write-Host "New character has been stored" -ForegroundColor Green
I am then processing the entries with this:
#Iterate through each character
foreach($char in $replaceChars) {
if($type -eq 1) {
gci -File -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item -NewName $_.Name.Replace($char, $newChar) }
} elseif ($type -eq 2) { #end-if
gci -Directory -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item -NewName $_.Name.Replace($char, $newChar) }
} else { #end-elseif
gci -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item -NewName $_.Name.Replace($char, $newChar) }
}#end-else
} #end-foreach
THE ERROR: If a user enters something like ( or ), then the last part of the script fails out with the following error code:
parsing "(" - Not enough )'s.
At <path to script>:109 char:50
+ ... gci -File -Path "$path" | Where-Object { $_.Name -match $char } | For ...
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException
MY SOLUTIONS: I have attempted to use [regex]::Escape() to escape the $char and $newChar variables to fix the parentheses error. I have also attempted to resolve this by enclosing the variables with single and double quotes; however, this prevents the actual character replacement from occuring. I understand the parentheses/special characters is what is causing the issue, and I need to escape those somehow; however, the [regex]::Escape() solution breaks the character matching and ends up not replacing any of the user's entered characters.
MY QUESTION: How can I successfully escape the $char and $newChar variables in the GCI cmdlet and still replace special characters like ( or )
gci -Path "$path" | Where-Object { $_.Name -match $char } | ForEach-Object { $_ | rename-item -NewName $_.Name.Replace($char, $newChar) }
How did you try to implement [regex]::Escape() exactly? If you tried to save $char before hand as escaped I see your problem. Since your .replace() method does not use regex. This should leave you with a few options
Just update the where clause to use the escape character just for the comparison. That was $char is not permanently changed. This would be a good idea regardless if you plan on using -match and not supporting regex characters.
gci -Path "$path" | Where-Object { $_.Name -match [regex]::Escape($char) } | ...
Save $char as escaped and use the -replace operator which also supports regex.
gci -Path "$path" | Where-Object { $_.Name -match $char } |
ForEach-Object { $_ | rename-item -NewName ($_.Name -replace $char, $newChar))
Not use regex operators at all
gci -Path "$path" | Where-Object { $_.Name.Contains($char) } | ...
Couple points of review
Don't use the name $input. It is a reserved variable name. See about_automatic_variables
Since you are working with the fullname of the file I would caution that you could also accidentally edit the extension of the file. The basename property might be useful to you in that case.
As TheMadTechnician points out you can avoid the regex/non-regex issue by filtering at the Get-ChildItem level instead.
gci -Path "$path" -Filter "*$char*" | Rename-Item -NewName {$_.Name.Replace($char, $newChar)}
THE SOLUTION: I have implemented the suggestions by #Matt and solved the errors with the following:
#Iterate through each character
foreach($char in $replaceChars) {
if($type -eq 1) {
gci -File -Path "$path" | Where-Object { $_.Basename.Contains($char) } | ForEach-Object { $_ | rename-item -NewName ($_.Basename.Replace($char, $newChar) + $_.extension) }
} elseif ($type -eq 2) { #end-if
gci -Directory -Path "$path" | Where-Object { $_.Basename.Contains($char) } | ForEach-Object { $_ | rename-item -NewName ($_.Basename.Replace($char, $newChar) + $_.extension) }
} else { #end-elseif
gci -Path "$path" | Where-Object { $_.Basename.Contains($char) } | ForEach-Object { $_ | rename-item -NewName ($_.Basename.Replace($char, $newChar) + $_.extension) }
}#end-else
} #end-foreach
I am trying to find a way to replace the characters at the end of an array variable.. tried various ways and had no luck.. here is what I have
foreach ($File in $Files){
if ($File.EndsWith(".test"))
{
#Replaces test with EURTest at the end of the string
$File2 += $_ -replace "test", "EURTest"
}
elseif ($CanBeRemovedRoamingProfile.EndsWith("CTE"))
{
# Do nothing
$File2 += $File
}
else{
$File2 += $_ + '.Final'
}
}
Any idea ?
In...
$File2 += $_ -replace "test", "EURTest"
...and...
$File2 += $_ + '.Final'
...you're operating on the $_ variable. $_ would be used in the ForEach-Object cmdlet, whereas inside of a foreach loop you should use the loop variable, which is $File in this case.
In Powershell, how would you search each row in a text file for an array of patterns? Select-string's pattern can accept an array, but it returns a true value if any of the strings in the array are found. I need it to return true (or actually the lines) if ALL the strings in the array are found in the line. Thanks.
For matching an array of strings against an array of patterns I believe you need something like this:
$patterns = #( ... )
Get-Content sample.txt | % {
$count = 0
foreach ($p in $patterns) {
if ($_ -match $p) { $count++ }
}
if ($count -eq $patterns.Length) { $_ }
}
or like this:
$patterns = #( ... )
$content = Get-Content sample.txt
foreach ( $line in $content ) {
$count = #($patterns | ? { $line -match $_ }).Length
if ( $count -eq $patterns.Length ) { $line }
}
Off the top of my head
Get-Content file.txt |
Where-Object {$_ -match $ptrn1 -and $_ -match $ptrn2 -and $_ -match $ptrn3}
Another couple of possibilities:
$patterns = 'abc','def','ghi'
$lines = 'abcdefghi','abcdefg','abcdefghijkl'
:nextline foreach ($line in $lines)
{foreach ($pattern in $patterns)
{if ($line -notmatch $pattern){continue nextline}
}$line}
abcdefghi
abcdefghijkl
That will abandon further processing of a line as soon as any of the patterns fails to match.
This works on the entire line collection at once, rather that doing a foreach:
$patterns = 'abc','def','ghi'
$lines = 'abcdefghi','abcdefg','abcdefghijkl'
foreach ($pattern in $patterns)
{$lines = $lines -match $pattern}
$lines
abcdefghi
abcdefghijkl
Substitute your get-content for the test literals to populate $lines.