ChatGPT解决这个技术问题 Extra ChatGPT

Unix tail equivalent command in Windows Powershell

I have to look at the last few lines of a large file (typical size is 500MB-2GB). I am looking for a equivalent of Unix command tail for Windows Powershell. A few alternatives available on are,

http://tailforwin32.sourceforge.net/

and

Get-Content [filename] | Select-Object -Last 10

For me, it is not allowed to use the first alternative, and the second alternative is slow. Does anyone know of an efficient implementation of tail for PowerShell.

How can we know if you will be allowed to use what we suggest if you don't say why you're not allowed to use the first alternative?
Any reason you can't use the tail command provided in sourceforge.net/projects/unxutils/files/unxutils/current/…?
this is in a production machine where I was not allowed allowed to copy copy any external executables. Some weird policies. :) Can't help it. Thanks for the Unxutils link.
https://devcentral.f5.com/blogs/us/unix-to-powershell-tail demonstrates pure PoSH implementation of this.
No need to use Select-Object: Get-Content [filename] -last 10 and add -tailfor -f

T
Tony Hinkle

Use the -wait parameter with Get-Content, which displays lines as they are added to the file. This feature was present in PowerShell v1, but for some reason not documented well in v2.

Here is an example

Get-Content -Path "C:\scripts\test.txt" -Wait

Once you run this, update and save the file and you will see the changes on the console.


Interesting. I would have thought that all arguments that exist also appear in help, yet man gc -par wait tells me there is no parameter. But I think this doesn't solve the problem that the OP has, since they asked for tail, not tail -f and an efficient implementation as well. Since this one also reads the complete file before returning the last lines this is painful for the file sizes they expect.
FYI, this is what the Get-FileTail (alias tail) implementation does in PSCX. If you're curious you can look at the source code: pscx.codeplex.com/SourceControl/changeset/view/78514#1358075
@Joey -Wait is a dynamic parameter that only applies to the FileSystem provider. GC can be used on any provider that implements that API. The only way besides documentation that I know to discover these is to use (gcm Get-Content).Parameters from within the appropriate provider path. Don't use the alias "gc" because the dynamic parameters will not show up.
I know it was a while ago, but this requires the process writing to the file to open, append, close it before Get-Content will work. If the writing process never closes the file then it won't work which is not the case with tail -f.
Oddly, -Wait only shows me new lines when I access a log file in some way (such as selecting it in Windows Explorer). Tail provides updates as new lines are written to my file. With -Wait, I can leave a PowerShell window open happily showing no new lines while the file is being written to. If I then pop over and click on the file in Windows Explorer, suddenly PowerShell "wakes up" and catches up the remaining lines. Is this a bug?
s
spyder0077

For completeness I'll mention that Powershell 3.0 now has a -Tail flag on Get-Content

Get-Content ./log.log -Tail 10

gets the last 10 lines of the file

Get-Content ./log.log -Wait -Tail 10

gets the last 10 lines of the file and waits for more

Also, for those *nix users, note that most systems alias cat to Get-Content, so this usually works

cat ./log.log -Tail 10

@LauraLiparulo in what way does this not work? I've used it before definitely.
I just used it and it worked spot on in this format Get-Content .\test.txt -Wait -Tail 1
@LauraLiparulo - Works for me also: Get-Content -Path .\sync.log -Wait -Tail 10
On ISE, I used to use while($true)/sleep and switched to this one, but this one also locks entire ISE and cannot run scripts on other tabs. Should I just start a new ISE instance?
@Teomanshipahi In whwat way did the -Wait parameter not work for you?
D
Dan Blanchard

As of PowerShell version 3.0, the Get-Content cmdlet has a -Tail parameter that should help. See the technet library online help for Get-Content.


Note for some - PS 3.0 is Unavailable to Windows XP and Vista.
I use the technique mentioned by Dan but I record it in my $PROFILE. Open it with notepad $PROFILE. Then in the text document, create a new function: function Tail ($path) { Get-content -tail 15 -path $path -wait } This way you can access the function each time you start PowerShell.
This should be the accepted answer. -Wait flag mentioned in currently accepted answer doesn't work anymore.
J
John Lockwood

I used some of the answers given here but just a heads up that

Get-Content -Path Yourfile.log -Tail 30 -Wait 

will chew up memory after awhile. A colleague left such a "tail" up over the last day and it went up to 800 MB. I don't know if Unix tail behaves the same way (but I doubt it). So it's fine to use for short term applications, but be careful with it.


S
StackzOfZtuff

PowerShell Community Extensions (PSCX) provides the Get-FileTail cmdlet. It looks like a suitable solution for the task. Note: I did not try it with extremely large files but the description says it efficiently tails the contents and it is designed for large log files.

NAME
    Get-FileTail

SYNOPSIS
    PSCX Cmdlet: Tails the contents of a file - optionally waiting on new content.

SYNTAX
    Get-FileTail [-Path] <String[]> [-Count <Int32>] [-Encoding <EncodingParameter>] [-LineTerminator <String>] [-Wait] [<CommonParameters>]

    Get-FileTail [-LiteralPath] <String[]> [-Count <Int32>] [-Encoding <EncodingParameter>] [-LineTerminator <String>] [-Wait] [<CommonParameters>]

DESCRIPTION
    This implentation efficiently tails the cotents of a file by reading lines from the end rather then processing the entire file. This behavior is crucial for ef
    ficiently tailing large log files and large log files over a network.  You can also specify the Wait parameter to have the cmdlet wait and display new content
    as it is written to the file.  Use Ctrl+C to break out of the wait loop.  Note that if an encoding is not specified, the cmdlet will attempt to auto-detect the
     encoding by reading the first character from the file. If no character haven't been written to the file yet, the cmdlet will default to using Unicode encoding
    . You can override this behavior by explicitly specifying the encoding via the Encoding parameter.

There's a bug in the current version that is fixed in daily bits. I would recommend grabbing the latest bits and compiling them at least until we get an updated version released.
The version 2.0 takes ages to show the 10 last lines of a 1GB csv file, and differently from Get-Content [filename] | Select-Object -Last 10 it can't be aborted
M
Mikael Sundberg

Just some additions to previous answers. There are aliases defined for Get-Content, for example if you are used to UNIX you might like cat, and there are also type and gc. So instead of

Get-Content -Path <Path> -Wait -Tail 10

you can write

# Print whole file and wait for appended lines and print them
cat <Path> -Wait
# Print last 10 lines and wait for appended lines and print them
cat <Path> -Tail 10 -Wait

E
EvosDeMercile

Probably too late for an answere but, try this one

Get-Content <filename> -tail <number of items wanted> -wait

None of these have a follow options. Which is the 99% use case for using the tail command.
@Andries did you see Ravikanth's answer? You can use -Wait to follow.
Added the flag suggested by @JasonS
h
hajamie

Using Powershell V2 and below, get-content reads the entire file, so it was of no use to me. The following code works for what I needed, though there are likely some issues with character encodings. This is effectively tail -f, but it could be easily modified to get the last x bytes, or last x lines if you want to search backwards for line breaks.

$filename = "\wherever\your\file\is.txt"
$reader = new-object System.IO.StreamReader(New-Object IO.FileStream($filename, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [IO.FileShare]::ReadWrite))
#start at the end of the file
$lastMaxOffset = $reader.BaseStream.Length

while ($true)
{
    Start-Sleep -m 100

    #if the file size has not changed, idle
    if ($reader.BaseStream.Length -eq $lastMaxOffset) {
        continue;
    }

    #seek to the last max offset
    $reader.BaseStream.Seek($lastMaxOffset, [System.IO.SeekOrigin]::Begin) | out-null

    #read out of the file until the EOF
    $line = ""
    while (($line = $reader.ReadLine()) -ne $null) {
        write-output $line
    }

    #update the last max offset
    $lastMaxOffset = $reader.BaseStream.Position
}

I found most of the code to do this here.


Is it true that Get-Content with the -Tail option reads the entire file? On large files it seems OK for me.
I believe it depends on the PS version. I've updated the answer. I was stuck on a server without the ability to install anything at the time, so the above code was useful.
B
Brian Reischl

I took @hajamie's solution and wrapped it up into a slightly more convenient script wrapper.

I added an option to start from an offset before the end of the file, so you can use the tail-like functionality of reading a certain amount from the end of the file. Note the offset is in bytes, not lines.

There's also an option to continue waiting for more content.

Examples (assuming you save this as TailFile.ps1):

.\TailFile.ps1 -File .\path\to\myfile.log -InitialOffset 1000000
.\TailFile.ps1 -File .\path\to\myfile.log -InitialOffset 1000000 -Follow:$true
.\TailFile.ps1 -File .\path\to\myfile.log -Follow:$true

And here is the script itself...

param (
    [Parameter(Mandatory=$true,HelpMessage="Enter the path to a file to tail")][string]$File = "",
    [Parameter(Mandatory=$true,HelpMessage="Enter the number of bytes from the end of the file")][int]$InitialOffset = 10248,
    [Parameter(Mandatory=$false,HelpMessage="Continuing monitoring the file for new additions?")][boolean]$Follow = $false
)

$ci = get-childitem $File
$fullName = $ci.FullName

$reader = new-object System.IO.StreamReader(New-Object IO.FileStream($fullName, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [IO.FileShare]::ReadWrite))
#start at the end of the file
$lastMaxOffset = $reader.BaseStream.Length - $InitialOffset

while ($true)
{
    #if the file size has not changed, idle
    if ($reader.BaseStream.Length -ge $lastMaxOffset) {
        #seek to the last max offset
        $reader.BaseStream.Seek($lastMaxOffset, [System.IO.SeekOrigin]::Begin) | out-null

        #read out of the file until the EOF
        $line = ""
        while (($line = $reader.ReadLine()) -ne $null) {
            write-output $line
        }

        #update the last max offset
        $lastMaxOffset = $reader.BaseStream.Position
    }

    if($Follow){
        Start-Sleep -m 100
    } else {
        break;
    }
}

s
starshine wang

try Windows Server 2003 Resource Kit Tools

it contains a tail.exe which can be run on Windows system.

https://www.microsoft.com/en-us/download/details.aspx?id=17657


C
Clark Mercer

I have a useful tip on this subject concerning multiple files.

Following a single log file (like 'tail -f' in Linux) with PowerShell 5.2 (Win7 and Win10) is easy (just use "Get-Content MyFile -Tail 1 -Wait"). However, watching MULTIPLE log files at once seems complicated. With PowerShell 7.x+ however, I've found an easy way by using "Foreach-Object -Parrallel". This performs multiple 'Get-Content' commands concurrently. For example:

Get-ChildItem C:\logs\*.log | Foreach-Object -Parallel { Get-Content $_ -Tail 1 -Wait }

P
Patrick Stalph

There have been many valid answers, however, none of them has the same syntax as tail in linux. The following function can be stored in your $Home\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 for persistency (see powershell profiles documentation for more details).

This allows you to call...

tail server.log
tail -n 5 server.log
tail -f server.log
tail -Follow -Lines 5 -Path server.log

which comes quite close to the linux syntax.

function tail {
<#
    .SYNOPSIS
        Get the last n lines of a text file.
    .PARAMETER Follow
        output appended data as the file grows
    .PARAMETER Lines
        output the last N lines (default: 10)
    .PARAMETER Path
        path to the text file
    .INPUTS
        System.Int
        IO.FileInfo
    .OUTPUTS
        System.String
    .EXAMPLE
        PS> tail c:\server.log
    .EXAMPLE
        PS> tail -f -n 20 c:\server.log
#>
    [CmdletBinding()]
    [OutputType('System.String')]
    Param(
        [Alias("f")]
        [parameter(Mandatory=$false)]
        [switch]$Follow,

        [Alias("n")]
        [parameter(Mandatory=$false)]
        [Int]$Lines = 10,

        [parameter(Mandatory=$true, Position=5)]
        [ValidateNotNullOrEmpty()]
        [IO.FileInfo]$Path
    )
    if ($Follow)
    {
        Get-Content -Path $Path -Tail $Lines -Wait
    }
    else
    {
        Get-Content -Path $Path -Tail $Lines
    }
}

J
Jesse

Very basic, but does what you need without any addon modules or PS version requirements:

while ($true) {Clear-Host; gc E:\test.txt | select -last 3; sleep 2 }


That's brutal on large files.
My workaround was: while($true) { Clear-Host; Get-Content <filename> -tail 40; sleep 1 } :)
G
George Ogden

It is possible to download all of the UNIX commands compiled for Windows from this GitHub repository: https://github.com/George-Ogden/UNIX


Hello. Welcome to SO. Before you answer a question take notice of the 12 other answers, 3 having scores over 100.