Capture output from a PowerShell command in a batc

2019-02-21 05:00发布

问题:

This PowerShell code selects the correct value for var5 from this string. The desired result it "Alert Raised".

PS C:\src\t> $s = 'Status 58   var5=Alert Raised on: March'
PS C:\src\t> $s
Status 58   var5=Alert Raised on: March
PS C:\src\t> $s | Where-Object { $_ -match '.*var5=(.*)\s+\w+:' } | ForEach-Object { $Matches[1] }
Alert Raised

However, using the same powershell code in a cmd shell script produces a different result. Why is that? Is there something that needs to be escaped for the cmd shell?

C:>type mat002-annex.bat
@ECHO OFF
SET "S=Status 58   var5=Alert Raised on: March"
ECHO S is set to %S
FOR /F %%a IN ('powershell -NoLogo -NoProfile -Command ^
    " '%S%' | Where-Object { $_ -match '.*var5=(.*)\s+\w+:' } | ForEach-Object { $Matches[1] } "') DO (ECHO Result is "%%a")

C:>mat002-annex.bat
S is set to S
Result is "Alert"

回答1:

PetSerAl, as he often does, has provided the crucial pointer in a comment:

cmd.exe's for /f loop by default breaks its input lines into tokens by whitespace, so that for /f %%a in (...) do only places the first such token in variable %%a.

This default behavior can be modified with an options string: for /f "<options>" %%a in (...) do.
Running for /? from a cmd.exe prompt describes its syntax:

    eol=c           - specifies an end of line comment character
                      (just one)
    skip=n          - specifies the number of lines to skip at the
                      beginning of the file.
    delims=xxx      - specifies a delimiter set.  This replaces the
                      default delimiter set of space and tab.
    tokens=x,y,m-n  - specifies which tokens from each line are to
                      be passed to the for body for each iteration.
                      This will cause additional variable names to
                      be allocated.  The m-n form is a range,
                      specifying the mth through the nth tokens.  If
                      the last character in the tokens= string is an
                      asterisk, then an additional variable is
                      allocated and receives the remaining text on
                      the line after the last token parsed.
    usebackq        - specifies that the new semantics are in force,
                      where a back quoted string is executed as a
                      command and a single quoted string is a
                      literal string command and allows the use of
                      double quotes to quote file names in
                      file-set.

If you want each input line to be captured in a single variable, you have two options:

Note: The examples below use the following PowerShell command line, which simply outputs a string literal with multiple leading, inner, and trailing spaces,   one two  .

powershell -command " '   one   two   ' "

To make quoting easier, option usebackq is used below, which allows embedding the command in `...`.

Also note that it's good practice to add -NoProfile to powershell.exe's command line to prevent unnecessary - and potentially behavior-changing - loading of a user's profile; I've omitted it below for brevity.


"delims=": preserving the line exactly as-is

@echo off

for /f "usebackq delims=" %%a in (`powershell -command " '   one   two   ' "`) do echo Result is "%%a"

The above yields:

Result is "   one   two   "

Note how the leading and trailing spaces were preserved too.

Important: for delims= to be recognized as deactivating delimiter-based (separator-based) parsing, it must be placed at the very end of the options string.


"tokens=*": trimming leading whitespace

@echo off

for /f "usebackq tokens=*" %%a in (`powershell -command " '   one   two   ' "`) do echo Result is "%%a"

The above yields:

Result is "one   two   "

Note how the leading spaces were trimmed.

It is curious that the trimming is only applied to leading whitespace, and there appears to be no direct way to trim trailing whitespace as well.



回答2:

You don't need the for loop in the batch file.

@ECHO OFF
SET "S=Status 58   var5=Alert Raised on: March"
powershell -NoLogo -NoProfile -Command " '%S%' | Where-Object { $_ -match '.*var5=(.*)\s+\w+:' } | ForEach-Object { $Matches[1] } "

The above gives the expected result.