Reputation: 4438
I am trying to write a simple text manipulation filter in powershell (5.1 on W10-64 bits). My goal is to use it as part of a pipeline in a batch (.bat), e.g.:
command1.exe | powershell "...my filter..." | command2.exe
I am facing a weird problem: "Read-Host" cmdlet always echoes its input. Example:
echo 1234 | powershell "$a=Read-Host"
produces
1234
(NOTE: see PowerShell: Disable echo in Read-Host).
How can I avoid this unexpected echo? Is there any alternate way to read STDIN in powershell?
NOTE2: I know I could do all the pipeline work inside powershell, but for many reason this doesn't fit my case. What I want is a "generic filter" suitable to be part of a pipeline in a ".bat". And, no, a temporary file to store intermediate data is not an option.
EDIT: as noted by "Compo", MS documentation says this about Read_Host:
You cannot pipe input to this cmdlet
So there is no point in trying to make it work.
EDIT2: As explainded by "mklement0", "[Read_Host] does accept pipeline, i.e. stdin input from the outside, such as when provided to PowerShell's CLI". The real problem is that it is intended to simplify user input, not to give generic access to stdin.
The actual question is: Is there any alternate way to read STDIN in powershell?
Upvotes: 1
Views: 796
Reputation: 439852
This answer complements Mathias R. Jessen's helpful answer, which shows the right way to handle data passed to the PowerShell CLI via the pipeline (stdin).
Does not accept pipeline input from inside a PowerShell session, which is what the documentation refers to.
Does accept pipeline, i.e. stdin[1] input from the outside, such as when provided to PowerShell's CLI.
However, this comes with limitations:
As in interactive use, Read-Host
only ever reads one line.
Because interactive behavior is emulated even with stdin input, the input is invariably echoed to the display as well, which cannot be suppressed.
Unlike all other cmdlets (as well as expressions) - which require the explicit use of $input
in order to process stdin data, as shown in Mathias' answer - Read-Host
implicitly reads stdin input.
Read-Host
calls or use $input
to relay it to other commands.This leaves only one legitimate use case for piping strings to Read-Host
from outside PowerShell:
Providing automated responses to a given command's / script's interactive prompts; e.g.:
C:\>echo y | powershell -c "$response = Read-Host 'Continue (y/n)'; if ($response.Trim() -ne 'y') { exit 2 }; 'continuing...'"
Continue (y/n): y
continuing...
See this answer for more information.
Conversely, this means that you shouldn't use Read-Host
to collect stdin input in a variable, given that only one line is read and given the unwanted echoing behavior.
Typically, you simply relay stdin input by command-internally piping $input
to other commands, as shown in Mathias' answer; to explicitly collect all stdin input in a variable up front, you can apply $()
, the subexpression operator (or @()
, the array-subexpression operator) to the $input
enumerator:
C:\>(echo abc & echo def) | powershell "$allInput=$($input); \"$($allInput.Count) lines received.\""
2 lines received.
[1] stdin, the standard input stream, is an OS-level concept, as are the standard output streams, stdout and stderr. By contrast, PowerShell implements its own system of streams: the 6(!) output streams are described in the conceptual about_Redirection
topic; the success output stream (number 1
) and the error stream (2
) are the analogs to stdout and stderr, respectively. However, there is no stdin analog as such: (stream) input to other commands is only ever provided via the pipeline (and not also via the <
operator supported in other shells, which PowerShell does not support).
Upvotes: 3
Reputation: 174835
Is there any alternate way to read STDIN in powershell?
That's already happening, you just need to know how to hook it - like using the $input
automatic enumerator variable for example:
C:\> echo "camelCase" |powershell -Command "$input |ForEach-Object ToUpper"
CAMELCASE
Since PowerShell and CMD use slightly different syntaxes and can be a bit of a pain to escape, I'd strongly recommend putting your filter(s) in script files and invoking those:
# myUpperCaseFilter.ps1
$input |ForEach-Object ToUpper
# script.bat
command1.exe |powershell -File path\to\myUpperCaseFilter.ps1 |command2.exe
Upvotes: 5