10

Could someone clarify how sls (Select-String) works compared to grep and findstr?

grep: grep <pattern> files.txt

sls: sls <pattern> files.txt (default parameter position for sls is pattern then file)

grep examples: grep "search text" *.log ; cat *.log | grep "search text"

sls examples: sls "search text" *.log ; cat *.log | grep "search text"

As an aside, all PowerShell Cmdlets are case-insensitive, unlike Linux tools which are generally always case-sensitive but also older tools like findstr which are case sensitive too, but findstr can be used in PowerShell, and works in situations where sls does not, for example: Get-Service | findstr "Sec" (this works without a problem!), but when we try to use sls in a similar way Get-Service | sls "Sec" we get nothing (presumably this fails because sls works with strings, but Get-Service returns an object, so that's understandable - but what is findstr doing then in that it can see the output as a string?).

So, my thinking is "ok, I need to make the output from Get-Service into a string to work with PowerShell Cmdlets", but that doesn't work (or not in a way that I would expect):

Get-Service | Out-String | sls "Sec" (gives results, but odd)

(Get-Service).ToString() | sls "Sec" (.ToString() just returns "System.Object[]")

How in general should I turn an object into a string so that it can manipulate the information (in the same way that Get-Service | findstr "Sec" can do so easily)?

Would appreciate if someone could clarify how things fit together in the above so that I can make more use of sls. In particular, Get-Service | Out-String | sls "Sec" does return stuff, just not the stuff I was expecting (is it searching for each character of "s" and "e" and "c" so is returning lots - that would not be very intuitive if so in my opinion)?

YorSubs
  • 3,194
  • 7
  • 37
  • 60

2 Answers2

11

When you use Out-String by default, it turns the piped input object (an array of service objects in this case) into a single string. Luckily, the -Stream switch allows each line to be output as a single string instead. Regarding case-sensitivity, Select-String supports the -CaseSensitive switch.

# For case-insensitive regex match
Get-Service | Out-String -Stream | Select-String "Sec"

# For case-sensitive regex match
Get-Service | Out-String -Stream | Select-String "Sec" -CaseSensitive

# For case-sensitive non-regex match
Get-Service | Out-String -Stream | Select-String "Sec" -CaseSensitive -SimpleMatch

In either case, Select-String uses regex (use the -SimpleMatch switch to do a string match) to pattern match against each input string and outputs the entire string that matched the pattern. So if you only pipe into it a single string with many lines, then all lines will be returned on a successful match.

AdminOfThings
  • 23,946
  • 4
  • 17
  • 27
  • That's great, thanks for the clarification. That's cleared up something that I've not understood for a long time. Makes sense. I googled around and had not found this. I knew about the CaseSensitive switch, but not the SimpleMatch, that's also very useful. Will be very easy to use Select-String in future with this information. – YorSubs Dec 07 '19 at 23:05
  • 1
    One thing that I'm left still curious on though: what happens in the findstr case then? When we pipe to a non-Cmdlet, does PowerShell say "ah, this findstr thing is not a Cmdlet, so do an `Out-String -Stream` before passing it" because it appears that it is giving findstr each line as a single string, right? – YorSubs Dec 07 '19 at 23:06
  • 1
    +1 for the answer, but the `findstr` explanation in the comment is confusing. In fact, PowerShell _implicitly_ implies `Out-String -Stream` when piping to external programs, something it should always have done for non-string input to `Select-Object` as well (see https://github.com/PowerShell/PowerShell/issues/10726). That is, it is PowerShell that feeds individual lines of the effectively `Out-String -Stream`-stringified input to `findstr`, and it is PowerShell that invariably terminates each stringified input (line) with a newline. /cc @YorSubs. – mklement0 Dec 09 '19 at 02:14
4

To complement AdminOfThings' helpful answer:

  • In order to find strings among the lines of the for-display string representations of non-string input objects as they would print to the console you indeed have to pipe to Out-String -Stream, whereas by default, simple .ToString() stringification is applied[1].

    • You shouldn't have to do this manually, however: Select-String should do it implicitly, as suggested in GitHub issue #10726.
  • Curiously, when piping to external programs such as findstr.exe, PowerShell already does apply Out-String -Stream implicitly; e.g:

    • Get-Date 1/1/2019 | findstr January works (in en-based cultures), because it is implicitly the same as
      Get-Date 1/1/2019 | Out-String -Stream | findstr January
    • By contrast, Get-Date 1/1/2019 | Select-String January is the equivalent of
      (Get-Date 1/1/2019).ToString([cultureinfo]::InvariantCulture) | Select-String January, and therefore does not work, because the input evaluates to 01/01/2019 00:00:00.

[1] More accurately, .psobject.ToString() is called, either as-is, or - if the object's ToString method supports an IFormatProvider-typed argument - as .psobject.ToString([cultureinfo]::InvariantCulture) so as to obtain a culture-invariant representation - see this answer for more information.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Indeed, seemed curious to me above that this happened implicitly when passed to findstr but not for Select-String (I see no reason not to). Often there are deeper technical reasons for the why (which I usually don't understand frankly) but it looks like it would have been better behaviour if `Select-String` implicitly did `Out-String -Stream` in the same way as when piping to `findstr`. I've always found Select-String awkward to use in the past in this way (so I just avoided it) and never understood why. Thanks for the explanation (and hopefully the proposed change is implemented). – YorSubs Dec 09 '19 at 08:42