PowerShell, форматирование значений в другой культуре

Есть ли в PowerShell простой способ форматирования чисел и тому подобного в другой локали? В настоящее время я пишу несколько функций для облегчения генерации SVG для меня и использования SVG. . в качестве десятичного разделителя, в то время как PowerShell соблюдает мои настройки локали (de-DE) при преобразовании чисел с плавающей точкой в ​​строки.

Есть ли простой способ установить другую локаль для функции или около того, не придерживаясь

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

после каждого double переменная?

Примечание. Речь идет о локали, используемой для форматирования, а не о строке формата.

(Дополнительный вопрос: должен ли я использовать инвариантную культуру в этом случае или скорее en-US?)

ETA: Ну, я пытаюсь что-то вроде следующего:

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

Помещая переменную в строку, заданная локаль как-то не применима.

4 ответа

Решение

Это функция 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

В то время как полезный ответ Кейта Хилла показывает вам, как изменить текущую культуру сценария по требованию (более современная альтернатива для PSv3+ и.NET framework v4.6 +:
[cultureinfo]::CurrentCulture = [cultureinfo]::InvariantCulture), нет необходимости менять культуру, потому что - как вы обнаружили во втором обновлении вопроса - интерполяция строк PowerShell (в отличие от использования -f оператор) всегда использует инвариант, а не текущую культуру:

Другими словами:

Если вы замените 'val: {0}' -f 1.2 с "val: $(1.2)" Буквально 1.2 не отформатирован в соответствии с правилами текущей культуры.
Вы можете проверить в консоли, запустив (в одной строке; PSv3+,.NET Framework 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 всегда применяет инвариант, а не текущую культуру в следующих контекстах, связанных со строками, если данный тип поддерживает преобразование для конкретной культуры в и из строк:

Как объясняется в этом подробном ответе, PowerShell явно запрашивает обработку, не зависящую от культуры, - передавая [cultureinfo]::InvariantCulture экземпляр - в следующих сценариях:

  • При строковой интерполяции: если тип объекта реализует IFormattable интерфейс; в противном случае PowerShell вызывает .psobject.ToString() на объекте.

  • При кастинге:

    • к строке, в том числе при привязке к [string] параметр: если тип источника реализует [IFormattable] интерфейс; в противном случае PowerShell вызывает .psobject.ToString(),
    • из строки: если целевой тип статический .Parse() метод имеет перегрузку с [IFormatProvider] параметр (который является интерфейсом, реализованным [cultureinfo]).
  • При сравнении строк (-eq, -lt, -gt), с использованием String.Compare() перегрузка, которая принимает CultureInfo параметр.

  • Другие?

Что касается того, что инвариантная культура является / для:

Инвариантная культура нечувствительна к культуре; это связано с английским языком, но не с какой-либо страной / регионом.
[...]
В отличие от чувствительных к культуре данных, которые могут изменяться пользовательской настройкой или обновлениями.NET Framework или операционной системы, данные инвариантной культуры стабильны во времени и во всех установленных культурах и не могут настраиваться пользователями. Это делает инвариантную культуру особенно полезной для операций, которые требуют независимых от культуры результатов, таких как операции форматирования и синтаксического анализа, которые сохраняют отформатированные данные, или операции сортировки и упорядочения, которые требуют, чтобы данные отображались в фиксированном порядке независимо от культуры.

Предположительно, именно стабильность в разных культурах побудила разработчиков PowerShell последовательно использовать инвариантную культуру при неявном преобразовании в и из строк.

Например, если вы жестко закодировали строку даты, такую ​​как '7/21/2017' в сценарий, а затем попробуйте преобразовать его в дату с [date] например, инвариантное поведение PowerShell гарантирует, что сценарий не прервется даже при запуске, когда действует культура, отличная от американского-английского - к счастью, инвариантная культура также распознает строки даты и времени в формате ISO 8601;
например, [datetime] '2017-07-21' тоже работает

С другой стороны, если вы хотите конвертировать в и из текущих -culture-соответствующих строк, вы должны сделать это явно.

Подвести итоги:

  • Преобразование в строки:

    • Внедрение экземпляров типов данных с строковыми представлениями, чувствительными к культуре по умолчанию, внутри "..." дает культурно- инвариантное представление ([double] или же [datetime] примеры таких типов).
    • Чтобы получить текущее представление культуры, позвоните .ToString() явно или использовать -f оператор форматирования (возможно внутри "..." через ограждение $(...)).
  • Преобразование из строк:

    • Прямое приведение ([<type>] ...) распознает только строковые представления, инвариантные к культуре.

    • Чтобы преобразовать текущее -culture-соответствующее строковое представление (или представление конкретной культуры), используйте статический тип целевого типа ::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
    • Подводный камень: из-за приоритета оператора любое выражение в качестве RHS -f должен быть заключен в (...) для того, чтобы быть признанным в качестве такового:
      • Например, '{0}' -f 1/10 оценивается как будто ('{0}' -f 1) / 10 был указан;
        использование '{0}' -f (1/10) вместо.
  • Вывод по умолчанию на консоль:

    • например, [cultureinfo]::CurrentCulture = 'de-DE'; 1.2 доходность 1,2

    • То же самое относится к выводу из командлетов; например,
      [cultureinfo]::CurrentCulture = 'de-DE'; Get-Date '2017-01-01' доходность
      Sonntag, 1. Januar 2017 00:00:00

    • Предупреждение: Похоже, что в Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.5 существует ошибка: в некоторых сценариях литералы, передаваемые в блок скрипта в качестве неограниченных параметров, могут приводить к выводу по умолчанию, не зависящему от культуры - см. Это Выпуск GitHub

  • При записи в файл с Set-Content / Add-Content или же Out-File / > / >>:

    • например, [cultureinfo]::CurrentCulture = 'de-DE'; 1.2 > tmp.txt; Get-Content tmp.txt доходность 1,2
  • При использовании статического ::Parse() / ::TryParse() методы на числовых типах, таких как [double] пока передаю только строку для разбора; например, с культурой fr-FR в действительности (где , является десятичной отметкой), [double]::Parse('1,2') возвращает двойной 1.2 (То есть, 1 + 2/10).

    • Предостережение: как указывает bviktor, разделители тысяч распознаются по умолчанию, но очень свободно: фактически разделитель тысяч можно разместить в любом месте внутри целочисленной части, независимо от того, сколько цифр в результирующих группах, и лидирующей 0 также принимается; например, в en-US культура (где , это разделитель тысяч), [double]::Parse('0,18') возможно удивительно успешно и дает 18,
      • Чтобы подавить распознавание тысяч разделителей, используйте что-то вроде [double]::Parse('0,18', 'Float') через NumberStyles параметр
  • Непреднамеренная чувствительность к культуре, которая не будет исправлена ​​для сохранения обратной совместимости:

  • Другие?

Я думал о том, как сделать это проще, и придумал ускорители:

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()

Надеюсь, это поможет.

Если у вас уже есть культура загружена в вашей среде,

    #>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

Конечно, вы должны иметь в виду, что если другие пользователи запускают сценарий с другой настройкой локали, результаты могут отличаться от запланированных.

Тем не менее, это решение может оказаться полезным. Повеселись!

Другие вопросы по тегам