In a PowerShell script, I would like to check if a branch exists locally already, which I'm currently doing as $existsInLocal = (git branch | sls $branch) -ne $null
, but that's incorrect because sls $branch
matches line(s) containing $branch
as a substring, but what I need is a version of sls $branch
that outputs line(s) matching $branch
exactly.
Is there a way to use ^
and $
to indicate start and end of line, respectively? My attempts at doing so didn't succeed.
I have looked at the Select-String
documentation and didn't find a solution.
There's also an almost solution here that makes use of \b
which matches a word boundary, but it doesn't work for my case because branch names can have \W
chars (e.g. /
or -
). For example, \bjose/feature\b
would match dev/jose/feature
(the first \b
matches the transition from /
to j
) or jose/feature/v2
or dev/jose/feature/v2
.
I know there's another way to check for existence of local branch, but I really am mostly interested in sls
exact matches in general, and the "determine whether local branch exists" use case is just one of many.
To address your general question:
I really am mostly interested if
sls
(Select-String
) finds exact matches in general.
Select-String
by design looks for the given search term(s) as substring(s) in each input string (line).[1]
-CaseSensitive
switch as needed.By default, each search term passed to the -Pattern
parameter (possibly positionally, as the first unnamed argument) isinterpreted as a regex (regular expression).
-SimpleMatch
switch.There is no option to request whole-line matching, so the only way to achieve whole-line matching is indeed by using a regex with the ^
and $
anchors.
However, this may necessitate escaping a search term meant to be used literally, so that any characters that happen to be regex metacharacters aren't accidentally interpreted as such.
Use [regex]::Escape()
to perform this escaping programmatically; in a literal search term, you may alternatively \
-escape metacharacters individually.
By default, Select-String
looks for all matches in the input file / among the input strings.
To make it only report the first match and stop searching for more, add the -List
switch, but note that this only works with file input (either via -Path
/ -LiteralPath
or by piping file-info objects via Get-ChildItem
or Get-Item
).
To make it only report a Boolean ($true
or $false
) depending on whether at least one match was found - which also stops searching after the first match - add the -Quiet
switch instead.
Select-String
and no match is found, as of PowerShell 7.4.x there is unexpectedly no output instead of $false
; this should be considered a bug - see GitHub issue #16681.if
statement, is equivalent to $false
, so this bug typically won't matter.Additionally - and independently of -List
- the -AllMatches
switch requests that the search term be matched as often as it occurs in each line (input string) rather than reporting just the first match.
Applied to your case:
$existsInLocal = git branch | sls -Quiet ('^\s*{0}$' -f [regex]::Escape($branch))
The above demonstrates the escaping technique, even though it's probably not necessary in your case, and, per your feedback matches \s*
, i.e. optional whitespace before the branch name.
-Quiet
makes Select-String
output $true
if a match was found (see above), and also stops searching in that case - this obviates the need for your (...) -ne $null
test, which, incidentally, is generally better written as $null -ne (...)
- see the relevant PSScriptAnalzyer
rule
Taking a step back:
As mclayton notes, a simpler solution for literal whole-line matching is possible, using the -contains
operator:
$existsInLocal = (git branch) -contains $branch
PowerShell automatically relays an external program's stdout output line by line and by enclosing the call in (...)
and using that with an operator, the lines are collected in an array (assuming two or more output lines).
-contains
then performs element-by-element equality comparisons on this array (essentially, -eq
operations with each array element as the LHS) until a match (at most one) is found, in which case $true
is returned (otherwise $false
). In the case at hand, this amounts to literal, whole-line comparisons.
-contains
too is case-insensitive by default, but you can use the case-sensitive operator variant, -ccontains
.The only potential down-side of this approach is that it requires collecting all stdout lines in memory, up front before matching is performed, whereas the streaming nature of a Select-String
-based pipeline solution allows stopping as soon as a match is found.
With only a limited amount of stdout output, such as in the case at hand, this won't be a problem, however.
[1] The typical use case is that Select-String
receives text line by line, such as by making it search a file using -Path
/ -LiteralPath
or when piping the output of an external program to it.
However, when you pass a multiline string as a single object to it (e.g. by piping Get-Content -Raw
to it), that string is searched through as a whole, enabling cross-line matching.