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
параметр
- Чтобы подавить распознавание тысяч разделителей, используйте что-то вроде
- Предостережение: как указывает bviktor, разделители тысяч распознаются по умолчанию, но очень свободно: фактически разделитель тысяч можно разместить в любом месте внутри целочисленной части, независимо от того, сколько цифр в результирующих группах, и лидирующей
Непреднамеренная чувствительность к культуре, которая не будет исправлена для сохранения обратной совместимости:
- В преобразованиях типов привязки параметров для командлетов (но не для функций) - см. Эту проблему GitHub.
- в
-as
оператор - см. этот вопрос GitHub. - В
[hashtable]
поиск ключевых слов - посмотрите этот ответ и эту проблему GitHub.
Другие?
Я думал о том, как сделать это проще, и придумал ускорители:
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
Конечно, вы должны иметь в виду, что если другие пользователи запускают сценарий с другой настройкой локали, результаты могут отличаться от запланированных.
Тем не менее, это решение может оказаться полезным. Повеселись!