Search code examples
powershellterminaltee

How to simultaneously capture external command output and print it to the terminal


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?


Solution

  • 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')
        
        • GitHub issue #19827 is a green-lit, but as yet (as of PowerShell (Core) 7.3.x) unimplemented improvement that will allow simplifying
          ($IsWindows ? '\\.\CON': '/dev/tty') to -Host.
      • 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.