PowerShell中如何以另一种语言格式化数值?

14

在PowerShell中有没有一种简单的方法来格式化数字和其他语言环境?我目前正在编写一些函数,以便为我减轻SVG生成的负担,而SVG使用 . 作为十进制分隔符,而当将浮点数转换为字符串时,PowerShell会遵循我的区域设置(de-DE)。

是否有一种简单的方法来为函数或其他设置另一个语言环境,而不必坚持使用当前的语言环境?

.ToString((New-Object Globalization.CultureInfo ""))

每个double变量之后?

注意:这是关于用于格式化的区域设置,而不是格式字符串。

(副问题:在这种情况下,我应该使用不变文化还是en-US?)

预计时间到达:好吧,我在尝试以下内容:

function New-SvgWave([int]$HalfWaves, [double]$Amplitude, [switch]$Upwards) {
    "<path d='M0,0q0.5,{0} 1,0{1}v1q-0.5,{2} -1,0{3}z'/>" -f (
        $(if ($Upwards) {-$Amplitude} else {$Amplitude}),
        ("t1,0" * ($HalfWaves - 1)),
        $(if ($Upwards -xor ($HalfWaves % 2 -eq 0)) {-$Amplitude} else {$Amplitude}),
        ("t-1,0" * ($HalfWaves - 1))
    )
}

我经常写的一些东西需要自动化处理,双精度值需要使用小数点而不是逗号(我的地区使用逗号)。

ETA2: 非常有趣的提示:

PS Home:> $d=1.23
PS Home:> $d
1,23
PS Home:> "$d"
1.23

将变量放入字符串中后,设置的语言环境似乎不起作用。


有趣的是,[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InvariantCulture 不起作用。 - stej
我只遇到了1.5与1,5的问题,这就是为什么inv.文化对我有效的原因。听起来像是批处理任务,所以我认为你可以在单独的powershell.exe会话中运行它。 - stej
@stej: [Threading.Thread]::CurrentThread.CurrentUICulture 确定_UI元素_的显示语言,而不是数字格式;相比之下,是 [Threading.Thread]::CurrentThread.CurrentCulture 确定数字格式;尝试(在单行上)[Threading.Thread]::CurrentThread.CurrentCulture = 'de-DE'; 1.2 - mklement0
4个回答

23

虽然 Keith Hill的有用回答 展示了如何在需要时更改脚本的当前区域设置(作为PSv3+和.NET框架v4.6+的更现代替代方案:
[cultureinfo] :: CurrentCulture = [cultureinfo] :: InvariantCulture),但无需更改区域设置,因为-正如您在问题的第二次更新中发现的那样-PowerShell的字符串插值-而不是使用-f运算符-始终使用不变的而不是当前的区域设置

换句话说:

如果您将 'val: {0}' -f 1.2 替换为 "val: $(1.2)",数字文字1.2 不会根据当前区域设置的规则进行格式化。
您可以通过运行以下命令在控制台中验证(一行上; PSv3+,.NET框架v4.6+):

 PS> [cultureinfo]::currentculture = 'de-DE'; 'val: {0}' -f 1.2; "val: $(1.2)"
 val: 1,2 # -f operator: GERMAN culture applies, where ',' is the decimal mark
 val: 1.2 # string interpolation: INVARIANT culture applies, where '.' is the decimal mark.

注意:在 PowerShell (Core) 7+ 中,更改到不同的文化将在会话的其余时间保持有效(这也应该适用于 Windows PowerShell,但实际上却没有)。

背景:

按设计,[1]但也许令人惊讶的是,PowerShell在以下与字符串相关的上下文中应用不变而非当前文化,如果手头的类型支持特定于文化的转换为和从字符串:

这个深入的答案所解释的那样,PowerShell在以下情况下明确请求文化不变的处理,如果可能 - 通过传递[cultureinfo]::InvariantCulture实例 -(PowerShell执行的字符串化相当于对值调用.psobject.ToString([NullString]::Value, [cultureinfo]::InvariantCulture)):

  • 字符串插值时:如果对象的类型实现了IFormattable接口。

  • 强制转换时:

    • 转换为字符串,包括隐式转换绑定到[string]类型参数时:如果源类型实现了[IFormattable]接口。

    • 字符串转换:如果目标类型的静态.Parse()方法有一个重载,其中包含一个[IFormatProvider]类型的参数(这是由[cultureinfo]实现的接口)。

  • 字符串比较-eq-lt-gt)时,使用接受CultureInfo参数的String.Compare()重载

  • 其他?

请注意,以下 .NET 类型在强制转换 / 隐式字符串化时会单独应用自定义字符串化:
  • Arrays and, more generally, similar list-like collection types that PowerShell enumerates in the pipeline (see the bottom section of this answer for what those types are).

    • The (stringified) elements of such types are concatenated with spaces (strictly speaking: with the string specified in the rarely used $OFS preference variable); the stringification of the elements is recursively subject to the rules described here.

    • E.g, [string] (1, 2) yields '1 2'

  • [pscustomobject]

    • Such instances result in a hashtable-like string format described in this answer; e.g.:

      # -> '@{foo=1; bar=2.2}'; values are formatted with the *invariant* culture
      [string] ([pscustomobject] @{ foo = 1; bar = 2.2 })
      
    • The fact that calling .ToString() directly on a [pscustomobject] instance does not yield this representation and instead returns the empty string should be considered a bug - see GitHub issue #6163.

  • Others?

关于“不变文化”的目的
不变文化是与语言相关但不与任何国家/地区相关的文化无关性。与文化敏感数据不同,它不受用户自定义或.NET Framework或操作系统更新的影响,不变文化数据随时间稳定,并跨安装的文化保持不变,不能被用户自定义。这使得不变文化特别适用于需要文化独立结果的操作,例如持久格式化和解析操作,或需要以固定顺序显示数据的排序和排序操作,而不考虑文化。
可以推断,正是跨文化的稳定性促使PowerShell的设计人员在将字符串隐式转换时始终使用不变文化。
例如,如果你在脚本中硬编码一个日期字符串,比如'7/21/2017',然后尝试使用[date]转换成日期,PowerShell的文化不变行为确保即使在其他英语美国之外的文化下运行脚本也不会出错-幸运的是,不变文化还可以识别ISO 8601格式的日期和时间字符串;例如,[datetime] '2017-07-21'也能工作。
另一方面,如果你确实想要转换成和从当前文化相关的字符串,你必须明确地这样做。
总结一下:
  • 将数据类型转换为字符串:

    • "..."中嵌入具有默认情况下支持区域性的字符串表示形式的数据类型实例,会产生一种文化不变的表示形式(例如[double][datetime])。
    • 调用.ToString()明确地获取当前区域性表示,或使用-f格式化运算符(可能在"..."中通过包含的$(...)中)。
  • 从字符串进行转换:

    • 直接强制转换([<type>] ...)只识别特定于文化不变的字符串表示形式。

    • 要从当前区域性适当的字符串表示(或特定区域性的表示)进行转换,请显式使用目标类型的静态::Parse()方法(可选地使用显式的[cultureinfo]实例来表示特定的区域性)。


文化不变的例子:

  • 字符串插值转换

    • "$(1/10)"[string] 1/10

      • 两者都产生字符串字面量 0.1,带有小数点.,与当前文化无关。
    • 同样,从字符串转换的类型转换是与文化无关的;例如:[double] '1.2'

      • .总是被识别为小数点,与当前文化无关。
      • 另一种说法是:[double] 1.2不会被翻译成默认情况下敏感于文化的方法重载[double]::Parse('1.2'),而是翻译成与文化无关的[double]::Parse('1.2', [cultureinfo]::InvariantCulture)
  • 字符串比较(假设[cultureinfo]::CurrentCulture='tr-TR' - 土耳其语,其中i不是I的小写形式

    • [string]::Equals('i', 'I', 'CurrentCultureIgnoreCase')
      • 在土耳其文化中,结果为$false
      • 'i'.ToUpper() 显示,在土耳其文化中,大写字母是İ,而不是I
    • 'i' -eq 'I'
      • 仍然是$true,因为应用了不变的文化。
      • 隐式地与[string]::Equals('i', 'I', 'InvariantCultureIgnoreCase')相同。

文化敏感的例子:

在以下情况下,当前文化得到尊重:

  • 使用 -f 字符串格式化运算符(如上所述):

    • [cultureinfo]::currentculture = 'de-DE'; '{0}' -f 1.2 得到 1,2
    • 注意:由于操作符优先级-f 的右侧任何表达式都必须用(...)括起来才能被识别为表达式:
      • 例如,'{0}' -f 1/10 被计算为如果指定了('{0}' -f 1) / 10
        请改用'{0}' -f (1/10)
  • 默认输出到控制台

    • 例如,[cultureinfo]::CurrentCulture = 'de-DE'; 1.2 得到 1,2

    • 对于来自 cmdlet 的输出也是一样;例如,
      [cultureinfo]::CurrentCulture = 'de-DE'; Get-Date '2017-01-01' 得到
      Sonntag, 1. Januar 2017 00:00:00

    • 注意:在某些情况下,传递给脚本块的无约束参数的字面值可能会导致文化不变的默认输出 - 请参见GitHub issue #4557GitHub issue #4558

  • 在(所有?)cmdlet中:

    • 执行相等比较的 cmdlet:

    • 写入文件的 cmdlet:

      • Set-ContentAdd-Content
      • Out-File 和因此其虚拟别名 > (和 >>)
        • 例如,[cultureinfo]::CurrentCulture = 'de-DE'; 1.2 > tmp.txt; Get-Content tmp.txt 得到 1,2
  • 由于.NET的逻辑,在仅传递要解析的字符串时,使用诸如[double]等数字类型的静态::Parse() / ::TryParse()方法时(例如在生效的区域设置为fr-FR时,其中,是小数标记),[double]::Parse('1,2')返回双倍1.2(即1 + 2/10

    • 注意:正如bviktor指出的那样,默认情

      [1] 目的是支持使用不随文化变化且不随时间变化的表现形式进行编程处理。请参见后面答案中来自文档的链接引用。


11

这是我用来测试其他文化环境下脚本的PowerShell函数。我认为它可以用于你所需要的功能:

function Using-Culture ([System.Globalization.CultureInfo]$culture =(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"),
                        [ScriptBlock]$script=(throw "USAGE: Using-Culture -Culture culture -Script {scriptblock}"))
{    
    $OldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
    $OldUICulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture
    try {
        [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture
        [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture        
        Invoke-Command $script    
    }    
    finally {        
        [System.Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture        
        [System.Threading.Thread]::CurrentThread.CurrentUICulture = $OldUICulture    
    }    
}

PS> $res = Using-Culture fr-FR { 1.1 }
PS> $res
1.1

Keith,你能解释一下为什么这总是返回我的区域设置而不是法语吗?[Threading.Thread] :: CurrentThread.CurrentUICulture = [Globalization.CultureInfo]'fr-FR'; [Threading.Thread] :: CurrentThread.CurrentUICulture.Name - stej
5
Stej,好问题。在命令行中键入以下内容并多次执行:[Threading.Thread]::CurrentThread.ManagedThreadId。现在执行以下内容:& { 1..5 | %{[Threading.Thread]::CurrentThread.ManagedThreadId} }。似乎当您交互式地执行命令时,无法保证它们会从一次调用到下一次都在同一个线程上执行。但在脚本块内,将使用相同的线程。 - Keith Hill
3
如果您在PowerShell命令行中加上“-STA”参数,线程将被重用(但文化变更似乎仍无法保持跨越单个管道)。 - Jaykul
2
@Jaykul 已确认。使用 -STA 在单线程公寓中启动 PowerShell,并在 一行 中键入:[System.Threading.Thread] :: CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]"ar-SA"; [DateTime] :: Now; [System.Threading.Thread] :: CurrentThread.ManagedThreadId; 可以将文化设置为阿拉伯语(沙特阿拉伯),以某种阿拉伯方式写入当前日期和时间,最后写入线程 ID。但是之后的 [DateTime] :: Now; [System.Threading.Thread] :: CurrentThread.ManagedThreadId; 将使用原始文化。线程 ID 是相同的。 - Jeppe Stig Nielsen
1
在PS3中,他们实际上更改了一些东西,以便重置文化。据我所知,您必须使用类似Keith Hill脚本的东西,以便它在一个管道中全部运行... - Jaykul
显示剩余2条评论

5

我正在思考如何使它更加简单易用,并想到了加速器:

Add-type -typedef @"
 using System;  

 public class InvFloat  
 {  
     double _f = 0;  
     private InvFloat (double f) {  
         _f = f;
     }  
     private InvFloat(string f) {  
         _f = Double.Parse(f, System.Globalization.CultureInfo.InvariantCulture);
     }  
     public static implicit operator InvFloat (double f) {  
         return new InvFloat(f);  
     }  
     public static implicit operator double(InvFloat f) {  
         return f._f;
     }  
     public static explicit operator InvFloat (string f) {  
         return new InvFloat (f);
     }  
     public override string ToString() { 
         return _f.ToString(System.Globalization.CultureInfo.InvariantCulture); 
     }
 }  
"@
$acce = [type]::gettype("System.Management.Automation.TypeAccelerators") 
$acce::Add('f', [InvFloat])
$y = 1.5.ToString()
$z = ([f]1.5).ToString()

我希望它能有所帮助。


2
如果您的环境中已经加载了该文化,则:
    #>Get-Culture
    LCID             Name             DisplayName                                                                                                                                             
----             ----             -----------                                                                                                                                             
1031             de-DE            German (Germany)                                                                                                                                        

#>Get-UICulture

LCID             Name             DisplayName                                                                                                                                             
----             ----             -----------                                                                                                                                             
1033             en-US            English (United States) 

可以解决这个问题:

PS Home:> $d=1.23
PS Home:> $d
1,23

像这样:

$d.ToString([cultureinfo]::CurrentUICulture)
1.23

当然,您需要记住,如果其他用户使用不同的语言环境运行脚本,则结果可能与最初预期的不同。

尽管如此,这个解决方案可能会很有用。玩得开心!


2
++对于在特定文化中进行_ad-hoc_字符串转换的方便技术;为了给出你的示例的显式等价物:$d.ToString([cultureinfo]::GetCultureInfo('en-US'))。 要在不变的文化中进行字符串化(这最终是OP想要的),最简单的方法是_cast_([string] $d),或者使用PowerShell的_字符串插值_("$d")。 - mklement0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接