Repository navigation
CLI: Stdin input does not implicitly become pipeline input #9497
Description
Currently, stdin input streamed to pwsh from the outside:
-
does not automatically become pipeline input;
- accessing stdin input currently requires constructing a separate pipeline in PowerShell code using
$Input
- accessing stdin input currently requires constructing a separate pipeline in PowerShell code using
-
only automatically becomes input to interactive prompts, such as
Read-Hostcalls or the automatic prompts displayed when mandatory arguments are missing.
This has the following implications:
You cannot directly pipe data to PowerShell commands from the outside:
Contrast the following two commands:
# Bash:
# stdin input is "relayed" to the 'wc' utility
$ printf 'one\ntwo' | bash -c 'wc'
2# PowerShell:
PS> printf 'one\ntwo' | pwsh -noprofile -c 'Measure-Object -Word | % Words'
0 # !! Measure-Object did NOT receive stdin input.
# Explicit reference to $Input in an internal pipeline is required:
PS> printf 'one\ntwo\n' | pwsh -noprofile -c '$Input | Measure-Object -Word | % Words'
2printf 'one\ntwo\n' | pwsh -noprofile -c 'Measure-Object -Word'
If you're calling an advanced script that binds pipeline input, it cannot be invoked with just -File, because it won't receive pipeline input:
# Create an advanced script that echoes each pipeline input enclosed in [...]
'[CmdletBinding()] param([Parameter(ValueFromPipeline)] $var) process { "[$var]" }' > t.ps1
# Call from either Bash or PowerShell (same behavior).
PS> echo hi | pwsh -noprofile -file t.ps1
[] # !! t.ps1 did NOT receive stdin input
# The workaround is again non-obvious and cumbersome:
PS> echo hi | pwsh -noprofile -command '$Input | ./t.ps1'
[hi] # OKSimilarly, if you author a pipeline-binding advanced script as shebang-line-based script on Unix, it cannot receive stdin input directly:
# Create a shebang-line-based script...
@'
#!/usr/bin/env pwsh
[CmdletBinding()] param([Parameter(ValueFromPipeline)] $var) process { "[$var]" }
'@ > foo
# ... and make it executable.
chmod +x foo
# Call from either Bash or PowerShell (same behavior).
PS> 1..2 | ./foo
[] # !! Pipeline input wasn't bound, not even with pipeline input from PowerShell.PowerShell's behavior here is at odds with traditional shells and arguably makes the typical use case unnecessarily difficult.
Note: I'm unclear on whether this can be fixed without breaking existing behavior.
The challenge is to know when to provide stdin input via an implied PowerShell pipeline, and when to use stdin input line by line, on demand, to respond to interactive prompts.
E.g., the following currently works - the two distinct Read-Host prompts are "answered" by 1 stdin input line each:
$ 'one\ntwo\n' | pwsh -noprofile -c 'read-host first; get-date; read-host second'
first: one
one
Monday, April 29, 2019 10:52:04 AM
second: two
twoEnvironment data
PowerShell Core 6.2.0
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
bergmeister commentedon Nov 14, 2019
I am hit by the same problem, I have to work with the databricks CLI, which has a terrible UX that requires me to pipe
stdinto the the native binary and it seems I have to usebashfor now as I could not translate the following toPowerShell:I've tried piping a string to it
with variations of
${newline}or also trying to pipe an array with no luck....mklement0 commentedon Nov 14, 2019
It sounds like you're describing the opposite direction: piping to an external program.
In my experience that usually does work; e.g.:
Ditto:
Is there something special about the
databricksutility?Note that, as usual, PowerShell uses the
$OutputEncodingcharacter encoding for sending text to external programs, which in PS Core is now UTF-8.bergmeister commentedon Nov 14, 2019
Thanks for the info. Yes, it is piping to an external binary that expects input via stdin as it prompts.
I decided to use authentication via environment variables on the PowerShell process level instead
mvalikar commentedon Dec 18, 2019
Christoph Bergmeister (@bergmeister) Can you share code how you are able to authenticate Databricks through Environment variables. I am pretty much struck in this case
bergmeister commentedon Dec 18, 2019
mvalikar You set the DATABRICKS_HOST and DATABRICKS_TOKEN environment variables as per docs, I suggest to set them only for the current process for security purposes and clean them up at the end
https://docs.databricks.com/dev-tools/cli/index.html
jiasli commentedon Sep 14, 2022
The same happens when directly calling
ps1.Bash
test.shhasWe can call it interactively:
When piping to
test.sh, the innerpythonreceives fromstdinautomatically:PowerShell
test.ps1have the same content:Interactive input works:
But when piping to
test.ps1, the innerpythoncan't receive fromstdinand waits for user input.If we explicitly pipe
$inputtopythonintest.ps1:Now piping works:
but interactive input fails:
jiasli commentedon Sep 14, 2022
A workaround I can think of is to check if
$inputis empty:$inputis not empty, pipe it to python$inputis empty, don't pipe it to python and leave python to readstdinfrom TTY(We need to save
$inputto another variable$stdinsince$inputis an enumerator and can be accessed only once.)This solution requires additional efforts and is cumbersome. It is also fundamentally different from Bash since we are deciding whether to connect
stdintopythonbased on the content ofstdin, instead of whether piping is used.In Bash, the behavior can be expressed as:
shscript is directly called, the inner executable'sstdinis connected to TTYshscript is called with pipe|, the output of the source is sent to inner executable'sstdinFor example, in Bash, even when piping is used with empty content, it leads to failure:
but the above workaround doesn't work as expected for empty pipe and falls back to interactive input:
So questions are:
$input?$inputis empty (pipe is not used) or$inputis empty string (an empty pipe is used)?stdincontains binary stream?mklement0 commentedon Sep 14, 2022
Use
$MyInvocation.ExpectingInputIf it's OK to collect all input up front:
Otherwise:
Note that without pipeline input, the
processblock is also called, namely exactly once.PowerShell currently has no support for binary data in its pipelines when communicating with external programs - everything is treated as text - both on sending and receiving - see
jiasli commentedon Sep 15, 2022
Michael Klement (@mklement0), thanks a lot for the insights.
This solution you mentioned doesn't seem to work. It returns
Trueeven with emptystdin(scenario 2):The other solution also has some unexpected behavior:
The expected result is to return
Falseif pipeline is not used (scenario 1)Trueis pipeline is used (scenario 2, 3)(Or otherwise - doesn't really matter.)
mklement0 commentedon Sep 15, 2022
I answered your questions in isolation, You need to combine your logic with
$MyInvocation.ExpectingInputas needed, to distinguish the pipeline from the non-pipeline scenarios.Apart from that, I see no unexpected behavior.
& { $allLines = @($Input); $allLines.Count -eq 0 }is designed to return$trueif there is either no input from the pipeline at all or if that input is empty.& { begin { $emptyInput = $true } process { $emptyInput = $false } end { $emptyInput } }is designed to return$trueonly if there's pipeline input and it is empty.pcgeek86 commentedon Jan 14, 2023
I'm running into the same limitation. I'm trying to use PowerShell to write a script to handle the
pre-pushgit hook.Since I am unable to grab stdin from PowerShell, I am unable to process this data.
Workaround
The only workaround that I've seen, to read StdIn from PowerShell, is to use
$Host.UI.ReadLine(), however that has a bug where it results in duplicate output. Also, theReadLine()method hangs script execution. If you reach the last line of input, and you callReadLine()again, your script will completely hang.pcgeek86 commentedon Nov 16, 2023
Yeah, let's just waste all the time and effort that the community has put into reporting bugs and usability issues. Good plan as usual, Microsoft.
fireflycons commentedon Dec 4, 2023
Trevor Sullivan (@pcgeek86) I had a similar issue while creating a mechanism to handle powershell hooks natively in git for Windows. The trick is to grab
stdindirectly and read from it, as shown under the cookbooks heading here https://github.com/fireflycons/git-hook-dispatcher