Мне нужно разделить входную строку на смежные пробелы в список строк. Входные данные могут включать строки в одинарных или двойных кавычках, которые следует игнорировать.
Как я могу разделить строку на пробелы, но игнорировать строки в кавычках, поэтому результат разделения этого
me you "us and them" 'everyone else' them
возвращает это?
me
you
us and them
everyone else
them
Дубликат этого вопроса, но также и необходимость игнорировать строки в одинарных кавычках.


Это отличное решение было изменено, чтобы игнорировать строки в одинарных кавычках и удалять все ведущие и конечные кавычки из каждого аргумента.
$people = 'me you "us and them" ''everyone else'' them'
$pattern = '(?x)
[ ]+ # Split on one or more spaces (greedy)
(?= # if followed by one of the following:
(?:[^"'']| # any character other a double or single quote, or
(?:"[^"]*")| # a double-quoted string, or
(?:''[^'']*'')) # a single-quoted string.
*$) # zero or more times to the end of the line.
'
[regex]::Split($people, $pattern) -replace '^["'']|["'']$', ''
Результаты:
me
you
us and them
everyone else
them
Короче говоря, это регулярное выражение соответствует строке пробелов, пока все, что следует за ней, является строкой без кавычек или строкой в кавычках, что эффективно рассматривает строки в кавычках как одиночные символы.
Краткое решение, основанное на операторах PowerShell -split и -match на основе регулярных выражений (и дословной here-string для ввода):
# Returns the following array:
# @('me', 'you', 'us and them', 'everyone else', 'them')
@'
me you "us and them" 'everyone else' them
'@ -split '"(.*?)"|''(.*?)''|(\S+)' -match '\S'
Примечание:
Токены с экранированными, встроенными символами " или ' (например, "Nat ""King"" Cole" не поддерживаются, а любые пустые токены ('' или "") эффективно удаляются из массива результатов.
Объяснение регулярного выражения , используемого с -split, а также возможности поэкспериментировать с ним см. на странице regex101.com.
-match '\S' работает с массивом результатов -split и исключает из него пустые элементы или элементы, состоящие только из пробелов, фильтруя только те элементы, которые содержат хотя бы один символ без пробелов (\S).
-split немного перепрофилирован выше: переданное ему регулярное выражение обычно описывает разделители между элементами, тогда как здесь оно описывает элементы, и именно вложение в (...) (группы захвата) также вызывает то, что эти группы соответствовали требованиям для включения в массив результатов, в дополнение к сериям пробелов, которые теперь технически являются «элементами», а также к начальному пустому элементу, который предшествует первому «разделителю»; -match '\S' по сути устраняет все эти нежелательные элементы.Альтернативно, используйте API .NET напрямую, а именно [regex]::Matches():
$string = @'
me you "us and them" 'everyone else' them
'@
# Returns the following array:
# @('me', 'you', 'us and them', 'everyone else', 'them')
[regex]::Matches($string, '"(?<a>.*?)"|''(?<a>.*?)''|(?<a>\S+)').
ForEach({ $_.Groups['a'].Value })
Это более непосредственно выражает намерение сопоставления и извлечения только аргументов, встроенных в строку.
(?<name>...) используются для захвата аргументов без заключения кавычек.?<a>) для нескольких групп означает, что какая бы из них ни захватила конкретное совпадение, она сообщает об этом через это имя в свойстве .Groups результирующего экземпляра [Match], и, следовательно, к захваченному тексту можно получить доступ. через .Groups['a'].ValueПроблема GitHub № 7867 — это запрос на добавление оператора -matchall, который позволит реализовать более идиоматическое решение PowerShell:
# WISHFUL THINKING, as of PowerShell 7.4.x
($string -matchall '"(?<a>.*?)"|''(?<a>.*?)''|(?<a>\S+)').
ForEach({ $_.Groups['a'].Value })
Альтернативным подходом может быть сопоставление по порядку одной или нескольких строк в двойных кавычках, строк в одинарных кавычках или непробельных пробелов:
$people = 'me you "us and them" ''everyone else'' them'
$pattern = '(?:"(?:[^"])*"|''(?:[^''])*''|\S)+'
([regex]::Matches($people, $pattern)).Value
Порядок важен: вы хотите, чтобы регулярное выражение соответствовало/захватывало цитируемые элементы целиком, прежде чем пытаться захватить непробелы.
Шаблон:
(?: # Start a non-capturing group
"(?:[^"])*" # match double-quoted string
| # or
'(?:[^'])*' # match single-quoted string
| # or
\S # match a non white space character
)+ # repeat non-capturing group 1 or more times
@ sthames42, это задумано. Это эквивалент разделения строки на пробелы, но не на пробелы внутри цитируемых разделов. Для того, что вы хотите, просто удалите внешнюю группу без захвата и замените \S на [^''\s"]+: $pattern = '"(?:[^"])*"|''(?:[^''])*''|[^''\s"]+'
Провел небольшое тестирование производительности и не обнаружил практически никакой разницы между этим решением и моим более чем 100 000 анализов одной и той же строки. Но я принял это, потому что предпочитаю сопоставление разбиению, и мне неясно, зачем мне нужен квалификатор *$. Я знаю, что без этого ничего не получится, но не знаю почему.
Вместо создания для этого регулярного выражения вы также можете положиться на надежный синтаксический анализатор (например, существующий PowerShell PSParser):
[System.Management.Automation.PSParser]::Tokenize($people, [ref]$null).Content
Обратите внимание, что парсеры обычно поддерживают больше, чем просто правила цитирования, а также строки здесь , умные кавычки и операторы, которые конкретно не включены или не исключены в вашем вопросе и примере, а это означает, что эти функции можно либо считать потерей или бонус к этому вопросу.
Это хорошее предложение, @iRon, и я подумаю об этом в будущем. Но решение RegEx будет работать на многих языках, кроме PowerShell. Спасибо.
Это хорошо, но есть проблема. Две строки в кавычках без пробела между ними возвращаются как одна строка:
"us and them"'everyone else'возвращается как есть вместо"us and them"и'everyone else'.