Как преобразовать строку, содержащую 2 числа в валюту с PowerShell?
У меня есть текстовые файлы, которые содержат 2 числа, разделенные знаком "+". Попытка выяснить, как заменить их валютным эквивалентом.
Пример строки:
20 + 2 будет конвертировано в $ 0,20 + $ 0,02 USD
1379+ 121 будет> 13,79+ 1,21 долл. США
400+20 будет 4,00 долл. США + 0,20 долл. США
и так далее.
Я попытался использовать несколько углов, но они не работают или дают странные результаты. Я пытался сделать это здесь, пытаясь найти по всем шаблонам, которые, как мне кажется, будут подходить.
.\Replace-FileString.ps1 "100+10" '$1.00+$0.10' $path1\*.txt -Overwrite
.\Replace-FileString.ps1 "1000+100" '$10.00+$1.00' $path1\*.txt -Overwrite
.\Replace-FileString.ps1 "300+30" '$3.00+$0.30' $path1\*.txt -Overwrite
.\Replace-FileString.ps1 "400+20" '$4.00+$0.20' $path1\*.txt -Overwrite
или это просто не работает
Select-String -Path .\*txt -Pattern '[0-9][0-9]?[0-9]?[0-9]?[0-9]?\+[0-9][0-9]?[0-9]?[0-9]?[0-9]?' | ForEach-Object {$_ -replace ", ", $"} {$_ -replace "+", "+$"}
3 ответа
Я пытался сделать это здесь, пытаясь найти по всем шаблонам, я думаю, что придет
Не пытайтесь это делать - мы люди, и мы не будем думать о всех крайних случаях, и даже если бы мы это сделали, объем кода, который нам был необходим для написания (или генерации), был бы нелепым.
Здесь нам нужно более общее решение, и регулярное выражение может действительно помочь в этом.
Шаблон, который вы описываете, может быть выражен в виде трех отдельных частей:
- 1 или более последовательных цифр
- 1 знак плюс (
+
) - 1 или более последовательных цифр
Имея это в виду, давайте начнем с упрощения шаблона регулярного выражения для использования:
\b\d+\+\d+\b
или выписано с объяснениями:
\b # a word boundary
\d+ # 1 or more digits
\+ # 1 literal plus sign
\d+ # 1 or more digits
\b # a word boundary
Теперь, чтобы преобразовать абсолютное значение центов в доллары, нам нужно захватить цифры по обе стороны от +
Итак, давайте добавим группы захвата:
\b(\d+)\+(\d+)\b
Теперь, чтобы сделать что-нибудь интересное с захваченными группами, мы можем использовать Regex.Replace()
Метод - он может принять блок сценария в качестве аргумента замещения:
$InputString = '1000+10'
$RegexPattern = '\b(\d+)\+(\d+)\b'
$Substitution = {
param($Match)
$Results = foreach($Amount in $Match.Groups[1,2].Value){
$Dollars = [Math]::Floor(($Amount / 100))
$Cents = $Amount % 100
'${0:0}.{1:00}' -f $Dollars,$Cents
}
return $Results -join '+'
}
В приведенном выше блоке сценариев мы ожидаем две группы захвата ($Match.Groups[1,2]
), рассчитайте сумму в долларах и центах, а затем, наконец, используйте -f
Оператор форматирования строки, чтобы убедиться, что значение цента всегда составляет две цифры.
Чтобы сделать замену, вызовите Replace()
метод:
[regex]::Replace($InputString,$RegexPattern,$Substitution)
И вот, пожалуйста!
Применить к группе файлов так же просто, как:
$RegexPattern = '\b(\d+)\+(\d+)\b'
$Substitution = {
param($Match)
$Results = foreach($Amount in $Match.Groups[1,2].Value){
$Dollars = [Math]::Floor(($Amount / 100))
$Cents = $Amount % 100
'${0:0}.{1:00}' -f $Dollars,$Cents
}
return $Results -join '+'
}
foreach($file in Get-ChildItem $path *.txt){
$Lines = Get-Content $file.FullName
$Lines |ForEach-Object {
[regex]::Replace($_, $RegexPattern, $Substitution)
} |Set-Content $file.FullName
}
Вы хотите что-то вроде этого выхода?
$20+$2 would be converted to $0.20+$0.02 USD
$1379+$121 would be> $13.79+$1.21 USD
$400+$20 would be $4.00+$0.20 USD
Затем вы можете попробовать эту команду в powershell
,
(gc test.txt) -replace '\b(\d+)\+(\d+)\b','$$$1+$$$2' | sc test.txt
gc , sc
: псевдоним дляget-content, set-content
команды соответственно\b(\d+)\+(\d+)\b
: соответствует целевой строке (numbers+numbers
) и захват номера в$1
,$2
с целью$$
:$
должен быть экранирован, чтобы указатьliteral $
dollor character
(что вы хотите разместить передnumbers
)$1, $2
:back-reference
к захваченному значениюtest.txt
: содержит ваш образец текста
Конечно, это применимо для нескольких файлов, как следует
gci '*.txt' -recurse | foreach-object{(gc $_ ) '\b(\d+)\+(\d+)\b','$$$1+$$$2' | sc $_ }
gci
: псевдоним дляget-childitem
команда. По умолчанию возвращает список в текущем каталоге. Если вы хотите изменить каталог, то должны использовать-path
вариант и-include
вариант.-recurse
опция: позволяет искатьsub-directory
отредактированный
Если ты хочешь capturing & dividing values & replacing
старое значение с новым, как следует
$0.2+$0.02 would be converted to $0.20+$0.02 USD
$13.79+$1.21 would be> $13.79+$1.21 USD
$4+$0.2 would be $4.00+$0.20 USD
тогда вы можете попробовать это.
gci *.txt -recurse | % {(gc $_) | % { $_ -match "\b(\d+)\+(\d+)\b" > $null; $num1=[int]$matches[1]/100; $num2=[int]$matches[2]/100; $dol='$$'; $_ -replace "\b(\d+)\+(\d+)\b","$dol$num1+$dol$num2"}|sc $_}
Эта команда поиска файлов в текущем каталоге и подкаталоге. Если вы не хотите искать в подкаталоге, то удалите -recurse
вариант. И если вы хотите другой путь, то используйте -path
вариант и -include
Вариант вроде следующего.
gci -path "your_path" -include *.txt | % {(gc $_) ...
Другие решения кажутся чрезмерно сложными: сначала преобразование строки в значения, а затем обратно в строки. Глядя на примеры, можно увидеть, что это просто разрезание струны и ее повторная сборка, при этом гарантируя, что разные части (доллары и центы) имеют правильную длину:
('20+2','1379+121','400+20') -replace
'(\d+)\+(\d+)','00$1+00$2' -replace
'0*(\d+)(\d\d)\+0*(\d+)(\d\d)','$$$1.$2+$$$3.$4 USD'
$0.20+$0.02 USD
$13.79+$1.21 USD
$4.00+$0.20 USD
Пояснение:
- Замените все значения центов, разделенные +, на значения с дополнением 0, чтобы было как минимум три цифры, то есть как минимум одна цифра в долларах и ровно 2 для цента.
- Соберите отдельные доллары и центы для каждого значения в отдельные группы захвата, одновременно отбрасывая любые посторонние начальные нули.
- Замените (только что заполненные) строки соответствующими отформатированными версиями.
Интересно отметить, как вторая замена опирается на жадную природу
*
. В
0*
будет соответствовать столько начальных нулей, сколько останется для оставшейся части шаблона.
Вы можете поместить в слове пограничного якоря (\ б), в одном или обоих концах профилей, если у вас есть части строки, где есть цифры, разделенные +, которые непосредственно рядом с другим текстом, и вы хотите их быть НЕ обрабатывается, иначе это не нужно.
Примечание: в приведенном выше примере показан массив
String
в качестве входных данных и создавая массив
String
(каждый элемент отображается в отдельной строке). когда
-Replace
применяется к массиву, он перечисляет массив, применяет замену к каждому элементу и собирает каждый (возможно, замененный) элемент в массив результатов. Выход
Get-Content
это массив
String
(перечисляется PowerShell при поставке конвейера). Точно так же "вход" в Set-Content - это массив
String
(возможно преобразованный из генерала
Object[]
и / или собираются из трубопровода). Таким образом, для преобразования файла просто используйте:
(gc somefile) -replace ... -replace ... | sc newfile
# or even
sc newfile ((gc somefile) -replace ... -replace ...)
# Set-Content [-Path] String[] [-Value] Object[]
В приведенном выше примере newfile и somefile могут быть одинаковыми из-за приятной особенности
Set-Content
при этом он даже не открывает / не создает свой выходной файл (ы), пока ему не будет что написать. Таким образом,
@() | sc existingfile
не уничтожает существующий файл. Однако обратите внимание, что
sc existingfile @()
уничтожает существующий файл. Это потому, что первый пример ничего не отправляет
Set-Content
а второй пример дает
Set-Content
что-то (пустой массив). Поскольку вывод из
Get-Content
собирается в (анонимный) массив перед
-Replace
применяется, нет конфликта между
Get-Content
и
Set-Content
за доступ к тому же файлу. Функционально эквивалентная версия
gc somefile | foreach { $_ -replace ... -replace ... } | sc newfile
не работает, если новый файл - это какой-то файл, так как
Set-Content
получает каждую (возможно замененную) строку от
Get-Content
до того, как будет прочитан следующий смысл
Set-Content
не могу открыть файл, потому что
Get-Content
он все еще открыт.
Это отдельный ответ, потому что он не объясняет, как достичь желаемого результата (уже сделал это), но объясняет, почему перечисленные попытки не работают (образовательный мотив).
Если вы используете
Replace-FileString.ps1
из GitHub, то не только примеры не являются общим решением, они не будут работать, как указано выше, потому что
Replace-FileString.ps1
использует
Replace
метод
[regex]
объект, поэтому "400+20" соответствует "40", затем 1 или более, "0", затем "20". Аналогично для других попыток. Обратите внимание, что в шаблонах нет "+", поэтому все не работают (если у вас нет таких строк, как "40020+125", которые соответствуют на 40020). Точно так же замена включает спецификатор группы захвата "$0" (как часть "$1,00+$0, 10") и другие спецификаторы. В шаблоне не указаны группы захвата, поэтому все спецификаторы группы будут восприниматься буквально, за исключением "$0", представляющего полное совпадение (если оно найдено). Таким образом, вместо "40020+125" нужно подставить "4,00 доллара + 0,20 доллара", получая "4,00+40020,20" (4 доллара = "4 доллара" и 0 долларов = "40020"). Наверное, совпадений не найдено. Результат -> файлы не изменены. (Уф!)
Для
Select-String
попытка
Select-String
вероятно, соответствовал бы требуемым данным, так как шаблон соответствовал до 5 цифр по обе стороны от +. Это отправит совпадающие строки (и проигнорирует остальные, если таковые имеются) в
ForEach-Object
в виде
[Microsoft.PowerShell.Commands.MatchInfo]
объекты (не строки). (Кроме того: это распространенная ошибка многих PowerShell, эм, новичков. Они предполагают, что то, что они видят на экране, совпадает с тем, что происходит внутри PowerShell. Это далеко от истины и, вероятно, приводит к большей части путаница среди новых пользователей. PowerShell обрабатывает объекты целиком и обычно отображает только сводку наиболее полезных битов.) В любом случае, я не уверен, что
ForEach-Object
пытается добиться, не в последнюю очередь из-за очевидной опечатки. В первом блоке сценария отсутствует как минимум один ", а также, возможно, запятая. Лучшее, что я могу интерпретировать, это
{ $_ -replace ", ",", $" }
т.е. поменять каждые ", " на ", $". Это предполагает, что все заменяемые строки предваряются знаком ", ". Примечание. Lone $ не является ошибкой, потому что его нельзя интерпретировать как подстановку переменной (нет следующего имени или {) или ссылку на захват (нет следующего спецификатора группы [0-9`+'_&]). Следующий блок скрипта более понятен, замените каждый "+" на "+$". К сожалению, снова первая строка интерпретируется как регулярное выражение и, в отличие от lone $, одиночный + здесь является ошибкой. Его нужно экранировать с помощью \. Однако даже после исправления этих ошибок остаются две большие проблемы:
- Вывод по умолчанию из
Select-String
это собрание[MatchInfo]
объекты, которые при (неявном) преобразовании вString
для использования в качестве LHS-replace
включить имя файла и номер строки, тем самым повредив строки из файла. Чтобы использовать только саму строку, укажите$_.Line
. - Совершенно некорректное использование параметров скриптблока для
ForEach-Object
. Хотя может показаться, что целью было выполнить две операции замены, размещение их в отдельных блоках сценариев является ошибкой. Даже если бы это сработало, оно выдало бы 2 отдельные частичные замены вместо одной полной замены, поскольку$_
не обновляется между двумя выражениями. ($_
доступен для записи!)
ForEach-Object
имеет 3 основные группы скриптовых блоков, 1
-Begin
блок, 1
-End
блок и все остальное вместе как
-Process
блоки. (The
-Parallel
здесь не имеет значения.) В документации упоминается группа под названием
-RemainingScripts
но на самом деле это просто конструкция реализации, позволяющая
-Process
блоки сценариев следует указывать как отдельные параметры, а не собирать в массив (аналогично массивам параметров в C# и VB). Я подозреваю, что это было сделано для того, чтобы пользователи могли просто отбросить имена параметров (-Begin, -Process и -End) и обработать блоки сценариев, как если бы они были позиционными параметрами, хотя, строго говоря, только
-Process
является позиционным и ожидает массив блоков скриптов (т.е. разделенных запятыми). Вступление к
-RemainingScripts
в PS3.0 (с атрибутом
ValueFromRemainingArguments
поэтому он ведет себя как массив параметров), вероятно, было сделано для того, чтобы убрать то, что могло быть неприятным кладжем, чтобы добиться удобного для пользователя поведения до PS3.0. Или, может быть, это просто формализация того, что уже происходит.
В любом случае, вернемся к теме. При указании нескольких блоков сценариев первый рассматривается как
-Begin
и, если их больше 2, последний рассматривается как
-End
. Таким образом, для двух скриптовых блоков первый -
-Begin
а другой
-Process
. Следовательно, даже если первый блок сценария был синтаксически правильным, он будет запускаться только один раз, а затем по-прежнему ничего не делать, поскольку
$_
не назначен (=$null
) в
-Begin
. Правильный способ - разместить обе замены, объединенные в одно выражение, в один блок сценария:
{ $_.Line -replace ", ",", $" -replace "\+","+$" }
Конечно, это просто описание того, как заставить его "работать". Это неправильное решение проблемы в исходном сообщении (см. Другой ответ).