我希望Select-String
考虑rn
(回车+换行符)作为Powershell中一行的结尾。
但是,如下所示,abc
匹配整个输入:
PS C:Toolshashcat> "abc`r`ndef" | Select-String -Pattern "abc"
abc
def
如果我将字符串分成两部分,那么Select-String
的行为符合我的预期:
PS C:Toolshashcat> "abc", "def" | Select-String -Pattern "abc"
abc
如何为Select-String
提供一个行以rn
结尾的字符串,然后使此 cmdlet 仅返回包含匹配项的字符串?
-
Select-String
对每个(按需字符串化[1])输入对象进行操作。 -
多行字符串(如
"abc`r`ndef"
)是单个输入对象。- 相比之下,
"abc", "def"
是一个包含两个元素的字符串数组,作为两个输入对象传递。
- 相比之下,
-
若要确保单独传递多行字符串的行,请使用 PowerShell 的
-split
运算符将字符串拆分为行数组:"abc`r`ndef" -split "`r?`n"
- (
?
使`r
可选,以便正确处理仅`n
(仅 LF,Unix 样式)行尾。
- (
总之:
"abc`r`ndef" -split "`r?`n" | Select-String -Pattern "abc"
等效的,使用带有正则表达式 (regex) 转义序列的 PowerShell 字符串文本(-split
的 RHS 是正则表达式):
"abc`r`ndef" -split 'r?n' | Select-String -Pattern "abc"
不幸的是,Select-String
文档讨论了对文本行的操作,因为真正的操作单元是输入对象- 正如我们所看到的,它们本身可能包含多行。
据推测,这来自通过Get-Content
cmdlet 提供输入对象的典型用例,该 cmdlet 逐行输出文本文件的行。
请注意,Select-String
不会直接返回匹配的字符串,而是将它们包装在包含有关匹配的有用元数据的[Microsoft.PowerShell.Commands.MatchInfo]
对象中。 然而,即使在那里,行隐喻也存在,因为它是包含匹配字符串的.Line
属性。
[1] 可选阅读:Select-String
如何串化输入对象
如果输入对象还不是字符串,则会将其转换为字符串,尽管可能不是您所期望的方式:
粗略地说,.ToString()
方法在每个非字符串输入对象[2]上调用,对于非字符串,该方法与使用PowerShell的默认输出格式获得的表示形式不同(后者是您在控制台打印对象或使用Out-File
时看到的表示形式;相比之下,它与字符串插值获得的表示形式相同。在双引号字符串中(当您在"..."
中嵌入变量引用或命令时,例如,"$HOME"
或"$(Get-Date)"
)。
通常,.ToString()
只生成对象类型的名称,不包含任何特定于实例的信息;例如,$PSVersionTable
字符串化为System.Management.Automation.PSVersionHashTable
。
# Matches NOTHING, because Select-String sees
# 'System.Management.Automation.PSVersionHashTable' as its input.
$PSVersionTable | Select-String PSVersion
如果您确实想逐行搜索默认输出格式,请使用以下习惯用法:
... | Out-String -Stream | Select-String ...
但是,请注意,对于非字符串输入,后续处理更可靠,最好通过查询具有Where-Object
条件的属性来筛选输入。
也就是说,Select-String
需要隐式应用字符串化Out-String -Stream
,这是一个强有力的理由,如本 GitHub 功能请求中所述。
[2] 更准确地说,.psobject.ToString()
被调用,要么按原样调用,要么 - 如果对象的ToString
方法支持IFormatProvider
类型参数 - 作为.psobject.ToString([cultureinfo]::InvariantCulture)
以获得区域性不变表示形式 - 有关详细信息,请参阅此答案。
"abc`r`ndef"
是一个字符串,如果您在控制台中回显 (Write-Output
) 将导致:
PS C:Usersgpunktschmitz> echo "abc`r`ndef"
abc
def
Select-String
将回显出"abc"是其中一部分的每个字符串。由于"abc"是字符串的一部分,因此将选择此字符串。
"abc", "def"
是两个字符串的列表。使用此处的Select-String
将首先测试"abc",如果模式与"abc"匹配,则测试"def"。由于只有第一个匹配,因此才会被选中。
使用以下命令将字符串拆分为一个列表,并仅选择包含"abc"的元素
"abc`r`ndef".Split("`r`n") | Select-String -Pattern "abc"
基本上Guenther Schmitz先生解释了Select-String
的正确用法,但我只想补充几点来支持他的回答。
-
我针对这个
Select-String
cmdlet 做了一些逆向工程工作。它位于Microsoft.PowerShell.Utility.dll中。一些相关的代码片段如下,请注意这些是来自逆向工程的代码以供参考,而不是实际的源代码。string text = inputObject.BaseObject as string; ... matchInfo = (inputObject.BaseObject as MatchInfo); object operand = ((object)matchInfo) ?? ((object)inputObject); flag2 = doMatch(operand, out matchInfo2, out text);
我们可以发现它只是将inputObject视为整个字符串,它不进行任何拆分。
-
我在 github 上找不到此 cmdlet 的实际源代码,可能这个实用程序部分还不是开源的。但是我找到了这个
Select-String
的单元测试.$testinputone = "hello","Hello","goodbye" $testinputtwo = "hello","Hello"
他们用于单元测试的测试字符串实际上是字符串列表。这意味着他们甚至没有考虑您的用例,很可能它只是为了接受字符串集合的输入而设计的。
-
但是,如果我们查看有关
Select-String
的Microsoft官方文档,我们确实会看到它经常谈论行,而它无法识别字符串中的行。我个人的猜测是,行的概念仅在 cmdlet 接受文件作为输入时才有意义,在文件类似于字符串列表的情况下,列表中的每一项都表示一行。
希望它能让事情更清楚。