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

8

6

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)
Neusatz answered 18/1, 2013 at 15:52 Comment(0)
I
6

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:

  1. gc means Get-Content
  2. % means Foreach
  3. $_ 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:

  1. (gc "c:\myfile.txt") | --> Gets the content of c:\myfile.txt and sends it down the line
  2. % --> Does a foreach loop (goes through each item in the pipeline individually)
  3. -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
  4. -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
  5. { $_ -replace "-" } else { $_ } --> if it's the first or last, it does the replacement, if it's not, it just leaves it alone
  6. | 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.

Innocent answered 18/1, 2013 at 17:45 Comment(9)
good answer, but be aware that if the files get huge it would be better to use get-content -First 1 and ... -Last 1 in the begin statement so you don't keep a xxGB file in memory while processing.Steak
True, but then we are back at the problem of it doesn't pass the whole file back, just the first and last line.Innocent
no. I said in the begin statement foreach -begin { ..here... }. :)Steak
Ahh, my mistake, I wasn't think what my own code did. I'll make the change.Innocent
I actually meant $first = get-content $path -first 1 and $last = get-content $path -last 1, but still an improvement :P I'm not 100% sure but with select you still work through the whole file to find the lines.Steak
Get-content doesn't have a -first or a -last parameter, at least in PSv2.Innocent
they are actually called -Totalcount(first) and -Tail(last). first and last are aliases that work(at least in 3.0) :) edit: oh, tail is introduced in 3.0, but the question is tagged with that anyways.Steak
Ahh, in 2.0 there is no tail option, and the -first and -last throw a no parameter error. There isn't an option I saw to read from the end of a file, so while -totalcount(1) would give the first line, there isn't anything to give the last line.Innocent
I added a warning about using this with large files.Innocent
L
6

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
Lamprophyre answered 18/1, 2013 at 15:58 Comment(4)
typos: $a --> $txt (last line) . $txt[1] --> $txt[0] :-)Steak
Also, you can just call $txt[-1] to get the last item in the arrayInnocent
What if the file is a huge text file? Say 10GB text file.Ancestral
then you split it up with read-count and use add-content to save it part by part as a new file. and use a long clever foreach loop with if-tests for 1st and last line. :)Steak
I
6

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:

  1. gc means Get-Content
  2. % means Foreach
  3. $_ 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:

  1. (gc "c:\myfile.txt") | --> Gets the content of c:\myfile.txt and sends it down the line
  2. % --> Does a foreach loop (goes through each item in the pipeline individually)
  3. -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
  4. -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
  5. { $_ -replace "-" } else { $_ } --> if it's the first or last, it does the replacement, if it's not, it just leaves it alone
  6. | 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.

Innocent answered 18/1, 2013 at 17:45 Comment(9)
good answer, but be aware that if the files get huge it would be better to use get-content -First 1 and ... -Last 1 in the begin statement so you don't keep a xxGB file in memory while processing.Steak
True, but then we are back at the problem of it doesn't pass the whole file back, just the first and last line.Innocent
no. I said in the begin statement foreach -begin { ..here... }. :)Steak
Ahh, my mistake, I wasn't think what my own code did. I'll make the change.Innocent
I actually meant $first = get-content $path -first 1 and $last = get-content $path -last 1, but still an improvement :P I'm not 100% sure but with select you still work through the whole file to find the lines.Steak
Get-content doesn't have a -first or a -last parameter, at least in PSv2.Innocent
they are actually called -Totalcount(first) and -Tail(last). first and last are aliases that work(at least in 3.0) :) edit: oh, tail is introduced in 3.0, but the question is tagged with that anyways.Steak
Ahh, in 2.0 there is no tail option, and the -first and -last throw a no parameter error. There isn't an option I saw to read from the end of a file, so while -totalcount(1) would give the first line, there isn't anything to give the last line.Innocent
I added a warning about using this with large files.Innocent
O
1

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 '-',''
Obstetric answered 18/1, 2013 at 17:0 Comment(4)
I was thinking like this too, the only problem I found is that he needs the entire array. If you try to pipe this back into the file, it will just have the first and last lines, not the whole file with a changed first and last line.Innocent
Also, it should be gc as the alias to Get-Content. gci is the alias to Get-ChildItemInnocent
Thanks for correcting me ; I use cat so often, I forget the entire name of the cmdlet sometimes. :)Obstetric
No problem, I would have just edited it myself, but I don't have the rep to only change two letters.Innocent
P
1

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.

Pungent answered 8/5, 2015 at 9:16 Comment(0)
O
0

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.

Obstetric answered 18/1, 2013 at 20:47 Comment(1)
There are a few issues with this script. First, it only does work on the last line, not first and last. Second, $line is never put back into anything, so the work is done and then lost, ever put back into the file.Innocent
N
0

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.

Neusatz answered 21/1, 2013 at 16:20 Comment(0)
A
0

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

Animator answered 27/3, 2020 at 18:51 Comment(0)
F
-1

Simple process: Replace $file.txt with your filename

Get-Content $file_txt | Select-Object -last 1

Faustofaustus answered 27/6, 2017 at 8:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.