我有一个脚本,它在Powershell 5.x上运行得很好,但在Powershell Core(7.2.1)上不再运行
当我试图分割文本(从电子邮件中复制过去的内容)时,就会出现问题。。
这一切都归结为代码的这一部分:
$test="blue
green
yellow
"
#$test.Split([Environment]::NewLine)
$x = $test.Split([Environment]::NewLine)
$x[0]
$x[1]
在Powershell 5中,$x[0]==blue
和$x[1]==green
的值但在Powershell Core中,拆分没有任何作用,并且CCD_;不存在";。
在Powershell 7中,换行处理方式不同(至少我认为是这样),但我找不到解决方案。
我尝试将代码更改为$rows = $path.split([Environment]::NewLine)
和$path.Split([System.Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)
,但这不会改变任何事情。。
此外,当我使用";此处字符串";
$test = @'
green
yellow
blue
white
'@
$x= $test -split "`r`n", 5, "multiline"
除$x[0]
外的所有内容均为空(即$x[2]
)
我已经在看了:https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_split?view=powershell-7.2
这里:powershell-split(';';)指定一个新行
这里:WT:在不执行的情况下将多行粘贴到Windows终端
到目前为止,我还没有找到解决问题的办法。
感谢您的帮助。
编辑:我发现了关于这个问题的提示,但还不明白它的含义:https://n-v-o.github.io/2021-06-10-String-Method-in-Powershell-7/
编辑2:感谢大家参与回答我的问题。首先,我想我要写一篇很长的解释,解释为什么我的问题与@SantiagoSquarzon的重复答案不同。但在阅读我的问题和另一个问题的答案时,我注意到我在做一些不同的事情。。
显然,当我使用时有一些不同
$splits = $test -split '`r?`n' # doesn't work in 5.1 and 7.2.1
$splits = $test -split 'r?n' # works in 5.1 and 7.2.1 as suggested from Santiago and others
但是
$splits = $test.Split("r?n") # doesn't work in 5.1 and 7.2.1
$splits = $test.Split("`r?`n") # doesn't work in 5.1 and 7.2.1
$splits = $test.Split([char[]]"rn") # doesnt' work in 7.2.1
$splits = $test.Split([char[]]"`r`n") # works in 7.2.1
tl;dr:
-
使用
-split 'r?n
将多行文本拆分为行,而不管使用的是Windows格式的CRLF还是Unix格式的LF换行(它甚至可以在单个字符串中处理这些格式的混合)。 -
如果您还想只处理CR换行(这可能很不寻常,但对您来说似乎是这样),请使用
-split 'r?n|r'
-
在Windows上,仅使用CRLF换行符,
.Split([Environment]::NewLine)
仅在PowerShell(Core)7+中按预期工作,而不是在Windows PowerShell(而且,意外地,在仅使用CR换行符,就像在您的情况下一样。)若要仅通过CR显式拆分,.Split("`r")
将恰好在两个版本中都按预期工作,由于仅通过单个字符进行拆分。
# Works on both Unix and Windows, in both PowerShell editions.
# Input string contains a mix of CRLF and LF and CR newlines.
"one`r`ntwo`nthree`rfour" -split 'r?n|r' | ForEach-Object { "[$_]" }
输出:
[one]
[two]
[three]
[four]
这是最稳健的方法,因为您通常可以而不是依赖输入文本来使用平台本机换行格式[Environment]::NewLine
;有关详细信息,请参阅底部部分。
注:
以上使用PowerShell的
-split
运算符,对正则表达式进行操作,从而启用上面显示的灵活匹配逻辑。- 此regex101.com页面解释了
r?n|r
regex,并允许您试用它
- 此regex101.com页面解释了
相比之下,
System.String.Split()
.NET方法仅通过文字字符串进行拆分,这虽然更快,但限制了您只能逐字查找分隔符。语法含义如下:
Regex构造(如转义序列
r
(CR)和n
(LF))仅受.NET正则表达式引擎支持,因此仅受-split
(以及使用正则表达式的其他PowerShell上下文)支持;regex元字符?
(与前面的子表达式零次或一次匹配)和|
(交替;与两边的子表达式匹配)也是如此
在字符串内部(这是正则表达式在PowerShell中必须表示的方式,最好是在'...'
内部),这些序列和字符对本身和.Split()
方法都没有特殊意义,后者逐字逐句地处理所有。相比之下,类似的转义序列
"`r"
(CR)和"`n"
(LF)是PowerShell功能,可在可扩展字符串中使用,即它们仅在"..."
内工作,而不在逐字逐句字符串'...'
-内工作,并扩展到它们在目标运算符、方法,或命令查看结果字符串。
这个答案更深入地讨论了
-split
与.Split()
,并建议常规使用-split
。
至于您尝试了什么:
如果确定输入字符串使用平台本机换行格式,则仅使用
$x[1]
0。值得注意的是,在PowerShell提示符下交互式输入的多行字符串文字即使在Windows上也使用Unix格式的LF换行符(唯一的例外是过时的仅限Windows的ISE,它使用CRLF)。脚本文件(
*.ps1
)中的字符串文字使用与中保存的剧本相同的换行格式,这可能是平台的格式,也可能不是平台的格式。此外,正如您在自己的回答中所暗示的,在.NET Core/.NET 5+-中的
System.String.Split()
方法中添加string
参数重载,因此PowerShell(Core)v6+-隐式地导致了相对于Windows PowerShell的中断性更改:具体而言,.Split('ab')
在Windows PowerShell中由'a'
或'b'
分割,即由组成字符串的任何单个字符分割,而在PowerShell(Core)v6+中由整个字符串'ab'
分割。这样的隐式中断更改很少见,但它们确实发生了,而且超出了PowerShell的控制范围。
因此,您应该始终更喜欢PowerShell本机功能以获得长期稳定性,在这种情况下,这意味着更喜欢
-split
运算符而不是.Split()
.NET方法。- 也就是说,有时由于性能原因,.NET方法更可取;可以使它们稳健地工作,但前提是仔细匹配感兴趣的方法重载的确切数据类型,这可能需要强制执行;请参见下文
请参阅此答案以获取更多信息,包括对隐式断裂变化的更详细解释。
您对-split 'r?n'
的反馈对您不起作用,以及您自己答案中的解决方案表明,您的输入字符串(异常)仅使用CR换行符。
您的答案的解决方案在使用Windows格式CRLF格式文本时不会像预期的那样工作,因为每个CR和LF都会单独进行拆分,这将导致输出数组中出现额外的空元素(每个元素表示CRLF序列之间的空字符串)。
如果您确实希望在Windows上通过[Environment]::NewLine
(即通过CRLF)进行拆分,并且希望坚持使用.Split()
方法,为了使其也能在Windows PowerShell中运行,您需要调用需要[string[]]
参数的重载,指示每个字符串(即使只有一个)将作为一个整体用作分隔符,而不是由其任何单个字符分割:
# On Windows, split by CRLF only.
# (Would also work on Unix with LF-only text.)
# In PowerShell (Core) 7+ only, .Split([Environment]::NewLine) would be enough.
"one`r`ntwo`r`nthree".Split([string[]] [Environment]::NewLine, [StringSplitOptions]::None) |
ForEach-Object { "[$_]" }
输出:
[one]
[two]
[three]
虽然这显然比使用-split 'r?n'
更具仪式感,但它确实有表现更好的优势——尽管这无关紧要。请参阅下一节以了解此方法的概括。
使用明确的.Split()
调用来改善性能:
注:
只有当
-split 'r?n'
或-split 'r?n|r'
在实践中太慢时,这才是所必需的,而这种情况不会经常发生。为了使这项工作稳健,在PowerShell版本和长期版本中,您必须仔细匹配感兴趣的
.Split()
过载的确切数据类型。下面的命令相当于
-split 'r?n|r'
,即它匹配CRLF、LF和CR换行符。调整字符串数组以进行更严格的匹配。
# Works on both Unix and Windows, in both PowerShell editions
"one`r`ntwo`nthree`rfour".Split(
[string[]] ("`r`n", "`n", "`r"),
[StringSplitOptions]::None
) | ForEach-Object { "[$_]" }
原因:在将文本粘贴到终端时,使用的终端很重要。默认的powershell 5.1、ISE终端和大多数其他Windows软件使用回车r
和换行n
字符分隔新行。我们可以通过转换为字节进行检查:
# 5.1 Desktop
$test = "a
b
c"
[byte[]][char[]]$test -join ','
97,13,10,98,13,10,99
#a,r,n, b,r,n, c
Powershell Core仅使用换行符n
字符分隔新行
# 7.2 Core
$test = "a
b
c"
[byte[]][char[]]$test -join ','
97,10,98,10,99
在Windows操作系统上,无论哪个控制台,[Environment]::NewLine
都是rn
。在Linux上,它是n
。
解决方案:在上拆分rn
或n
上的多行字符串(但不是仅在r
上)。这里的简单方法是使用像@Santiago squarzon建议的regex:
$splits = $test -split 'r?n'
$splits[0]
a
$splits[1]
b
多亏了这个网站,我找到了一个解决方案:https://n-v-o.github.io/2021-06-10-String-Method-in-Powershell-7/
在.NET4中,字符串类只有将字符作为参数类型。PowerShell看到它并自动转换它,让你的生活轻松一点。注意有一个隐含的"OR"(|)这里是一个字符数组。
为什么PowerShell 7的行为不同?在.NET5中,字符串类具有一些接受字符串的附加参数。PowerShell 7确实不采取任何自动行动。
为了解决我的问题,我不得不使用这个:
$test.Split("`r").Split("`n") #or
$test.Split([char[]]"`r`n")