Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
646 views
in Technique[技术] by (71.8m points)

batch file - Getting stdin into the Powershell stream

The following script works well when the filename is specified on the command line.

tail.bat
@echo off
set "COUNT=%1"
set "COUNT=%COUNT:-=%"
set "FILENAME=%~2"
powershell "Get-Content %FILENAME% -Last %COUNT%"

However, what I need is to be able to pipe the text into Get-Content from stdin. I would like to write the following to get the last three Subversion tags assigned to the project. What can I do to get the source to Get-Content to be stdin?

svn ls svn://ahost/arepo/aproject/tags | call tail.bat -3

NB: I am not permitted to install any helpful tools like tail from the outside. It has to be done with the programs already available on the machine.

Update:

@mklement0 provided the answer. From that, I added code to use a default COUNT value of 10 if it is not provided. This matches the UNIX/Linux way.

@echo off

SET "COUNT=%~1"
IF "%COUNT:~0,1%" == "-" (
    SET "COUNT=%COUNT:~1%"
    SHIFT
) ELSE (
    SET "COUNT=10"
)
SET "FILENAME=%~1"

if "%FILENAME%" == "" (
    powershell -noprofile -command "$Input | Select-Object -Last %COUNT%"
) else (
    powershell -noprofile -command "Get-Content "%FILENAME%" -Last %COUNT%"
)

EXIT /B
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Rewrite tail.bat as follows:

@echo off

set "COUNT=%1"
set "COUNT=%COUNT:-=%"
set "FILENAME=%~2"

if "%FILENAME%"=="" (
  powershell -noprofile -command "$Input | Select-Object -Last %COUNT%"
) else (
  powershell -noprofile -command "Get-Content "%FILENAME%" -Last %COUNT%"
)

This will make the PowerShell CLI read stdin input via the automatic $input variable, if no filename argument was passed, courtesy of this answer.

Example:

C:> (echo one & echo two & echo three) | tail.bat -2
two
three

Note:

  • While PowerShell generally sends through the pipeline and outputs objects of any kind, its interface to the outside world invariably involves strings.

  • Thus, given that $Input is an enumerator that represents outside stdin input, we can be sure that it enumerates the input text lines (as strings) one by one, so all we need is to select the lines of interest, which is why piping to Select-Object is sufficient.

  • By contrast, reading a file by name in PowerShell requires Get-Content (which, incidentally, also sends the input file's lines one by one through the pipeline, unless you also specify -Raw); since Get-Content has tail functionality built in, via parameter -Tail (and its alias -Last), it is all that is needed here.

  • CAVEAT: Character decoding on input and re-encoding on output is involved when PowerShell talks to the outside world:

    • If you're only ever dealing with ASCII-encoded input (single-byte characters with code points ranging between 0 - 127), you needn't worry.

    • Otherwise, prepare for a world of pain - see below for details.


Character decoding/re-encoding issues:

  • Assuming that PowerShell recognizes your input encoding (see below), the output encoding is invariably what the console window's assigned encoding is; by default, unfortunately, that is the OEM codepage (e.g., the "DOS" code page CP437 on US-English systems), reflected in PS as [Console]::OutputEncoding.

  • Thus, with properly recognized input, if you print to the console, things will look OK, but if you capture the output in a file, you'll end up with an OEM-codepage-encoded file, which is probably undesired.

  • If feasible, you could fundamentally set up your console windows to use your codepage (input and output encoding) of choice (using chcp), but trying to change the encoding ad-hoc in your script is, unfortunately, not an option.
    Note that using UTF-8 - codepage 65001 - only works if you configure your console windows to use one of the TT (TrueType) fonts.

  • As written above, the set of input encodings that are properly recognized is unfortunately limited to the following, based on the default input encoding (which is also the OEM codepage, reflected in PS as [Console]::InputEncoding; remember: input will be re-encoded on output):

    • ASCII input (re-encoding on output will by default preserve this encoding)
    • UTF-16 LE input with a BOM (which is what PowerShell calls Unicode, subject to re-encoding to something potentially different on output)
  • You could hard-code an expected input encoding by adding -Encoding <enc> to the Get-Content call (which expects the Windows default codepage encoding by default), but to do the same for stdin input (as reflected in $Input) would be non-trivial.

    • E.g., with the default input encoding, if you explicitly wanted to interpret the input as UTF-8 (again, note that on output [Console]::OutputEncoding encoding is applied):
      powershell -noprofile -command "$Input | % { [text.encoding]::utf8.GetString([Console]::InputEncoding.GetBytes($_)) } | Select-Object -Last %COUNT%"

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...