Как преобразовать строку, содержащую 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 или более последовательных цифр
  2. 1 знак плюс (+)
  3. 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
}

Это регулярное выражение тоже работает

\b\d{3,4}(?=\+)|\d{2,3}(?=\")

https://regex101.com/

Вы хотите что-то вроде этого выхода?

$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

Пояснение:

  1. Замените все значения центов, разделенные +, на значения с дополнением 0, чтобы было как минимум три цифры, то есть как минимум одна цифра в долларах и ровно 2 для цента.
  2. Соберите отдельные доллары и центы для каждого значения в отдельные группы захвата, одновременно отбрасывая любые посторонние начальные нули.
  3. Замените (только что заполненные) строки соответствующими отформатированными версиями.

Интересно отметить, как вторая замена опирается на жадную природу *. В 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 $, одиночный + здесь является ошибкой. Его нужно экранировать с помощью \. Однако даже после исправления этих ошибок остаются две большие проблемы:

  1. Вывод по умолчанию из Select-String это собрание [MatchInfo] объекты, которые при (неявном) преобразовании в String для использования в качестве LHS -replaceвключить имя файла и номер строки, тем самым повредив строки из файла. Чтобы использовать только саму строку, укажите $_.Line.
  2. Совершенно некорректное использование параметров скриптблока для 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 "\+","+$" }

Конечно, это просто описание того, как заставить его "работать". Это неправильное решение проблемы в исходном сообщении (см. Другой ответ).

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