Using PowerShell in a .bat file, replace a string with multiple strings - powershell

I'm using a .baat to move several files into another folder, but before the actual move part, I want to replace the LAST line (it is a known line), for example I have a file output.txt like this:
HEADER
BODY
FOOTER
Using this snippet of code:
powershell -Command "(gc output.txt) -replace 'FOOTER', 'ONE_MORE_LINE `r`n FOOTER' | Out-File output.txt"
The return that I expected was
HEADER
BODY
ONE_MORE_LINE
FOOTER
But what I got was:
HEADER
BODY
ONE_MORE_LINE `r`n FOOTER
I've tried:
\n
<br>
"`r`n"
"`n"
echo ONE_MORE_LINE >> output.txt; echo. >> output.txt; echo FOOTER >> output.txt"
This last one got close, but the result was some broken characters.
Other suggestions besides the PowerShell are welcome. I'm only using it because it was an easy get way to do the adding lines and replace it.
EDIT :
Tried this command
powershell -Command "(gc output.txt) -replace 'FOOTER;', ""ONE_MORE_LINE `r`n FOOTER"" | Out-File output.txt "
And returned this error:
A cadeia de caracteres não tem o terminador: ".
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
EDIT2 - Possible Solution:
I realized that using the PowerShell command altered the encoding of the file, breaking the echo ONE_MORE_LINE, and using the suggestion from #AnsgarWiechers, I made this code
findstr /v "FOOTER" output.sql > new_output.sql
TYPE new_output.sql > output.sql
del new_output.sql
ECHO. >> %%f
ECHO ONE_MORE_LINE >> %%f
ECHO FOOTER >> %%f
ECHO. >> %%f
What it does is using the commant findstr /v "FOOTER" I look for all lines that are not FOOTER in the file output.sql and Write it on new_output.sql
Then I TYPE it back to the original file, and DEL the new_output.sql
Then I Echo all the lines I need right under it.
It works BUT, for big files I think that re-writing it twice will take a lot of time, but I can't figure an other solution.

When working with big files it's best to use a file stream. More typical methods of reading a file line-by-line using a Batch for /f loop or using Get-Content in PowerShell to read the entire file into memory can slow the process to a crawl with large files. Using a file stream on the other hand, you can nearly instantly seek back from the end of the file to the beginning of the last line, insert your desired data, and then reassemble the bytes you overwrote.
The following example will use PowerShell's access to .NET methods to open a file as a byte stream for rapid reading and writing. See inline comments for details. File encoding will hopefully be preserved. Save this with a .bat extension and give it a shot.
<# : batch portion
#echo off & setlocal
set "file=test.txt"
set "line=Line to insert!"
powershell -noprofile "iex (${%~f0} | out-string)"
goto :EOF
: end batch / begin PowerShell hybrid #>
# construct a file stream for reading and writing $env:file
$IOstream = new-object IO.FileStream((gi $env:file).FullName,
[IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite)
# read BOM to determine file encoding
$reader = new-object IO.StreamReader($IOstream)
[void]$reader.Read((new-object byte[] 3), 0, 3)
$encoding = $reader.CurrentEncoding
$reader.DiscardBufferedData()
# convert line-to-insert to file's native encoding
$utf8line = [Text.Encoding]::UTF8.GetBytes("`r`n$env:line")
$line = [Text.Encoding]::Convert([Text.Encoding]::UTF8, $encoding, $utf8line)
$charSize = [math]::ceiling($line.length / $utf8line.length)
# move pointer to the end of the stream
$pos = $IOstream.Seek(0, [IO.SeekOrigin]::End)
# walk back pointer while stream returns no error
while ($char -gt -1) {
$IOstream.Position = --$pos
$char = $reader.Peek()
$reader.DiscardBufferedData()
# break out of loop when line feed preceding non-whitespace is found
if ($foundPrintable) { if ($char -eq 10) { break } }
else { if ([char]$char -match "\S") { $foundPrintable++ } }
}
# step pointer back to carriage return and read to end into $buffer with $line prepended
$pos -= $charSize
$IOstream.Position = $pos
$buffer = $encoding.GetBytes($encoding.GetString($line) + $reader.ReadToEnd())
$IOStream.Position = $pos
"Inserting data at byte $pos"
$IOstream.Write($buffer, 0, $buffer.Length)
# Garbage collection
$reader.Dispose()
$IOstream.Dispose()
This method should be much more efficient than reading the file from the beginning, or copying the entire file into memory or on disk with a new line inserted. In my testing, it inserts the line into a hundred meg file in about 1/3 of a second.

Related

How do I make PowerShell read a file as single lines when it thinks there are multiple lines?

I have an input file with the following lines:
1000095710|SavRx
1000124602|Mason’s Pharmacy
1001130436|Regence
1001314900|HealthE Systems
1001322929|IT management
Which I created with the the line:
$line = $numValueYouSee + "|" + $fd.Replace("Client", "") #Client used to say ClientNameOfClientYouSeeNowAsSavRx
When I read the new output file with PowerShell using $FileText = gc "Path\To\File.txt", it is reading the lines as the following:
1000095710
|SavRx
1000124602
|Mason’s Pharmacy
1001130436
|Regence
1001314900
|HealthE Systems
1001322929
|IT management
I can verify the lines of the file by just clicking arrow keys, but in Notepad++ I can see extra CR-LFs. How can I fix this ridiculous output to the correct output?
Looks to me like your data fields have trailing CR characters. Demonstration:
PS C:\Temp> $s = "1000095710`r|SavRx`r`r`n1000124602`r|Mason's Pharmacy`r`r`n"
PS C:\Temp> [IO.File]::WriteAllText('c:\temp\out.txt', $s)
PS C:\Temp> Get-Content .\out.txt
1000095710
|SavRx
1000124602
|Mason's Pharmacy
You can remove them by trimming your fields before creating the output.
$line = $numValueYouSee.Trim() + "|" + $fd.Replace("Client", "").Trim()
I cannot replicate your issue either. Maybe you have to save your file again from notepad. Maybe there is hidden formatting in the text like a hidden "`n"
You could try this:
$FileText = Import-Csv Path\To\File.txt -Header Number,Type -Delimiter "|" -Encoding Default

After sorting from external listing.csv file and insert result in txt file >>listing2.txt

#echo off
setlocal EnableDelayedExpansion
for /f "delims=; tokens=1-5" %%i in ('type listing.csv') do (
echo %%k | find "CNSHA" > nul
if !errorlevel!==0 echo Searched_Value "CNSHA" was found in %%i: %%j:%%k
)
pause
With file listing.csv I would like to save the search result to another file listing2.txt.
In which lines of code to be include in the code formula >> listing2.txt to save the result of the sort or similar?
You can append to a file inside a loop like this:
for ... %%i in (...) do (
echo something >> output.txt
echo or other
)
The above will put only "something" lines in the output file while the "or other" lines are printed to the console.
You can also write the entire loop output to a file by putting the redirection operator outside the loop:
for ... %%i in (...) do (
echo something
echo or other
) >> output.txt
That will put both "something" and "or other" lines into the output file.
Note that when using the append redirection operator (>>) you need to truncate or remove an already existing file if you don't want to preserve content that was present prior to the loop:
type nul> output.txt
You can avoid this additional step by using the write redirection operator (>), but for that you need to put the whole loop in parentheses:
(for ... %%i in (...) do (
echo something
echo or other
)) > output.txt
BTW, you can also put redirection operators at the beginning of a line, and it's good practice to do so, because it avoids involuntarily adding trailing spaces to output lines:
for ... %%i in (...) do (
>>output.txt echo something
echo or other
)
or
>>output.txt for ... %%i in (...) do (
echo something
echo or other
)
Of course you can also mix the two approaches (redirecting inside and outside the loop), so you get some output in one file and the rest of the output in another:
>>output1.txt for ... %%i in (...) do (
>>output2.txt echo something
echo or other
)
If you want filtered records from the CSV as the output, you need to reconstruct the output lines, though:
>>output.csv echo %%~i;%%~j;%%~k
In your particular scenario it might be easier to just use find instead of working with a loop:
>output.csv (type input.csv | find ";CNSHA")
Since you also tagged your question powershell I'm going to throw in a PowerShell solution as well, just for good measure:
$headers = 'foo', 'bar', 'baz'
Import-Csv 'input.csv' -Delimiter ';' -Header $headers | Where-Object {
$_.baz -eq 'CNSHA'
} | Export-Csv 'output.csv' -NoType
The above writes all records where the 3rd field has the value "CNSHA" to a new file. Remove the -Header $headers if your input CSV comes with headers. Change the Export-Csv statement to something like this if you don't want headers in the output:
... | ConvertTo-Csv -NoType | Select-Object -Skip 1 | Set-Content 'output.csv'
Is there a reason why you cannot just use:
Find "CNSHA"<"listing.csv">"listing2.txt"
Presuming a possible previous file listing2.txt should be overwritten:
#echo off
setlocal
(for /f "delims=; tokens=1-5" %%i in (
'find /i "CNSHA" listing.csv'
) do echo Searched_Value "CNSHA" was found in %%i: %%j:%%k
) >listing2.txt
pause
Otherwse double the > the redirection to >>

Remove all carriage return and line feed from file

Last week I have asked you guys to replace a string with newline character with .bat script. I have realized that my file has some carriage return and newline characters already, which I need to remove first and then do the replace.
to replace '#####' with linefeed I am using the line below.
(gc $Source) -replace "#####", "`r`n"|set-content $Destination
So I tried to implement the same logic to replace \r and \n as well, however it did not work.
(gc $Source) -replace "`n", ""|set-content $Destination
my file looks like :
abc|d ef|123#####xyz|tuv|567#####
and I need to make it look like
abc|def|123 xyz|tuv|567
like I said, replacing the row delimiter character with new line works, but I need to remove all cr and lf characters first before I do that.
For small files the script below works, but my file is >1.5GB and it throws OutofMemoryException error
param
(
[string]$Source,
[string]$Destination
)
echo $Source
echo $Destination
$Writer = New-Object IO.StreamWriter $Destination
$Writer.Write( [String]::Join("", $(Get-Content $Source)) )
$Writer.Close()
Use the below function to remove the special characters. Put all of them in $SpecChars what ever you want to remove and call the function with the Text-data as a parameter.
Function Convert-ToFriendlyName
{param ($Text)
# Unwanted characters (includes spaces and '-') converted to a regex:
#Whatever characters you want to remove, put it here with comma separation.
$SpecChars = '\', ' ','\\','-'
$remspecchars = [string]::join('|', ($SpecChars | % {[regex]::escape($_)}))
# Convert the text given to correct naming format (Uppercase)
$name = (Get-Culture).textinfo.totitlecase(“$Text”.tolower())
# Remove unwanted characters
$name = $name -replace $remspecchars, ""
$name
}
Hope it helps...!!!
This is vbscript. Windows isn't consistent. Mostly it breaks on CR and removes LF (all inbuilt programming languages). But Edit controls (ie Notepad) break on LF and ignore CR (unless preceding a LF).
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Do Until Inp.AtEndOfStream
Text = Inp.readall
Text = Replace(Text, vbcr, "")
Text = Replace(Text, vblf, "")
Text = Replace(Text, "#####", vblf)
outp.write Text
Loop
This uses redirection of StdIn and StdOut.
Filtering the output of a command
YourProgram | Cscript //nologo script.vbs > OutputFile.txt
Filtering a file
Cscript //nologo script.vbs < InputFile.txt > OutputFile.txt
See my CMD Cheat Sheet about the Windows' command line Command to run a .bat file
So this removes line ending in win.ini and prints to screen the now one line win.ini.
cscript //nologo "C:\Users\David Candy\Desktop\Replace.vbs" < C:\windows\win.ini

Piping from a variable instead of file in Powershell

Is ther any way in Powershell to pipe in from an virable instead of a file?
There are commands that I need to pipe into another command, right now that is done by first creating a file with the additional commands, and then piping that file into the original command. Code looks somehting like this now:
$val = "*some command*" + "`r`n" + "*some command*" + "`r`n" + "*some command*"
New-Item -name Commands.txt -type "file" -value $val
$command = #'
db2cmd.exe /C '*custom db2 command* < \Commands.txt > \Output.xml'
'#
Invoke-Expression -Command:$command
So instead of creating that file, can I somehow just pipe in $val insatead of Commands.txt?
Try this
$val = #("*some command*1","*some command2*","*some command3*")
$val | % { db2cmd.exe /C $_ > \Output.xml }
You should be able to pipe in from $val provided you use Write-Output or its shorthand echo, but it may also be worth trying passing the commands directly on the command line. Try this (and if it doesn't work I can delete the answer):
PS C:\> filter db2cmd() { $_ | db2cmd.exe ($args -replace '(\\*)"','$1$1\"') }
PS C:\> $val = #"
>> *custom db2 command*
>> *some command*
>> *some command*
>> *some command*
>> "#
>>
PS C:\> db2cmd /C $val > \Output.xml
What happens here is that Windows executables receive their command line from a single string. If you run them from cmd.exe you cannot pass newlines in the argument string, but Powershell doesn't have that restriction so with many programs you can actually pass multiple lines as a single argument. I don't know db2cmd.exe so it might not work here.
The strange bit of string replacement is to handle any double quotes in the arguments: Powershell doesn't quote them and the quoting rules expected by most exe files are a bit bizarre.
The only limitation here would be that $val must not exceed about 32,600 characters and cannot contain nulls. Any other restrictions (such as whether non-ascii unicode characters work) would depend on the application.
Failing that:
echo $val | db2cmd.exe /C '*custom db2 command*' > \Output.xml
may work, or you can use it in combination with the filter I defined at the top:
echo $val | db2cmd /C '*custom db2 command*' > \Output.xml

Delete a line in txt file after a specific keyword

I have a txt file like:
sample text1
sample text2
sample text3
and I need it to be:
sample
sample
sample
Basically, I need to delete the entire line after a given phrase, in this case the "sample" keyword
Can someone help me? I would prefer vbscript or something that is stanalone and I can make a batch for this
Regards,
(Get-Content sample.txt) -replace '.*sample(.+)', '$1' | Out-File sample.txt
This will work inside a .cmd or .bat file:
for /f "tokens=1" %%i in ('type file1') do #echo %i >> file2
Since you tagged your question with powershell, I'll give you a solution in powershell:
$target='sample'
foreach ($line in gc .\foo.txt){$line.substring(0,$target.length)|out-file -append bar.txt}
I assume that every line starts with "sample".
If the "sample" text is anywhere in the line, this should work:
$target='sample'
foreach ($line in gc .\foo.txt){$line.substring(0,$line.indexof($target)+$target.length)|out-file -append bar.txt}
With Just Vbscript, you can achieve this as follows
Option Explicit
Dim oFSO, oTxtFile, sLine ,filePath, cTxtFile
Const ForReading=1
Const ForWriting=2
filePath ="C:\Documents and Settings\Amol\Desktop\Converted\test.txt"
'' Filepath is your local path to txt file
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oTxtFile = oFSO.OpenTextFile(filePath, 1)
Set cTxtFile = oFSO.CreateTextFile(filePath & ".tmp")
Do Until oTxtFile.AtEndOfStream
sLine = oTxtFile.ReadLine
If InStr(sLine,"sample") <> 0 Then
sLine = Left(sLine, InStr(sLine,"sample") -1 )&"sample"
cTxtFile.WriteLine(sLine)
End if
Loop
oTxtFile.Close
cTxtFile.Close
oFSO.DeleteFile(filePath)
oFSO.MoveFile filePath&".tmp", filePath