Regex para dividir uma string usando espaço quando não estiver entre aspas simples ou duplas


114

Eu sou novo em expressões regulares e gostaria de receber sua ajuda. Estou tentando montar uma expressão que dividirá a string de exemplo usando todos os espaços que não estão entre aspas simples ou duplas. Minha última tentativa é parecida com esta: (?!")e não está funcionando bem. Está se dividindo no espaço antes da citação.

Exemplo de entrada:

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

Saída desejada:

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

Observe isso "will be"e 'regular expression'mantenha o espaço entre as palavras.


Você está realmente usando o método "split" ou o loop com o método "find" no Matcher seria suficiente?
Erickson

9
"e agora ele tem dois problemas"

Respostas:


251

Não entendo por que todos os outros estão propondo expressões regulares tão complexas ou códigos tão longos. Essencialmente, você deseja obter dois tipos de coisas de sua string: sequências de caracteres que não são espaços ou aspas e sequências de caracteres que começam e terminam com aspas, sem aspas entre dois tipos de aspas. Você pode combinar facilmente essas coisas com esta expressão regular:

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

Eu adicionei os grupos de captura porque você não quer as aspas na lista.

Este código Java constrói a lista, adicionando o grupo de captura se correspondesse para excluir as aspas e adicionando a correspondência de regex geral se o grupo de captura não correspondesse (uma palavra sem aspas foi correspondida).

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());
    }
} 

Se você não se importa em ter as aspas na lista retornada, pode usar um código muito mais simples:

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

1
Jan, obrigado pela sua resposta. BTW, sou um grande fã do EditPad.
carlsz

E se eu quiser permitir aspas de escape nas strings \"?
Monstieur

3
O problema com esta resposta é com citação incomparável: John's motherresultados divididos em[John, s, mother]
leonbloy

2
Para corrigir os contornos questão leonbloy, você pode reordenar os operandos um pouco e omitir as aspas do grupo espaços em branco: "([^"]*)"|'([^']*)'|[^\s]+.
Ghostkeeper

1
Baseando-se esta e outras respostas, o seguinte regex permite escapar caracteres dentro das aspas: "([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+. Consulte stackoverflow.com/questions/5695240/…
Limnic

15

Existem várias questões no StackOverflow que cobrem essa mesma questão em vários contextos usando expressões regulares. Por exemplo:

ATUALIZAÇÃO : Exemplo de regex para lidar com strings entre aspas simples e duplas. Ref: Como posso dividir em uma string, exceto quando entre aspas?

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

Testei isso com um snippet Perl rápido e a saída foi reproduzida abaixo. Também funciona para strings vazias ou strings apenas com espaço em branco se estiverem entre aspas (não tenho certeza se isso é desejado ou não).

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

Observe que isso inclui os próprios caracteres de aspas nos valores correspondentes, embora você possa removê-los com uma substituição de string ou modificar o regex para não incluí-los. Vou deixar isso como um exercício para o leitor ou outro pôster por enquanto, já que 2 da manhã é tarde demais para mexer mais com as expressões regulares;)


Acho que sua regex permite aspas incompatíveis, por exemplo, "será 'e' expressões regulares".
Zach Scrivena

@Zach - você está certo, ele faz ... atualizou para corrigir isso apenas no caso
Jay


3

A regex de Jan Goyvaerts é a melhor solução que encontrei até agora, mas também cria correspondências vazias (nulas), que ele exclui em seu programa. Essas correspondências vazias também aparecem em testadores de regex (por exemplo, rubular.com). Se você girar as pesquisas (primeiro procure as partes citadas e, em seguida, as palavras separadas por espaço), você pode fazer isso uma vez com:

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

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

Isso irá corresponder aos espaços não entre aspas duplas. Tenho que usar min, max {0,99999} porque o Java não oferece suporte * e + em lookbehind.


1

Provavelmente será mais fácil pesquisar a string, agarrando cada parte, do que dividi-la.

A razão é que você pode dividi-lo nos espaços antes e depois "will be". Mas, não consigo pensar em nenhuma maneira de especificar ignorando o espaço entre dentro de uma divisão.

(não Java real)

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"
    }
}

Além disso, capturar aspas simples pode levar a problemas:

"Foo's Bar 'n Grill"

//=>

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

Sua solução não lida com strings entre aspas simples, que são parte do exemplo de Carl.
Jan Goyvaerts

1

String.split()não é útil aqui porque não há como distinguir entre espaços entre aspas (não dividir) e aqueles fora (dividir). Matcher.lookingAt()é provavelmente o que você precisa:

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);
    }
}

que produz a seguinte saída:

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

Eu gostei da abordagem de Marcus, no entanto, eu a modifiquei para que pudesse permitir texto perto das aspas e suportar ambos os caracteres de aspas "e '. Por exemplo, eu precisava de a =" algum valor "para não dividi-lo em [a =," algum valor "].

(?<!\\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

A abordagem de Jan é ótima, mas aqui está outra para registro.

Se você realmente deseja dividir conforme mencionado no título, mantendo as aspas entre "will be"e 'regular expression', então você pode usar este método que está diretamente fora de Corresponder (ou substituir) um padrão, exceto nas situações s1, s2, s3 etc.

O regex:

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

As duas alternâncias esquerdas combinam completo 'quoted strings'e "double-quoted strings". Iremos ignorar essas correspondências. O lado direito corresponde e captura espaços para o Grupo 1, e sabemos que eles são os espaços certos porque não foram correspondidos pelas expressões à esquerda. Substituímos aqueles por e SplitHeredepois partimos SplitHere. Novamente, este é um verdadeiro caso de divisão onde você deseja "will be", não will be.

Aqui está uma implementação funcional completa (veja os resultados na demonstração online ).

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

Se você estiver usando c #, você pode usar

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);

Eu adicionei especificamente " | <(? [\ W \ s] *)> " para destacar que você pode especificar qualquer caractere para agrupar frases. (Neste caso, estou usando <> para agrupar.

O resultado é:

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

0

Estou razoavelmente certo de que isso não é possível usando apenas expressões regulares. Verificar se algo está contido em alguma outra tag é uma operação de análise. Este parece ser o mesmo problema de tentar analisar XML com um regex - não pode ser feito corretamente. Você pode conseguir o resultado desejado aplicando repetidamente um regex não ganancioso e não global que corresponda às strings entre aspas e, uma vez que não consiga encontrar mais nada, divida-o nos espaços ... que tem um número de problemas, incluindo o controle da ordem original de todas as substrings. Sua melhor aposta é apenas escrever uma função realmente simples que itera sobre a string e retire os tokens que você deseja.


É possível com um regex, veja alguns dos exemplos que vinculei. Existem algumas variações sobre isso e já vi várias perguntas semelhantes no SO que tratam disso por meio de expressões regulares.
Jay

1
Saber quando não usar regex é um conhecimento mais útil do que ser capaz de criar um (?: (['"]) (. *?) (? <! \) (?> \\\) * \ 1 | ([ ^ \ s] +))
René

0

Alguns ajustes úteis na resposta aceita de Jan:

(['"])((?:\\\1|.)+?)\1|([^\s"']+)
  • Permite aspas com escape dentro de strings entre aspas
  • Evita repetir o padrão para aspas simples e duplas; isso também simplifica a adição de mais símbolos de citação, se necessário (às custas de mais um grupo de captura)

Isso quebra palavras com apóstrofos, comoyou're
Design by Adrian

0

Você também pode tentar isso:

    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]);
        }
    }

Você realmente deve adicionar alguma explicação de por que isso deve funcionar - você também pode adicionar o código, bem como os comentários no próprio código - em sua forma atual, não fornece qualquer explicação que possa ajudar o resto da comunidade a entender o que você fez para resolver / responder à questão. Isso é especialmente importante para perguntas que já têm respostas.
ishmaelMakitla

0

O seguinte retorna uma matriz de argumentos. Os argumentos são a variável 'comando' dividida em espaços, a menos que incluídos entre aspas simples ou duplas. As correspondências são então modificadas para remover as aspas simples e duplas.

using System.Text.RegularExpressions;

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

2
Você pode adicionar um pouco de explicação à sua resposta para que outras pessoas possam entendê-la mais facilmente? Idealmente, queremos evitar respostas apenas de código.
Jaquez

0

1º one-liner usando 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.]

não divida no espaço em branco se o espaço em branco estiver entre aspas simples ou duplas
divida no espaço em branco quando os 255 caracteres à esquerda e todos os caracteres à direita do espaço não forem aspas simples ou duplas

adaptado da postagem original (lida apenas com aspas duplas)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.