Home > OS >  How to output only the first or all matches in Powershell like `sed`?
How to output only the first or all matches in Powershell like `sed`?

Time:07-25

In sed one can output all matches or e.g. only the first:

$ echo -e 'a1\nb2\nb3'
a1
b2
b3

# print all matches
$ echo -e 'a1\nb2\nb3' | sed -n '/b\(.\)/s//\1/p'
2
3

# print only first match
$ echo -e 'a1\nb2\nb3' | sed -n '0,/b\(.\)/s//\1/p'
2

How can I do this with Powershell?

I get only the first match:

@'
>> a1
>> b2
>> b3
>> '@ | Select-String 'b(.*)' | ForEach-Object{$_.Matches.Groups[1].Value}
2

CodePudding user response:

  • Your PowerShell here-string (@'<newline>...<newline>'@) is a single object (string) that happens to be a multi-line string.

  • To Select-String, each input object is a "line", and by default it looks only for the first match on each "line" - unless you specify the -AllMatches switch.

    • Input is implicitly line by line only in the following scenarios:
      • Letting Select-String itself read and process files with the -Path or -LiteralPath parameter.
      • Using Get-Content output as input, which streams line by line by default (though using Select-String's -Path / -LiteralPath or piping Get-ChildItem output is much more efficient).
      • Piping output from calls to external programs, whose stdout output is also streamed line by line.
  • With -AllMatches present and more than one object stored in the .Matches collection, the member-access enumeration operation $_.Matches.Groups[1].Value is insufficient for obtaining each match's .Groups[1].Value property - you need to iterate over the $_.Matches collection and apply .Groups[1].Value to each element.

Therefore, to get all matches:

@'
a1
b2
b3
'@ |
  Select-String 'b(.*)' -AllMatches |
  ForEach-Object{ $_.Matches.ForEach({ $_.Groups[1].Value }) }

Output:

2
3

To get only the first match, your command works as-is, for the reasons explained above.


Alternatively, you could have split your here-string into individual lines first, in which case the rest of your command would have worked as-is to get all matches:

@'
a1
b2
b3
'@ -split '\r?\n' |  # Split the multi-line string into indiv. lines
  Select-String 'b(.*)' |
  ForEach-Object{ $_.Matches.Groups[1].Value }

To get only the first match, with line-by-line output, pipe the above to Select-Object -First 1.

Note: Select-String has a -List switch that looks for at most one match, but it only applies to files as input, either via its own -Path / -LiteralPath parameter, or by piping file-info objects from Get-ChildItem.

  • Related