Я пытаюсь переименовать строки, извлеченные из пользовательского поля. Итак, я настроил матрицу, которая создает связь между тем, что поступает из UDF, и тем, как я хочу переименовать эту строку.
Вот псевдопример моей матрицы (CSV):
Key,Value
FooBar1234,Test 1
ASDF[1234]=qwerty,Test 2
Yellow,Test 3
YellowString,Test 3
1234567890,Test 1
Foo,Test 1
Jane Smith,Test 4
Jane,Test 4
Jane(ABC),Test 4
Вот пример данных, которые получает мой скрипт (CSV):
FooBar1234
ASDF[1234]=qwerty
Yellow
YellowString
1234567890
Foo
Jane Smith
Jane
Jane(ABC)
Jane(abc)
Вот мой сценарий PowerShell:
$path = 'Path\To\CSV\file.csv'
$data = Import-Csv $path -Header UDFName
$matrix = @{}
#import matrix
$re = Import-Csv 'Path\To\Matrix\file.csv' | ForEach-Object{
#key equals value
$matrix[$_.Key] = $_.Value
[regex]::Escape($_.Key)
}
$re = $re -join '|' -as [regex]
$data | ForEach-Object {
$udf_rename = $re.Replace($_.UDFName, { $matrix[$args[0].Value] })
$_.UDFName = $udf_rename
$_
}| ConvertTo-Csv | Select-Object -Skip 2 | Set-Content $path
И это полный вывод:
"Test 1"
"Test 2"
"Test 3"
"Test 3String"
"Test 1"
"Test 1"
"Test 4"
"Test 4"
"Test 4(ABC)"
"Test 4(abc)"
Проблема, с которой я сталкиваюсь (помимо того факта, что это пользовательское поле 😓), заключается в том, что когда я пытаюсь заменить ключ значением, мой код заменяет только часть входной строки для некоторых входных данных. Например, если YellowString
передается через цикл, я ожидаю увидеть Test 3
в качестве результата. Однако, поскольку Yellow,Test 3
является частью матрицы, в результате я получаю Test 3String
. Мой цикл заменяет только часть Yellow
в YellowString
из-за связи, установленной между Yellow
и Test 3
в матрице. Нечто подобное происходит с Jane
и Jane(ABC)
.
К сожалению, я не могу удалить Yellow,Test 3
и другие варианты из матрицы, поэтому мне нужно выяснить, как точно сопоставить входную строку. Я попытался удалить все пробелы из ключей, но результат все равно тот же. Я также подумал, что, возможно, я мог бы прикрепить уникальные коды к каждому ключу и затем таким образом перевести, но тогда я столкнулся с той же проблемой, когда дело дошло до замены. Есть идеи? Пожалуйста, дайте мне знать, если я могу предоставить дополнительную информацию.
Есть ли способ сделать это нечувствительным к регистру? Я надеюсь, что Jane(ABC), Test 4
сможет заменить Джейн(abc) и Джейн(ABC).
Функциональность регулярных выражений PowerShell и API регулярных выражений .NET по умолчанию работают с подстроками.
(Например, 'food' -replace 'oo', '@@'
дает 'f@@d'
, как и ([regex] 'oo').Replace('food', '@@')
)
Чтобы гарантировать полное совпадение каждой входной строки, привяжите каждое регулярное выражение к ^...$
:
'^{0}$' -f [regex]::Escape($_.Key)
Примечание. В приведенном выше примере используется -f
, оператор формата для синтеза строки; альтернативно используйте'^' + [regex]::Escape($_.Key) + '$'
или"^$([regex]::Escape($_.Key))`$"
Я обдумывал это, но не был уверен, что фраза на замену будет единственной, что будет стоять на кону. Если да, то это лучший ответ
Для простоты я не упомянул, что данные, которые я передаю скрипту, выглядят примерно так (например, одна строка): A123,FooBar1234,0000,123456789
... и так далее. Я предполагаю, что опубликованное вами регулярное выражение нацелено на начало строки, поэтому, когда я его вставляю, оно не работает должным образом. Приносим извинения за то, что изначально не были более ясными в отношении данных.
@Crimp: вы выполняете замену одного свойства ваших CSV-данных, разобранных на объекты ($_.UDFName
), поэтому нет необходимости беспокоиться о совпадении во всей строке.
Спасибо, @DougMaurer; пожалуйста, посмотрите комментарий, который я только что адресовал Кримпу.
Я думаю, это то, что я ищу! На первый взгляд, применительно к моей большей группе данных, кажется, что все было правильно переименовано. Единственными выбросами являются другие странные варианты строк, которых нет в моей исходной матрице. Спасибо, сэр!
Есть ли способ сделать это нечувствительным к регистру? Например, если YellowString
передается через скрипт и yellowstring
находится в матрице, цикл не подхватит его и не переименует.
Рад это слышать, @Crimp. Самый простой способ сделать регулярное выражение нечувствительным к регистру — добавить к строке регулярного выражения встроенный параметр: '(?i)'
, т. е. $re = '(?i)' + ($re -join '|') -as [regex]
. Обратите внимание, что операторы PowerShell по умолчанию обычно нечувствительны к регистру (и имеют варианты с учетом регистра, например -creplace
), а в PowerShell (Core) 7 вместо этого можно использовать операцию -replace
: $udf_rename = $_.UDFName -replace $re, { $matrix[$_.Value] }
(затем можно оставить $re
в качестве нить).
Установите границу слова в регулярном выражении с помощью \b...\b
$path = 'Path\To\CSV\file.csv'
$data = Import-Csv $path -Header UDFName
$matrix = @{}
#import matrix
$re = Import-Csv 'Path\To\Matrix\file.csv' | ForEach-Object{
#key equals value
$matrix[$_.Key] = $_.Value
'\b{0}\b' -f [regex]::Escape($_.Key)
}
$re = $re -join '|' -as [regex]
$data | ForEach-Object {
$udf_rename = $re.Replace($_.UDFName, { $matrix[$args[0].Value] })
$_.UDFName = $udf_rename
$_
}| ConvertTo-Csv | Select-Object -Skip 2 | Set-Content $path
Выход
udfname
-------
Test 1
Test 2
Test 3
Test 3
Test 1
Test 1
Test 4
Test 4
Test 4(ABC)
Test 4(abc)
Это касается желтой струны.
РЕДАКТИРОВАТЬ
Если вы добавите строчную букву (abc) в свою матрицу, воспользуйтесь предложением @TheMadTechnician и используйте начальную букву \b
- тогда это сработает.
$re = $csv | ForEach-Object{
#key equals value
$matrix[$_.Key] = $_.Value
'\b{0}' -f [regex]::Escape($_.Key)
}
$re = ($re | sort length -descending) -join '|'
$data | ForEach-Object {
if ($_.udfname -match $re){
$_.UDFName -replace [regex]::escape($matches[0]),$matrix[$matches[0]]
}
}
Выход
Test 1
Test 2
Test 3
Test 3
Test 1
Test 1
Test 4
Test 4
Test 4
Test 4
Чтобы обновить свойство, просто присвойте его обратно $_.udfname.
$data | ForEach-Object {
if ($_.udfname -match $re){
$_.UDFName = $_.UDFName -replace [regex]::escape($matches[0]),$matrix[$matches[0]]
}
}
Есть ли способ справиться с Джейн(ABC) и Джейн(abc)? В моей матрице есть Jane(ABC),Test 4
, но с этим решением, похоже, с этим не справиться. Кроме того, я не упомянул, что хотел бы оставить дело без внимания. Я надеюсь, что отношение Jane(ABC),Test 4
сможет заменить Джейн(ABC) и Джейн(abc).
Простой подход без регулярных выражений:
$data | foreach-object {
$matrix.ContainsKey($_) ? $matrix[$_] : $_
}
который выводит
Test 1
Test 2
Test 3
Test 3
Test 1
Test 1
Test 4
Test 4
Test 4
Test 4
При этом используется тернарный оператор PowerShell Тернарный (<condition> ? <if-true> : <if-false>
) (который недоступен в Windows PowerShell 5.1), чтобы по сути сказать, соответствует ли строка данных ключу в матрице поиска, а затем вернуть значение для этого ключа, в противном случае просто вернуть строку данных. дословно.
Если вам нужно, чтобы он работал в Windows PowerShell 5.1, вы можете использовать немного более длинную форму:
$data | foreach-object {
if ( $matrix.ContainsKey($_) ) { $matrix[$_] } else { $_ }
}
Ваше совпадение должно происходить слева направо, поэтому
YellowString
при сопоставлении сYellow|YellowString
будет соответствоватьYellow
, а при сопоставлении сYellowString|Yellow
будет соответствоватьYellowString
. Отсортируйте шаблоны регулярных выражений от самого длинного до самого короткого, прежде чем объединять их с помощью каналов.$re = ($re | sort length -descending) -join '|' -as [regex]