Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 693
lit Avatar asked Jul 29 '16 21:07

lit


People also ask

How to prompt input in PowerShell?

Use the Read-Host to Prompt for User Input in PowerShell The -Prompt parameter is used to specify a text to provide the information about what to input. It appends a colon : to the text you enter. The Read-Host pauses execution and receives input. When a user enters the value at the prompt, it returns that same value.

What is read-Host in PowerShell?

The Read-Host cmdlet reads a line of input from the console (stdin). You can use it to prompt a user for input. Because you can save the input as a secure string, you can use this cmdlet to prompt users for secure data, such as passwords. Note. Read-Host has a limit of 1022 characters it can accept as input from a user ...

How to write PowerShell script output to a text file?

There are a couple of ways to write the output of PowerShell to a file. The most common ways are to use the Out-File cmdlet or the redirection operator > . Other options are to use the Set-Content and Add-Content cmdlet.

How do I get the content of a file in PowerShell?

The Get-Content cmdlet gets the content of the item at the location specified by the path, such as the text in a file or the content of a function. For files, the content is read one line at a time and returns a collection of objects, each of which represents a line of content.


1 Answers

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%"
like image 92
mklement0 Avatar answered Sep 22 '22 07:09

mklement0