3

Can I pipe back from:

$OUTPUT = $(flutter build ios --release --no-codesign | tail -1)

I would like to get both the last line from the build AND show progress, something like

$OUTPUT = $(flutter build ios --release --no-codesign | out | tail -1)

where the hypothetical out utility would also send the output to the terminal.

Do you know how?

mklement0
  • 382,024
  • 64
  • 607
  • 775
Chris G.
  • 23,930
  • 48
  • 177
  • 302
  • That should work as is in osx or linux, even without the `$( )`, assuming there's an out command. – js2010 Jan 29 '20 at 16:46
  • 1
    @js2010 `$VAR=...` is a syntax error in bash, variable names in assignments must not be preceded by a dollar sign (the spaces would also be problematic) – Aaron Jan 29 '20 at 17:06

4 Answers4

2

Note:

  • On Unix-like platforms, with external-program output, js2010's elegant tee /dev/tty solution is the simplest.

  • The solutions below, which also work on Windows, may be of interest for processing external-program output line by line in PowerShell.

  • A general solution that also works with the complex objects that PowerShell-native commands can output, requires different approaches:

    • In PowerShell (Core) 7+, use the following:

      # PS v7+ only. Works on both Windows and Unix
      ... | Tee-Object ($IsWindows ? 'CON' : '/dev/tty')
      
    • In Windows PowerShell, where Tee-Object unfortunately doesn't support targeting CON, a proxy function that utilizes Out-Host is required - see this answer.


A PowerShell solution (given that the code in your question is PowerShell[1]):

I'm not sure how flutter reports its progress, but the following may work:

If everything goes to stdout:

$OUTPUT = flutter build ios --release --no-codesign | % {
  Write-Host $_ # print to host (console)
  $_  # send through pipeline
} | select -Last 1

Note: % is the built-in alias for ForEach-Object, and select the one for Select-Object.

If progress messages go to stderr:

$OUTPUT = flutter build ios --release --no-codesign 2>&1 | % {
  Write-Host $_.ToString() # print to host (console)
  if ($_ -is [string]) { $_ }  # send only stdout through pipeline
} | select -Last 1

[1] As evidenced by the $ sigil in the variable name in the LHS of an assignment and the spaces around =
($OUTPUT = ), neither of which would work as intended in bash / POSIX-like shells.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

I assume you mean bash because to my knowledge there is no tail in powershell.

Here's how you can see a command's output while still capturing it into a variable.

#!/bin/bash

# redirect the file descriptor 3 to 1 (stdout)
exec 3>&1

longRunningCmd="flutter build ios --release --no-codesign"

# use tee to copy the command's output to file descriptor 3 (stdout) while 
# capturing 1 (stdout) into a variable
output=$(eval "$longRunningCmd" | tee >(cat - >&3) )

# last line of output
lastline=$(printf "%s" "$output" | tail -n 1)

echo "$lastline"
Jonas Eberle
  • 2,835
  • 1
  • 15
  • 25
  • Hopefully the OP will clarify, but the code in the question is PowerShell; while PowerShell itself has no `tail` command, running PowerShell _Core_ (v6+) on macOS or Linux allows you to use the standard `tail` utility, and even on Windows there are `tail` ports available. – mklement0 Jan 29 '20 at 16:58
1

I use write-progress in the pipeline. In order to keep readable pipeline, I wrote a function

function Write-PipedProgress{ <#

.SYNOPSIS
    Insert this function in a pipeline to display progress bar to user

.EXAMPLE
    $Result = (Get-250Items | 
        Write-PipedProgress -PropertyName Name -Activity "Audit services" -ExpectedCount 250 |
        Process-ItemFurther)

>

[cmdletBinding()]
param(
    [parameter(Mandatory=$true,ValueFromPipeline=$true)]
    $Data,
    [string]$PropertyName=$null,
    [string]$Activity,
    [int]$ExpectedCount=100
    )

begin {
    Write-Verbose "Starting $($MyInvocation.MyCommand)"
    $ItemCounter = 0
}
process {
    Write-Verbose "Start processing of $($MyInvocation.MyCommand)($Data)"

    try {
        $ItemCounter++
        # (3) mitigate unexpected additional input volume"
        if ($ItemCounter -lt $ExpectedCount) {
            $StatusProperty = if ($propertyName) { $Data.$PropertyName } > > else { ""}
            $StatusMessage = "Processing $ItemCounter th $StatusProperty"
            $statusPercent = 100 * $ItemCounter / $ExpectedCount
            Write-Progress -Activity $Activity -Status $StatusMessage -> > PercentComplete $statusPercent
        } else {
            Write-Progress -Activity $Activity -Status "taking longer than expected" -PercentComplete 99
        }

        # return input data to next element in pipe
        $Data
    
    } catch {
        throw
    }
    finally {
        Write-Verbose "Complete processing of $Data in > $($MyInvocation.MyCommand)"
    }

}
end {
    Write-Progress -Activity $Activity -Completed
    Write-Verbose "Complete $($MyInvocation.MyCommand) - processed $ItemCounter items"
}

}

Hope this helps ;-)

Community
  • 1
  • 1
Pierre
  • 11
  • 2
  • 2
    Please [format your code and sample input/output properly](http://meta.stackexchange.com/a/22189/248777). – mklement0 Jan 29 '20 at 17:12
1

I believe this would work, at least in osx or linux powershell (or even Windows Subsystem for Linux) that have these commands available. I tested it with "ls" instead of "flutter". Is there actually an "out" command?

$OUTPUT = bash -c 'flutter build ios --release --no-codesign | tee /dev/tty | tail -1'

Or, assuming tee isn't aliased to tee-object. Actually, tee-object would work too.

$OUTPUT = flutter build ios --release --no-codesign | tee /dev/tty | tail -1

It would work with the $( ) too, but you don't need it. In powershell, it's used to combine multiple pipelines.

js2010
  • 23,033
  • 6
  • 64
  • 66