Разделение строки на пробелы, но игнорирование раздела в двойных или одинарных кавычках

Мне нужно разделить входную строку на смежные пробелы в список строк. Входные данные могут включать строки в одинарных или двойных кавычках, которые следует игнорировать.

Как я могу разделить строку на пробелы, но игнорировать строки в кавычках, поэтому результат разделения этого

me   you   "us and them"   'everyone else' them

возвращает это?

me
you
us and them
everyone else
them

Дубликат этого вопроса, но также и необходимость игнорировать строки в одинарных кавычках.

В чем разница между методом "==" и equals()
В чем разница между методом "==" и equals()
Это один из наиболее часто задаваемых вопросов новичкам на собеседовании. Давайте обсудим его на примере.
Замена символа по определенному индексу в JavaScript
Замена символа по определенному индексу в JavaScript
В JavaScript существует несколько способов заменить символ в строке по определенному индексу.
2
0
112
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Это отличное решение было изменено, чтобы игнорировать строки в одинарных кавычках и удалять все ведущие и конечные кавычки из каждого аргумента.

$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

Это хорошо, но есть проблема. Две строки в кавычках без пробела между ними возвращаются как одна строка: "us and them"'everyone else' возвращается как есть вместо "us and them" и 'everyone else'.

sthames42 26.05.2024 19:12

@ sthames42, это задумано. Это эквивалент разделения строки на пробелы, но не на пробелы внутри цитируемых разделов. Для того, что вы хотите, просто удалите внешнюю группу без захвата и замените \S на [^''\s"]+: $pattern = '"(?:[^"])*"|''(?:[^''])*''|[^''\s"]+'

Darin 26.05.2024 23:41

Провел небольшое тестирование производительности и не обнаружил практически никакой разницы между этим решением и моим более чем 100 000 анализов одной и той же строки. Но я принял это, потому что предпочитаю сопоставление разбиению, и мне неясно, зачем мне нужен квалификатор *$. Я знаю, что без этого ничего не получится, но не знаю почему.

sthames42 03.06.2024 21:01

Вместо создания для этого регулярного выражения вы также можете положиться на надежный синтаксический анализатор (например, существующий PowerShell PSParser):

[System.Management.Automation.PSParser]::Tokenize($people, [ref]$null).Content

Обратите внимание, что парсеры обычно поддерживают больше, чем просто правила цитирования, а также строки здесь , умные кавычки и операторы, которые конкретно не включены или не исключены в вашем вопросе и примере, а это означает, что эти функции можно либо считать потерей или бонус к этому вопросу.

Это хорошее предложение, @iRon, и я подумаю об этом в будущем. Но решение RegEx будет работать на многих языках, кроме PowerShell. Спасибо.

sthames42 03.06.2024 21:03

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