OrenIshShalom
OrenIshShalom

Reputation: 7102

windows powershell findstr incorrect behaviour

I am looking for a search string inside a json file:

> type .\input.json
[
    {"name": "moish"},
    {"name": "oren"}
]
> type .\input.json | findstr /n /l "\`"name\`": \`"or"
2:    {"name": "moish"},
3:    {"name": "oren"}

How come moish entry is found? what am I missing?

Upvotes: 1

Views: 221

Answers (2)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174435

Note: The quoted lines below, originally a direct part of the answer, turned out not to apply to the problem at hand, because the escaping in the question is correct (the only thing missing was placing /c: directly before the string to make findstr.exe search for it as a whole).

See this answer for a more comprehensive analysis of the problem.

Escape the literal quotation marks by doubling them:

type input.json |findstr /n /l """name"": ""or"

... or use single-quotes to qualify the search term:

type input.json |findstr /n /l '"name": "or'

.... or perhaps use the native PowerShell cmdlet Select-String instead of findstr:

Select-String -LiteralPath input.json -Pattern '"name": "or'

Upvotes: 1

mklement0
mklement0

Reputation: 437062

Prepend /c: to your search string in order to make findstr treat it as a single string to search for:

Get-Content .\input.json | findstr /n /l /c:"\`"name\`": \`"or" # Note the /c:

Note the use of the Get-Content cmdlet for reading a file line by line, which type is a built-in alias for in PowerShell.

Note:

  • By default, if a search string contains spaces, findstr searches for the occurrence of any of the space-separated words, i.e., "name" or "or, causing both lines to match. /c: signals that the string as a whole string should be searched for (either as a regular expression, by default, or as a literal string, with /l)

  • Except for the missing /c:, your search string was correct, but you could have simplified by using a verbatim (single-quoted) string ('...'):

    • ... | findstr /n /l /c:'\"name\": \"or'
    • Sadly, the additional \-escaping of the embedded " chars. is a requirement either way, up to at least PowerShell 7.2.x, even though it shouldn't be necessary.
      • It is due to a long-standing bug in how PowerShell passes arguments with embedded double-quotes to external programs; a - possibly opt-in - fix may be coming - see this answer.
      • If you're using a 7.2.x version or a 7.3 preview version of PowerShell with the experimental feature named PSNativeCommandArgumentPassing enabled and the $PSNativeCommandArgumentPassing preference variable set to either 'Standard' or 'Windows', the \-escaping is no longer needed, because PowerShell then (finally) does it for you; that is, findstr /n /l /c:'"name": "or' would suffice.

PowerShell alternative: Select-String:

As shown in Mathias R. Jessen's answer, you may alternatively use the Select-String cmdlet, the more powerful PowerShell counterpart to findstr.exe, not least to avoid quoting headaches (see above) and potential character-encoding issues.

  • Like findstr.exe, Select-String uses regular expressions by default; use -SimpleMatch to opt for literal matching.

  • Unlike findstre.exe, Select-String is case-insensitive by default (as PowerShell generally is). Use -CaseSensitive to make matching case-sensitive.

  • Select-String wraps matching lines in objects that include metadata about each match; if you're interested in the line text only, use -Raw in PowerShell (Core) 7+, or pipe to ForEach-Object Line in Windows PowerShell.

  • While piping lines read from a file via Get-Content works, it is much slower than the passing the file path as an argument directly to Select-String, via its -LiteralPath parameter (you may also pipe file-info objects obtained with Get-ChildItem to it).

    • This has the added advantage that the display representation of the matching lines includes the file name an line number (see below).

Thus, the equivalent of your (corrected) findstr.exe call is:

Select-String -LiteralPath .\input.json -CaseSensitive -SimpleMatch -Pattern '"name": "or'

# Alternative:
Get-ChildItem .\input.json |
  Select-String -CaseSensitive -SimpleMatch -Pattern '"name": "or'

You'll get the following output in the console (note the output-line prefix consisting of the file name and the line number):

input.json:3:  {"name": "oren"}

Note that this is the for-display representation of the object of type [Microsoft.PowerShell.Commands.MatchInfo] that Select-String emitted for the matching line.

Upvotes: 1

Related Questions