This question already has answers here:
How can I prevent additional newlines with set-content while keeping existing ones when saving in UTF8?
(2 answers)
Set-Content appends a newline (line break, CRLF) at the end of my file
(3 answers)
Closed 4 years ago.
I have a snippet of code which gets the content of each file and will replace values within it if it matches my variable list.
The code works fine. However, after the scan it's leaving a blank line at the end of the file which I do not want to happen.
# From the location set in the first statement
# Recurse through each file in each folder that has an extension defined
# in-Include
$configFiles = Get-ChildItem $Destination -Recurse -File -Exclude *.exe,*.css,*.scss,*.png,*.min.js
foreach ($file in $configFiles) {
Write-Host $file.FullName
# Get the content of each file and search and replace values as defined in
# the searc/replace table
$fileContent = Get-Content $file.FullName
$fileContent | ForEach-Object {
$line = $_
$lookupTable.GetEnumerator() | ForEach-Object {
# [Regex]::Escape($_.Key) treats regex metacharacters in the search
# string as string literals
if ($line -match [Regex]::Escape($_.Key)) {
$line = $line -replace [Regex]::Escape($_.Key), $_.Value
}
}
$line
} | Set-Content $file.FullName
}
I've tried adding:
Set-Content $file.FullName -NoNewline
This just puts everything in the file on one line.
Some files will already have a blank line at the end which I want to stay the same, so I can't just remove the last line of every file.
How do I stop this script from adding a new line once finished scanning?
$lookuptable for reference:
$lookupTable = #{
'Dummy' = $ReplacementValue
'Dummy2' = $ReplacementValue
}
Related
Every night I got a text file that needs to be edited manually.
The file contains approximately 250 rows. Three example of a rows:
112;20-21;32;20-21;24;0;2;248;271;3;3;;
69;1;4;173390;5;0;0;5460;5464;3;3;;
24;7;4;173390;227;0;0;0;0;3;3;;
I need to replace the two last values in each row.
All rows ending with ;0;3;3;; should be replaced with ;0;17;18;; (the last one, solved)
The logic for the other two:
If the row contain a '-' it should replace the two last values from ;3;3;; to ;21;21;;
If it don´t have a '-' it should replace the two last values from ;3;3;; to ;22;22;;
This is my script
foreach ($file in Get-ChildItem *.*)
{
(Get-Content $file) -replace ';0;3;3;;',';;0;17;18;;' -replace ';3;3;;',';21;21;;' |Out-file -encoding ASCII $file-new}
If I could add a '-' in the end of each row continga a '-' I could solve the issue with a modified script:
(Get-Content $file) -replace ';0;3;3;;',';;0;17;18;;' -replace ';3;3;;-',';22;22;;' -replace ';3;3;;',';21;21;;'|Out-file -encoding ASCII $file-new}`
But how do I add a '-' in the end of a row, if the row contain a '-'?
Best Regards
Mr DXF
I tried with select-string, but I can´t figure it out...
if select-string -pattern '-' {append-text '-'|out-file -encoding ascii $file-new
else end
}
The following might do the trick, it uses a switch with the -Regex flag to read your files and match lines with regular expressions.
foreach ($file in Get-ChildItem *.* -File) {
& {
switch -Regex -File $file.FullName {
# if the line ends with `;3;3;;` but is not preceded by `;0`
'(?<!;0);3;3;;$' {
# if it contains a `-`
if($_.Contains('-')) {
$_ -replace ';3;3;;$', ';21;21;;'
continue
}
# if it doesn't contain a `-`
$_ -replace ';3;3;;$', ';22;22;;'
continue
}
# if the line ends with `';0;3;3;;`
';0;3;3;;$' {
$_ -replace ';0;3;3;;$', ';0;17;18;;'
continue
}
# if none of the above conditions are matched,
# output as is
Default { $_ }
}
} | Set-Content "$($file.BaseName)-new$($file.Extension)" -Encoding ascii
}
Using the content example in question the end result would become:
112;20-21;32;20-21;24;0;2;248;271;21;21;;
69;1;4;173390;5;0;0;5460;5464;22;22;;
24;7;4;173390;227;0;0;0;0;17;18;;
SHORT: I am trying to duplicate lines in all files in a folder based on a certain string and then replace original strings in duplicated lines only.
Contents of the original text file (there are double quotes in the file):
"K:\FILE1.ini"
"K:\FILE1.cfg"
"K:\FILE100.cfg"
I want to duplicate the entire line 4 times only if a string ".ini" is present in a line.
After duplicating the line, I want to change the string in those duplicated lines (original line stays the same) to: for example, ".inf", ".bat", ".cmd", ".mov".
So the expected result of the script is as follows:
"K:\FILE1.ini"
"K:\FILE1.inf"
"K:\FILE1.bat"
"K:\FILE1.cmd"
"K:\FILE1.mov"
"K:\FILE1.cfg"
"K:\FILE100.cfg"
Those files are small, so using streams is not neccessary.
I am at the beginning of my PowerShell journey, but thanks to this community, I already know how to replace string in files recursively:
$directory = "K:\PS"
Get-ChildItem $directory -file -recurse -include *.txt |
ForEach-Object {
(Get-Content $_.FullName) -replace ".ini",".inf" |
Set-Content $_.FullName
}
but I have no idea how to duplicate certain lines multiple times and handle multiple string replacements in those duplicated lines.
Yet ;)
Could point me in the right direction?
To achieve this with the operator -replace you can do:
#Define strings to replace pattern with
$2replace = #('.inf','.bat','.cmd','.mov','.ini')
#Get files, use filter instead of include = faster
get-childitem -path [path] -recurse -filter '*.txt' | %{
$cFile = $_
#add new strings to array newData
$newData = #(
#Read file
get-content $_.fullname | %{
#If line matches .ini
If ($_ -match '\.ini'){
$cstring = $_
#Add new strings
$2replace | %{
#Output new strings
$cstring -replace '\.ini',$_
}
}
#output current string
Else{
$_
}
}
)
#Write to disk
$newData | set-content $cFile.fullname
}
This gives you the following output:
$newdata
"K:\FILE1.inf"
"K:\FILE1.bat"
"K:\FILE1.cmd"
"K:\FILE1.mov"
"K:\FILE1.ini"
"K:\FILE1.cfg"
"K:\FILE100.cfg"
Problem is solved, but I don't understand why :-)
I have a Powershell script that perform replacements inside files (language metadata):
loads a list of replacement from a txt file into an array
gets all xml files from a Start folder
performs all the replacements from the array
performs a replacement on the filename based on the array first entry
saves the resulting files in a End folder
I've been using successfully variations of the exact same script for many years, with the only thing changing being the replacement file name and content... except today when creating another variant. The only change was the content of the substitution file, and suddenly the replacement did not happen anymore in the filename.
Here is the code:
#load the replacements from file
$data = Import-Csv -Path substitutions.txt -Header "Source", "Target", "Safe", "Count" -Delimiter "|"
#load the files to be processed
$xmlfiles = -join ($Startfolder, "*.xml")
$Fileset = Get-ChildItem $xmlfiles -recurse
foreach ($File in $Fileset) {
$NewFileName = ""
$WipFile = Get-Content $File
# set safe replacement flag to nothing
$flag = ""
#perform replacements
foreach ($item in $data) {
if ($WipFile -cmatch $item.Source) {
if ($item.Safe -eq 'yes') {
$WipFile = $WipFile -creplace $item.Source, $item.Target
$item.Count = $item.Count + 1
}
else {
$WipFile = $WipFile -creplace $item.Source, $item.Target
$item.Count = $item.Count + 1
$flag = "TOCHECK "
}
}
}
#replace language code in filename, based on first entry in the substitution list
$NewFileName = -join ($Endfolder, $flag, $file.name -creplace $data.Source[0], $data.Target[0])
Write-Host $NewFileName
#save file with updated content
$WipFile | Set-Content -Encoding Unicode ($File)
#move file to End folder
Move-Item $File $NewFileName
}
The substitution file is formatted as follows:
nl-NL|nl-BE|yes
After testing more, I discovered my new variant was failing if my substitution file had only one line. Add another one, and it works. How come?
I have two files: FileA and FileB, they are nearly identical.
Both files have a section which starts with ////////// MAIN \\\\\\\\\\. I need to replace the whole content from this point until the end of the file.
So the process high level looks like:
find content (starting with ////////// MAIN \\\\\\\\\\) until the end of the file in FileA and copy it to clipboard
find content (starting with ////////// MAIN \\\\\\\\\\) until the end of the file in FileB and replace it with the content from the clipboard
How do I do this?
I understand that it would look like this (found it online) but I'm missing the pattern and logic I can use for selecting the text until the end of the file:
# FileA
$inputFileA = "C:\fileA.txt"
# Text to be inserted
$inputFileB = "C:\fileB.txt"
# Output file
$outputFile = "C:\fileC.txt"
# Find where the last </location> tag is
if ((Select-String -Pattern "\</location\>" -Path $inputFileA |
select -last 1) -match ":(\d+):")
{
$insertPoint = $Matches[1]
# Build up the output from the various parts
Get-Content -Path $inputFileA | select -First $insertPoint | Out-File $outputFile
Get-Content -Path $inputFileB | Out-File $outputFile -Append
Get-Content -Path $inputFileA | select -Skip $insertPoint | Out-File $outputFile -Append
}
You could do that in two lines of code:
# first write the top part including the '////////// MAIN \\\\\\\\\\' from FileB to the new file
((Get-Content -Path "D:\Test\fileB.txt" -Raw) -split '(?<=/+ MAIN \\+\r?\n)', 2)[0] | Set-Content -Path "D:\Test\fileC.txt" -NoNewline
# then append the bottom part excluding the '////////// MAIN \\\\\\\\\\' from FileA to the new file
((Get-Content -Path "D:\Test\fileA.txt" -Raw) -split '/+ MAIN \\+\r?\n', 2)[-1] | Add-Content -Path "D:\Test\fileC.txt"
Regex details:
(?<= # Assert that the regex below can be matched, with the match ending at this position (positive lookbehind)
/ # Match the character “/” literally
+ # Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\ MAIN\ # Match the characters “ MAIN ” literally
\\ # Match the character “\” literally
+ # Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\r # Match a carriage return character
? # Between zero and one times, as many times as possible, giving back as needed (greedy)
\n # Match a line feed character
)
Or, if the files are quite large:
# first write the top part including the '////////// MAIN \\\\\\\\\\' from FileB to the new file
$copyThis = $true
$content = switch -Regex -File "D:\Test\fileB.txt" {
'/+ MAIN \\+' { $copyThis = $false; $_ ; break}
default { if ($copyThis) { $_ } }
}
$content | Set-Content -Path "D:\Test\fileC.txt"
# then append the bottom part excluding the '////////// MAIN \\\\\\\\\\' from FileA to the new file
$copyThis = $false
$content = switch -Regex -File "D:\Test\fileA.txt" {
'/+ MAIN \\+' { $copyThis = $true }
default { if ($copyThis) { $_ } }
}
$content | Add-Content -Path "D:\Test\fileC.txt"
Requirement is to insert a blank line in multiple files before the matching pattern line
Consider a file with below contents
Apple
Tree
orange
[Fruit]
Red
Green
Expected output:
Apple
Tree
orange
[Fruit]
Red
Green
Tried below code. Help me to figure out the mistake in below code
$FileName = Get-ChildItem -Filter *.ini -Recurse
$Pattern = "\[Fruit]\"
[System.Collections.ArrayList]$file = Get-Content $FileName
$insert = #()
for ($i=0; $i -lt $file.count; $i++) {
if ($file[$i] -match $pattern) {
$insert += $i #Record the position of the line before this one
}
}
#Now loop the recorded array positions and insert the new text
$insert | Sort-Object -Descending | ForEach-Object { $file.insert($_," ") }
Set-Content $FileName $file
above code owrks fine for single file but for multiple file, the contents of the file are repeated
Re: how to make this work for multiple files...
$FileName = Get-ChildItem -Filter *.ini -Recurse
If there is only one .ini file then $FileName will be a single file.
The use of the wildcard and -Recurse switch suggests that you are expecting to find multiple files; thus this command will assign that collection of files to the $FileName variable (i.e. it will be an array).
Notice that when you call Get-Content you pass $FileName:
[System.Collections.ArrayList]$file = Get-Content $FileName
This won't work when $FileName is a collection/array of files.
What you need to do is put a loop in place that will perform your "insert a line break" logic foreach (hint hint) of the files in the array. NOW go and look at those PS tutorials again...
Regex character class
Try to take the time to learn regex properly
$Pattern = "\[Fruit\]"