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


114

Я новичок в регулярных выражениях и буду признателен за вашу помощь. Я пытаюсь составить выражение, которое разделит строку примера, используя все пробелы, не окруженные одинарными или двойными кавычками. Моя последняя попытка выглядит так: (?!")и не совсем работает. Он разбивается на пространство перед цитатой.

Пример ввода:

This is a string that "will be" highlighted when your 'regular expression' matches something.

Желаемый результат:

This
is
a
string
that
will be
highlighted
when
your
regular expression
matches
something.

Обратите внимание на это "will be"и 'regular expression'сохраните пробел между словами.


Вы действительно используете метод «split», или будет достаточно цикла с методом «find» в Matcher?
erickson

9
«и теперь у него две проблемы»

Ответы:


251

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

[^\s"']+|"([^"]*)"|'([^']*)'

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

Этот код Java создает список, добавляя группу захвата, если она соответствует, чтобы исключить кавычки, и добавляя общее совпадение регулярного выражения, если группа захвата не соответствует (было найдено слово без кавычек).

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    if (regexMatcher.group(1) != null) {
        // Add double-quoted string without the quotes
        matchList.add(regexMatcher.group(1));
    } else if (regexMatcher.group(2) != null) {
        // Add single-quoted string without the quotes
        matchList.add(regexMatcher.group(2));
    } else {
        // Add unquoted word
        matchList.add(regexMatcher.group());
    }
} 

Если вы не возражаете, чтобы в возвращаемом списке были кавычки, вы можете использовать гораздо более простой код:

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    matchList.add(regexMatcher.group());
} 

1
Ян, спасибо за ответ. Кстати, я большой поклонник EditPad.
carlsz

Что, если я хочу разрешить использование экранированных кавычек в строках \"?
Monstieur

3
Проблема с этим ответом заключается в непревзойденной цитате: John's motherрезультаты разделились[John, s, mother]
leonbloy

2
Чтобы устранить проблему leonbloy контуров, вы можете изменить порядок операндов немного и опускаете цитаты из пробельной-группы: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
Основываясь на этом и другие ответы, следующее регулярное выражение позволяет избежать символы внутри кавычек: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. См stackoverflow.com/questions/5695240/...
озерный

15

На StackOverflow есть несколько вопросов, которые охватывают этот же вопрос в различных контекстах с использованием регулярных выражений. Например:

ОБНОВЛЕНИЕ : образец регулярного выражения для обработки строк с одинарными и двойными кавычками. Ссылка: Как я могу разбить строку, кроме как внутри кавычек?

m/('.*?'|".*?"|\S+)/g 

Протестировал это с помощью быстрого фрагмента Perl, и результат был таким, как показано ниже. Также работает для пустых строк или строк, содержащих только пробелы, если они заключены в кавычки (не уверен, желательно это или нет).

This
is
a
string
that
"will be"
highlighted
when
your
'regular expression'
matches
something.

Обратите внимание, что это включает сами символы кавычек в совпадающие значения, хотя вы можете удалить это с помощью замены строки или изменить регулярное выражение, чтобы не включать их. Я оставлю это как упражнение для читателя или другого автора, так как 2 часа ночи уже слишком поздно, чтобы больше возиться с регулярными выражениями;)


Я думаю, что ваше регулярное выражение допускает несовпадающие кавычки, например, «будет» и «регулярные выражения».
Зак Скривена,

@Zach - ты прав, это так ... обновил, чтобы исправить это на всякий случай
Джей

6

Если вы хотите разрешить экранированные кавычки внутри строки, вы можете использовать что-то вроде этого:

(?:(['"])(.*?)(?<!\\)(?>\\\\)*\1|([^\s]+))

Цитированные строки будут группой 2, отдельные слова без кавычек будут группой 3.

Вы можете попробовать это на различных строках здесь: http://www.fileformat.info/tool/regex.htm или http://gskinner.com/RegExr/


3

Регулярное выражение от Яна Гойвертса - лучшее решение, которое я нашел до сих пор, но создает также пустые (нулевые) совпадения, которые он исключает в своей программе. Эти пустые совпадения также появляются в тестерах регулярных выражений (например, rubular.com). Если вы перевернете поиск по кругу (сначала найдите цитируемые части, а затем слова, разделенные пробелами), вы можете сделать это один раз с помощью:

("[^"]*"|'[^']*'|[\S]+)+

2
(?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s

Это будет соответствовать пробелам, не заключенным в двойные кавычки. Мне нужно использовать min, max {0,99999}, потому что Java не поддерживает * и + в ретроспективе.


1

Вероятно, будет проще искать строку, хватая каждую часть, чем разбивать ее.

Причина в том, что вы можете разделить его на пробелы до и после "will be". Но я не могу придумать никакого способа указать игнорирование пробела между разделителями.

(не настоящая Java)

string = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";

regex = "\"(\\\"|(?!\\\").)+\"|[^ ]+"; // search for a quoted or non-spaced group
final = new Array();

while (string.length > 0) {
    string = string.trim();
    if (Regex(regex).test(string)) {
        final.push(Regex(regex).match(string)[0]);
        string = string.replace(regex, ""); // progress to next "word"
    }
}

Кроме того, захват одинарных кавычек может привести к проблемам:

"Foo's Bar 'n Grill"

//=>

"Foo"
"s Bar "
"n"
"Grill"

Ваше решение не обрабатывает строки в одинарных кавычках, которые являются частью примера Карла.
Ян Гойвертс,

1

String.split()здесь бесполезен, потому что нет способа отличить пробелы внутри кавычек (не разделять) и те, которые находятся за их пределами (разбивать). Matcher.lookingAt()наверное то, что вам нужно:

String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
str = str + " "; // add trailing space
int len = str.length();
Matcher m = Pattern.compile("((\"[^\"]+?\")|('[^']+?')|([^\\s]+?))\\s++").matcher(str);

for (int i = 0; i < len; i++)
{
    m.region(i, len);

    if (m.lookingAt())
    {
        String s = m.group(1);

        if ((s.startsWith("\"") && s.endsWith("\"")) ||
            (s.startsWith("'") && s.endsWith("'")))
        {
            s = s.substring(1, s.length() - 1);
        }

        System.out.println(i + ": \"" + s + "\"");
        i += (m.group(0).length() - 1);
    }
}

который дает следующий результат:

0: "This"
5: "is"
8: "a"
10: "string"
17: "that"
22: "will be"
32: "highlighted"
44: "when"
49: "your"
54: "regular expression"
75: "matches"
83: "something."

1

Мне понравился подход Маркуса, однако я изменил его, чтобы разрешить текст рядом с кавычками и поддерживать символы кавычек "и". Например, мне нужно было a = "some value", чтобы не разбивать его на [a =, " какое-то значение "].

(?<!\\G\\S{0,99999}[\"'].{0,99999})\\s|(?<=\\G\\S{0,99999}\".{0,99999}\"\\S{0,99999})\\s|(?<=\\G\\S{0,99999}'.{0,99999}'\\S{0,99999})\\s"

1

Подход Яна великолепен, но вот еще один для протокола.

Если вы действительно хотели разделить, как указано в заголовке, сохранив кавычки в "will be"и 'regular expression', тогда вы можете использовать этот метод, который прямо не соответствует (или заменяет) шаблон, за исключением ситуаций s1, s2, s3 и т. Д.

Регулярное выражение:

'[^']*'|\"[^\"]*\"|( )

Два левых чередования соответствуют полному 'quoted strings'и "double-quoted strings". Мы проигнорируем эти совпадения. Правая сторона соответствует и захватывает пробелы в Группу 1, и мы знаем, что это правильные пробелы, потому что они не были сопоставлены выражениями слева. Мы заменяем их на SplitHereзатем разделенные SplitHere. Опять же, это для настоящего случая разделения, когда вы хотите "will be", а не will be.

Вот полная рабочая реализация (см. Результаты в онлайн-демонстрации ).

import java.util.*;
import java.io.*;
import java.util.regex.*;
import java.util.List;

class Program {
public static void main (String[] args) throws java.lang.Exception  {

String subject = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
Pattern regex = Pattern.compile("\'[^']*'|\"[^\"]*\"|( )");
Matcher m = regex.matcher(subject);
StringBuffer b= new StringBuffer();
while (m.find()) {
    if(m.group(1) != null) m.appendReplacement(b, "SplitHere");
    else m.appendReplacement(b, m.group(0));
}
m.appendTail(b);
String replaced = b.toString();
String[] splits = replaced.split("SplitHere");
for (String split : splits) System.out.println(split);
} // end main
} // end Program

1

Если вы используете C #, вы можете использовать

string input= "This is a string that \"will be\" highlighted when your 'regular expression' matches <something random>";

List<string> list1 = 
                Regex.Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""|'(?<match>[\w\s]*)'|<(?<match>[\w\s]*)>").Cast<Match>().Select(m => m.Groups["match"].Value).ToList();

foreach(var v in list1)
   Console.WriteLine(v);

Я специально добавил " | <(? [\ W \ s] *)> ", чтобы подчеркнуть, что вы можете указать любой символ для группировки фраз. (В этом случае я использую <> для группировки.

Выход:

This
is
a
string
that
will be
highlighted
when
your
regular expression 
matches
something random

0

Я уверен, что это невозможно, используя только регулярные выражения. Проверка того, что что-то содержится внутри какого-либо другого тега, является операцией синтаксического анализа. Это похоже на ту же проблему, что и при попытке проанализировать XML с помощью регулярного выражения - это невозможно сделать правильно. Вы можете получить желаемый результат, многократно применяя нежадное неглобальное регулярное выражение, которое соответствует цитируемым строкам, а затем, как только вы не сможете найти что-либо еще, разделите его на пробелы ... которые имеют ряд проблемы, включая отслеживание исходного порядка всех подстрок. Лучше всего написать действительно простую функцию, которая выполняет итерацию по строке и извлекает нужные вам токены.


Это возможно с регулярным выражением, см. Некоторые образцы, с которыми я связался. Есть несколько вариантов этого, и я видел несколько похожих вопросов по SO, которые решают эту проблему с помощью регулярных выражений.
Джей

1
Знать, когда не использовать регулярное выражение, более полезно, чем иметь возможность создать (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
Рене

0

Пара, надеюсь, полезных настроек принятого ответа Яна:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Позволяет использовать экранированные кавычки в кавычках
  • Избегает повторения шаблона для одинарных и двойных кавычек; это также упрощает добавление дополнительных символов кавычек при необходимости (за счет еще одной группы захвата)

Это разбивает слова с апострофами, напримерyou're
Дизайн Адриана

0

Вы также можете попробовать это:

    String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something";
    String ss[] = str.split("\"|\'");
    for (int i = 0; i < ss.length; i++) {
        if ((i % 2) == 0) {//even
            String[] part1 = ss[i].split(" ");
            for (String pp1 : part1) {
                System.out.println("" + pp1);
            }
        } else {//odd
            System.out.println("" + ss[i]);
        }
    }

Вам действительно следует добавить объяснение, почему это должно работать - вы также можете добавить код, а также комментарии в самом коде - в его текущей форме он не предоставляет никаких объяснений, которые могли бы помочь остальной части сообщества понять, что вы сделали, чтобы решить / ответить на вопрос. Это особенно важно для вопросов, на которые уже есть ответы.
ishmaelMakitla

0

Следующее возвращает массив аргументов. Аргументы - это переменная command, разделенная пробелами, если они не заключены в одинарные или двойные кавычки. Затем совпадения модифицируются для удаления одинарных и двойных кавычек.

using System.Text.RegularExpressions;

var args = Regex.Matches(command, "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").Cast<Match>
().Select(iMatch => iMatch.Value.Replace("\"", "").Replace("'", "")).ToArray();

2
Можете ли вы добавить немного пояснения к своему ответу, чтобы другим было легче его понять? В идеале мы хотим избежать ответов, содержащих только код.
Jaquez

0

1-й однострочный с использованием String.split ()

String s = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
String[] split = s.split( "(?<!(\"|').{0,255}) | (?!.*\\1.*)" );

[This, is, a, string, that, "will be", highlighted, when, your, 'regular expression', matches, something.]

не разделять пробел, если пробел заключен в одинарные или двойные кавычки,
разделенный на пробел, когда 255 символов слева и все символы справа от пробела не являются ни одинарными, ни двойными кавычками

адаптировано из оригинального сообщения (обрабатывает только двойные кавычки)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.