Add some data at the end of a specific line of a file in powershell - powershell

I need to find in a file specific line and add some data at the end of the line, something like this:
function AddSomeData($file, $key, $value)
{
$content = Get-Content $file
if ($content -match "I work very")
{
$content- add at the end $value
Set-Content $file
}
}
AddSomeData "settings.conf" "I work very" " hard"
As a result - file with changed line: I work very hard. How to do that ?

Here's how I'ld do it:
Function AddSomeData(){
param(
[Parameter(Mandatory=$true)][String]$file,
[Parameter(Mandatory=$true)][String]$key,
[Parameter(Mandatory=$true)][String]$value
)
$content = get-content $file | ForEach-Object {
IF($_ -eq $key){"$key $value"}
ELSE {$_}
}
$content | Set-Content $file
}
Then just call the Function with:
AddSomeData settings.conf 'I work very' 'hard'

You can use a regular expression to search and replace on each line and then output to a new file. For example:
get-content "settings.conf" | foreach-object {
$_ -replace 'I work very$','$0 hard'
} | out-file "settings2.conf"

Related

How to make changes to file content and save it to another file using powershell?

I want to do this
read the file
go through each line
if the line matches the pattern, do some changes with that line
save the content to another file
For now I use this script:
$file = [System.IO.File]::ReadLines("C:\path\to\some\file1.txt")
$output = "C:\path\to\some\file2.txt"
ForEach ($line in $file) {
if($line -match 'some_regex_expression') {
$line = $line.replace("some","great")
}
Out-File -append -filepath $output -inputobject $line
}
As you can see, here I write line by line. Is it possible to write the whole file at once ?
Good example is provided here :
(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
But my problem is that I have additional IF statement...
So, what could I do to improve my script ?
You could do it like that:
Get-Content -Path "C:\path\to\some\file1.txt" | foreach {
if($_ -match 'some_regex_expression') {
$_.replace("some","great")
}
else {
$_
}
} | Out-File -filepath "C:\path\to\some\file2.txt"
Get-Content reads a file line by line (array of strings) by default so you can just pipe it into a foreach loop, process each line within the loop and pipe the whole output into your file2.txt.
In this case Arrays or Array List(lists are better for large arrays) would be the most elegant solution. Simply add strings in array until ForEach loop ends. After that just flush array to a file.
This is Array List example
$file = [System.IO.File]::ReadLines("C:\path\to\some\file1.txt")
$output = "C:\path\to\some\file2.txt"
$outputData = New-Object System.Collections.ArrayList
ForEach ($line in $file) {
if($line -match 'some_regex_expression') {
$line = $line.replace("some","great")
}
$outputData.Add($line)
}
$outputData |Out-File $output
I think the if statement can be avoided in a lot of cases by using regular expression groups (e.g. (.*) and placeholders (e.g. $1, $2 etc.).
As in your example:
(Get-Content .\File1.txt) -Replace 'some(_regex_expression)', 'great$1' | Set-Content .\File2.txt
And for the good example" where [MYID\] might be somewhere inline:
(Get-Content c:\temp\test.txt) -Replace '^(.*)\[MYID\](.*)$', '$1MyValue$2' | Set-Content c:\temp\test.txt
(see also How to replace first and last part of each line with powershell)

How to match each line of a text file contents in powershell script

I have a text file 'abc.txt' that contains the below.
hello_1
hello_2
..
..
hello_n
I need to write a script to open the file abc.txt and read each line and store that each line in a variable called $temp. I need to read the line only that starts with 'hello'. What is wrong with the below code?
I have the below Code:
foreach ($line in Get-Content "c:\folder\abc.txt")
{
if($line Select-String -Pattern 'hello')
$temp=$line
}
Try this -
$temp = #()
(Get-Content "c:\folder\abc.txt") | % {$temp += $_ | Select-String -Pattern "hello"}
$temp
The code is getting the content of abc.txt, and for each object checking if the pattern matches hello. If it's a match, then it stores the corresponding value in the array defined as $temp.
OR
You can rephrase your original code like this -
$temp = #()
foreach ($line in Get-Content "c:\folder\abc.txt")
{
if($line | Select-String -Pattern 'hello') {
$temp += line
}
}
In you original code, you are missing a pipeline in the statement if($line Select-String -Pattern 'hello'). And you are missing braces{} to enclose the if statement.
You missing pipeline after $line, and curly braces are missing in the whole scriptblock { and } after the foreach, should be:
foreach ($line in Get-Content "c:\folder\abc.txt")
{
{
if($line | Select-String -Pattern 'hello')
$temp=$line
}
}
Also, I don't know what is your purpose, but if you want $line will not be overwrited each time you should create an array outside of the iterration and fill it each time:
so first is: $line = #() and instead of $temp=$line change to $temp += $line
But then again if all your purpose is to filter the hello string from the text file then this should be enough:
$temp = (Get-Content "c:\folder\abc.txt") -match '^hello'

Parsing and splitting files based on the string

I have a very large file (hence .ReadLines) which I need to efficiently and quickly parse and split into other files. For each line which contains a keyword I need to copy that line and append to a specific file. This is what I have so far, the script runs but the files aren't getting populated.
$filename = "C:\dev\powershell\test1.csv"
foreach ($line in [System.IO.File]::ReadLines($filename)) {
if ($line | %{$_ -match "Apple"}){Out-File -Append Apples.txt}
elseif($line | %{$_ -match "Banana"}){Out-File -Append Bananas.txt}
elseif($line | %{$_ -match "Pear"}){Out-File -Append Pears.txt}
}
Example content of the csv file:
Apple,Test1,Cross1
Apple,Test2,Cross2
Apple,Test3,Cross3
Banana,Test4,Cross4
Pear,Test5,Cross5
I want Apples.txt to contain:
Apple,Test1,Cross1
Apple,Test2,Cross2
Apple,Test3,Cross3
Couple of things:
Your if conditions don't need %/foreach-object - -match will do on its own:
foreach ($line in [System.IO.File]::ReadLines($filename)) {
if($line -match "Apple"){
# output to apple.txt
}
else($line -match "Banana"){
# output to banana.txt
}
# etc...
}
The files aren't getting populated because you're not actually sending any output to Out-File:
foreach ($line in [System.IO.File]::ReadLines($filename)) {
if($line -match "Apple"){
# send $line to the file
$line |Out-File apple.txt -Append
}
# etc...
}
If your files are really massive and you expect a lot of matching lines, I'd recommend using a StreamWriter for the output files - otherwise Out-File will be opening and closing the file all the time:
$OutFiles = #{
'apple' = New-Object System.IO.StreamWriter $PWD\apples.txt
'banana' = New-Object System.IO.StreamWriter $PWD\bananas.txt
'pear' = New-Object System.IO.StreamWriter $PWD\pears.txt
}
foreach ($line in [System.IO.File]::ReadLines($filename)) {
foreach($keyword in $OutFiles.Keys){
if($line -match $keyword){
$OutFiles[$keyword].WriteLine($line)
continue
}
}
}
foreach($Writer in $OutFiles.Values){
try{
$Writer.Close()
}
finally{
$Writer.Dispose()
}
}
This way you also only have to maintain the $OutFiles hashtable if you need to update the keywords for example.

How can I search the first line and the last line in a text file?

I need to only search the 1st line and last line in a text file to find a "-" and remove it.
How can I do it?
I tried select-string, but I don't know to find the 1st and last line and only remove "-" from there.
Here is what the text file looks like:
% 01-A247M15 G70
N0001 G30 G17 X-100 Y-100 Z0
N0002 G31 G90 X100 Y100 Z45
N0003 ; --PART NO.: NC-HON.PHX01.COVER-SHOE.DET-1000.050
N0004 ; --TOOL: 8.55 X .3937
N0005 ;
N0006 % 01-A247M15 G70
Something like this?
$1 = Get-Content C:\work\test\01.I
$1 | select-object -index 0, ($1.count-1)
Ok, so after looking at this for a while, I decided there had to be a way to do this with a one liner. Here it is:
(gc "c:\myfile.txt") | % -Begin {$test = (gc "c:\myfile.txt" | select -first 1 -last 1)} -Process {if ( $_ -eq $test[0] -or $_ -eq $test[-1] ) { $_ -replace "-" } else { $_ }} | Set-Content "c:\myfile.txt"
Here is a breakdown of what this is doing:
First, the aliases for those now familiar. I only put them in because the command is long enough as it is, so this helps keep things manageable:
gc means Get-Content
% means Foreach
$_ is for the current pipeline value (this isn't an alias, but I thought I would define it since you said you were new)
Ok, now here is what is happening in this:
(gc "c:\myfile.txt") | --> Gets the content of c:\myfile.txt and sends it down the line
% --> Does a foreach loop (goes through each item in the pipeline individually)
-Begin {$test = (gc "c:\myfile.txt" | select -first 1 -last 1)} --> This is a begin block, it runs everything here before it goes onto the pipeline stuff. It is loading the first and last line of c:\myfile.txt into an array so we can check for first and last items
-Process {if ( $_ -eq $test[0] -or $_ -eq $test[-1] ) --> This runs a check on each item in the pipeline, checking if it's the first or the last item in the file
{ $_ -replace "-" } else { $_ } --> if it's the first or last, it does the replacement, if it's not, it just leaves it alone
| Set-Content "c:\myfile.txt" --> This puts the new values back into the file.
Please see the following sites for more information on each of these items:
Get-Content uses
Get-Content definition
Foreach
The Pipeline
Begin and Process part of the Foreach (this are usually for custom function, but they work in the foreach loop as well)
If ... else statements
Set-Content
So I was thinking about what if you wanted to do this to many files, or wanted to do this often. I decided to make a function that does what you are asking. Here is the function:
function Replace-FirstLast {
[CmdletBinding()]
param(
[Parameter( `
Position=0, `
Mandatory=$true)]
[String]$File,
[Parameter( `
Position=1, `
Mandatory=$true)]
[ValidateNotNull()]
[regex]$Regex,
[Parameter( `
position=2, `
Mandatory=$false)]
[string]$ReplaceWith=""
)
Begin {
$lines = Get-Content $File
} #end begin
Process {
foreach ($line in $lines) {
if ( $line -eq $lines[0] ) {
$lines[0] = $line -replace $Regex,$ReplaceWith
} #end if
if ( $line -eq $lines[-1] ) {
$lines[-1] = $line -replace $Regex,$ReplaceWith
}
} #end foreach
}#End process
end {
$lines | Set-Content $File
}#end end
} #end function
This will create a command called Replace-FirstLast. It would be called like this:
Replace-FirstLast -File "C:\myfiles.txt" -Regex "-" -ReplaceWith "NewText"
The -Replacewith is optional, if it is blank it will just remove (default value of ""). The -Regex is looking for a regular expression to match your command. For information on placing this into your profile check this article
Please note: If you file is very large (several GBs), this isn't the best solution. This would cause the whole file to live in memory, which could potentially cause other issues.
try:
$txt = get-content c:\myfile.txt
$txt[0] = $txt[0] -replace '-'
$txt[$txt.length - 1 ] = $txt[$txt.length - 1 ] -replace '-'
$txt | set-content c:\myfile.txt
You can use the select-object cmdlet to help you with this, since get-content basically spits out a text file as one huge array.
Thus, you can do something like this
get-content "path_to_my_awesome_file" | select -first 1 -last 1
To remove the dash after that, you can use the -Replace switch to find the dash and remove it. This is better than using System.String.Replace(...) method because it can match regex statements and replace whole arrays of strings too!
That would look like:
# gc = Get-Content. The parens tell Powershell to do whatever's inside of it
# then treat it like a variable.
(gc "path_to_my_awesome_file" | select -first 1 -last 1) -Replace '-',''
If your file is very large you might not want to read the whole file to get the last line. gc -Tail will get the last line very quickly for you.
function GetFirstAndLastLine($path){
return New-Object PSObject -Property #{
First = Get-Content $path -TotalCount 1
Last = Get-Content $path -Tail 1
}
}
GetFirstAndLastLine "u_ex150417.log"
I tried this on a 20 gb log file and it returned immediately. Reading the file takes hours.
You will still need to read the file if you want to keep all excising content and you want only to remove from the end. Using the -Tail is a quick way to check if it is there.
I hope it helps.
A cleaner answer to the above:
$Line_number_were_on = 0
$Awesome_file = Get-Content "path_to_ridiculously_excellent_file" | %{
$Line = $_
if ($Line_number_were_on -eq $Awesome_file.Length)
{ $Line -Replace '-','' }
else
{ $Line } ;
$Line_number_were_on++
}
I like one-liners, but I find that readability tends to suffer sometimes when I put terseness over function. If what you're doing is going to be part of a script that other people will be reading/maintaining, readability might be something to consider.
Following Nick's answer: I do need to do this on all text files in the directory tree and this is what I'm using now:
Get-ChildItem -Path "c:\work\test" -Filter *.i | where { !$_.PSIsContainer } | % {
$txt = Get-Content $_.FullName;
$txt[0] = $txt[0] -replace '-';
$txt[$txt.length - 1 ] = $txt[$txt.length - 1 ] -replace '-';
$txt | Set-Content $_.FullName
}
and it looks like it's working well now.
Simple process:
Replace $file.txt with your filename
Get-Content $file_txt | Select-Object -last 1
I was recently searching for comments in the last line of .bat files. It seems to mess up the error code of previous commands. I found this useful for searching for a pattern in the last line of files. Pspath is a hidden property that get-content outputs. If I used select-string, I would lose the filename. *.bat gets passed as -filter for speed.
get-childitem -recurse . *.bat | get-content -tail 1 | where { $_ -match 'rem' } |
select pspath
PSPath
------
Microsoft.PowerShell.Core\FileSystem::C:\users\js\foo\file.bat

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.