Search code examples
powershellpipelinestdinio-redirectiontail

Equivalent of Unix/Linux tail command for Powershell - that supports pipes?


I'm aware that Get-Content has a -Tail parameter, but as far as I can tell, there's simply no way to get it to accept piped input.

So how do I get something like this to work? I just want to be able to select first X or last X lines from any preceding command, without the hassle of creating temp files simply so I can grab the first/last lines from it.

# This won't work as Get-Content demands a -Path to a file
Get-Item .\* | Get-Content -Tail 10

Some added finagling will likely be required depending on whether the first command is providing objects or strings as output; if you can include such intermediary steps in your reply, that'd be awesome. I expect something like Out-String -stream might be the done thing?

I've scoured the internet and came up empty-handed. Thanks in advance for any help.


Solution

  • PowerShell's pipeline is based on objects rather than lines of text, and Mathias R. Jessen's helpful answer has the object-oriented angle covered:

    • Select-Object -First / -Last selects only the first / last N objects - whatever their type - and then renders them to the display using PowerShell's rich for-display output-formatting system.

    • Given that the formatting of a single object can span multiple lines (consider the display output when you submit $PSVersionTable), there's no guarantee that the output will be limited to N lines.

    If, by contrast, your intent is to limit the output to a given number of display lines - irrespective of how many objects the result corresponds to:

    • Insert an Out-String call with the -Stream switch parameter before the Select-Object call. PowerShell v5+ even has a built-in wrapper function for this, oss.

    • This performs the for-display formatting to a string, with -Stream then emitting the lines of the result one by one.


    A simple example that illustrates the two approaches and how they differ:

    # Create 10 sample objects that render with Format-List
    # (each property on its own line)
    $objects = 
      1..10 | ForEach-Object { [pscustomobject] @{ a=1*$_; b=2*$_; c=3*$_; d=4*$_; e=5*$_ } }
    
    Write-Verbose -Verbose 'First 2 *objects*:'
    # Select the first 2 *objects*, which are then rendered, 
    # each with multiple lines.
    $objects | Select-Object -First 2
    
    Write-Verbose -Verbose 'First 2 *lines*:'
    # Create the for-display representation as a string up front, 
    # then select the first 2 *lines*
    $objects | oss | Select-Object -First 2
    

    This outputs the following:

    VERBOSE: First 2 *objects*:
    
    a : 1
    b : 2
    c : 3
    d : 4
    e : 5
    
    a : 2
    b : 4
    c : 6
    d : 8
    e : 10
    
    VERBOSE: First 2 *lines*:
    
    a : 1
    

    Note:

    • PowerShell's output formatting often involves a leading and trailing empty line, which is why the output from the oss-based command starts with an empty line and only shows one property value (you could address that by eliminating blank lines as follows:
      $objects | oss | Where-Object { $_.Trim() } | Select-Object -First 2)

    • An important use case for oss is quick-and-dirty text searches in object output via Select-String:

      • E.g., the following would show only the lines that contain the digit (string) 3:

        $objects | oss | Select-String '3'
        
        • Note that this technique is meant for convenient, interactive use only; for robustness, especially in scripts, using OO techniques for filtering, notably via Where-Object, is the right approach.
      • Sadly, Select-String doesn't have the oss stringification logic built in, even though it arguably should - see GitHub issue #10726.