Reputation: 131666
Suppose I'm running:
some_command_here | fl foo*
and I'm getting:
a fool and his money are soon parted
foot in the door
snafoo
foo-bar
Now, I want to filter out the empty lines. How do I do this?
Upvotes: 3
Views: 826
Reputation: 438378
tl;dr
# Remove empty lines from the fl (Format-List) output
# (Collects all output in memory first.)
@(Get-Date | fl | oss) -ne ''
# Alternatively, spelled out:
@(Get-Date | Format-List | Out-String -Stream) -ne ''
# (Streaming alternative.)
Get-Item /, $PSHOME | fl | oss | ? { [bool] $_ }
# Alternatively, spelled out:
Get-Item /, $PSHOME | Format-List | Out-String -Stream |
Where-Object { '' -ne $_ }
Get-Date
and Get-Item /, $PSHOME
are sample commands.
fl
is a built-in alias of the Format-List
cmdlet.
oss
is a built-in function that wraps Out-String
with the -Stream
switch to produce line-by-line output.
?
is a built-in alias of the Where-Object
cmdlet.
Read on for background information.
Note:
The answer below, in line with the question, focuses on filtering command output via its for-display formatted string representations, as produced by PowerShell's formatting system.
grep
or findstr.exe
, but on Windows there are character-encoding pitfalls.By contrast, if you're looking to extract data from command output, rely on PowerShell's object-oriented nature and query / return properties of the input objects, using cmdlets such as Where-Object
, ForEach-Object
, and Select-Object
; e.g.:
# Return those input objects whose .prop value starts with 'foo'
[pscustomobject] @{ prop = 'foo' },
[pscustomobject] @{ prop = 'other' },
[pscustomobject] @{ prop = 'fool' } |
Where-Object prop -like foo*
Format-*
cmdlets such as Format-List
(fl
) output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-*
cmdlets to format data for display, never for subsequent programmatic processing.
If you really want to filter the for-display output-formatting that Format-List
emits:
Note:
grep
or findstr.exe
is actually simpler, although there can be character-encoding issues - see the bottom section.$sampleInput = [pscustomobject] @{ foo=1 },
[pscustomobject] @{ other=2 },
[pscustomobject] @{ fool=3 }
$sampleInput |
Format-List -Property foo* |
Out-String -Stream | # alternatively, use: oss
Where-Object { '' -ne $_.Trim() }
Note: Where-Object { [bool] $_ }
is enough to remove empty lines (but would keep blank lines, i.e. those composed of intra-line whitespace only), using PowerShell's to-Boolean coercing logic - see the bottom section of this answer for details.
The above lists only properties whose name starts with foo
(as fl foo*
does in your question), along with their values, and eliminates empty and blank lines from the formatted output and therefore outputs:
foo : 1
fool : 3
Note:
Since Format-List
, as noted, produces formatting instructions, not text, Out-String
-Stream
is needed to convert those instructions into a stream of text lines representing the rendering of these formatting instructions, i.e. the output you would normally see in the console.
The Where-Object
then eliminates empty or blank (all-whitespace) lines from the resulting output, by testing if the whitespace-trimmed (.Trim()
) line at hand ($_
) is non-equal to the empty string (''
); note that you could omit the '' -ne
part, because PowerShell implicitly treats any non-empty string as $true
in a Boolean context (see this answer for a summary of PowerShell's implicit to-Boolean conversion rules)
If, by contrast, you want to filter the formatted output produced by Format-List
to those lines that contain substring foo
anywhere on the line, i.e. irrespective of whether that substring is in the property name, its value, or even in a header line:
$sampleInput |
Format-List |
Out-String -Stream |
Select-String foo |
ForEach-Object Line
Note:
Unfortunately, Out-String -Stream
is needed here too, because, as of PowerShell 7.2, Select-String
does not automatically search the for-display representations of non-text / formatting-objects input - see GitHub issue #10726.
To ease the pain somewhat, PowerShell ships with convenience function oss
, which is short for Out-String -Stream
.
In PowerShell (Core) 7+, Select-String
now supports the -Raw
switch to pass only the matching lines (stringified input objects) through, without the default Microsoft.PowerShell.Commands.MatchInfo
wrapper; that is, you can then use Select-String -Raw foo
, without also needing ForEach-Object Line
.
Searching through formatted for-display output using native utilities, such as grep
and findstr.exe
:
You can take advantage of the fact that when PowerShell sends input to external programs, it essentially performs Out-String -Stream
implicitly:
PowerShell only "speaks text" when communicating with external programs, so it must send a string representation when piping (non-string) data to an external program. By default, it uses the same for-display representation you would see in the PowerShell console, sent line by line.
It follows that extra work is needed if you want to send data that is suitable for programmatic processing: you must then use a structured text format, such as JSON (via ConvertTo-Json
) or CSV (via ConvertTo-Csv
)
In your case, the implication is that you can simply pipe to a standard string-search utility such as grep
on Unix-like platforms, and findstr.exe
on Windows:
# Windows
$sampleInput | Format-List | findstr foo
# Unix (macOS, Linux)
$sampleInput | Format-List | grep foo
Note: You don't strictly need a Format-*
call; if the default formatting of your command output results in the desired representation, you can pipe directly for grep
/ findstr.exe
.
On Windows, however, if your (formatted) command output comprises non-ASCII-range characters, more work is needed (usually not a concern on Unix-like platforms, where (BOM-less) UTF-8 is pretty much universally used these days):
You'll have to set the $OutputEncoding
preference variable to the character encoding that the target program expects, which in the case of findstr.exe
is the system's
legacy OEM code page, by default reflected in [Console]::OutputEncoding
(and in the output from chcp.com
).
$OutputEncoding = [Console]::OutputEncoding
Note that [Console]::OutputEncoding
is also what PowerShell uses to decode output received from external programs, into .NET strings.
As a general aside: Given that certain modern programs, such as Node.js (node.exe
), invariably use UTF-8 encoding, irrespective of the active OEM code page - in an effort to overcome the limited character sets of the legacy Windows code pages - you may have to (temporarily) set both values in order to both send and receive text properly:
$OutputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
For more information on how PowerShell handles character encoding when communicating with external programs, see this answer.
Upvotes: 5