我希望拥有以下字符串
!cmd 45 90 "An argument" Another AndAnother "Another one in quotes"
成为以下
的数组{ "!cmd", "45", "90", "An argument", "Another", "AndAnother", "Another one in quotes" }
我试过
new StringTokenizer(cmd, "\"")
但是这会返回“Another”和“AndAnother as”另一个AndAnother“这不是预期的效果。
感谢。
编辑: 我再次改变了这个例子,这次我认为它解释了最好的情况,尽管它和第二个例子没什么不同。
答案 0 :(得分:52)
在这种情况下,使用java.util.regex.Matcher
并执行find()
而不是任何类型的split
要容易得多。
也就是说,不是在标记之间定义分隔符的模式,而是为标记本身定义模式。
以下是一个例子:
String text = "1 2 \"333 4\" 55 6 \"77\" 8 999";
// 1 2 "333 4" 55 6 "77" 8 999
String regex = "\"([^\"]*)\"|(\\S+)";
Matcher m = Pattern.compile(regex).matcher(text);
while (m.find()) {
if (m.group(1) != null) {
System.out.println("Quoted [" + m.group(1) + "]");
} else {
System.out.println("Plain [" + m.group(2) + "]");
}
}
以上打印(as seen on ideone.com):
Plain [1]
Plain [2]
Quoted [333 4]
Plain [55]
Plain [6]
Quoted [77]
Plain [8]
Plain [999]
模式基本上是:
"([^"]*)"|(\S+)
\_____/ \___/
1 2
有两个替补:
请注意,这不会处理引用段中的转义双引号。如果您需要这样做,那么模式会变得更复杂,但Matcher
解决方案仍然有效。
请注意,StringTokenizer
是遗留类。建议使用java.util.Scanner
或String.split
,或者当然java.util.regex.Matcher
,以获得最大的灵活性。
答案 1 :(得分:6)
用老式的方式做。创建一个查看for循环中每个字符的函数。如果角色是一个空格,请将所有内容(不包括空格)取出并添加为数组的条目。注意位置,并再次执行相同的操作,将空格后的下一部分添加到数组中。遇到双引号时,将名为'inQuote'的布尔值标记为true,并在inQuote为true时忽略空格。当inQuote为true时命中引号时,将其标记为false并在遇到空格时返回到破坏状态。然后,您可以根据需要扩展它以支持转义字符等。
这可以用正则表达式完成吗?我想,我不知道。但是整个函数的写入时间比这个回复要少。
答案 2 :(得分:2)
以老式的方式:
public static String[] split(String str) {
str += " "; // To detect last token when not quoted...
ArrayList<String> strings = new ArrayList<String>();
boolean inQuote = false;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '"' || c == ' ' && !inQuote) {
if (c == '"')
inQuote = !inQuote;
if (!inQuote && sb.length() > 0) {
strings.add(sb.toString());
sb.delete(0, sb.length());
}
} else
sb.append(c);
}
return strings.toArray(new String[strings.size()]);
}
我认为嵌套引号是非法的,并且也可以省略空标记。
答案 3 :(得分:2)
Apache Commons to rescue!
import org.apache.commons.text.StringTokenizer
import org.apache.commons.text.matcher.StringMatcher
import org.apache.commons.text.matcher.StringMatcherFactory
@Grab(group='org.apache.commons', module='commons-text', version='1.3')
def str = /is this 'completely "impossible"' or """slightly"" impossible" to parse?/
StringTokenizer st = new StringTokenizer( str )
StringMatcher sm = StringMatcherFactory.INSTANCE.quoteMatcher()
st.setQuoteMatcher( sm )
println st.tokenList
输出:
[是,完全&#34;不可能&#34;,或者,&#34;略微&#34;不可能,要解析?]
一些注意事项:
@Grab
行提供了您需要的依赖关系类型的线索
(例如在build.gradle
)...或者只包括.jar
当然是classpath StringTokenizer
这里不
java.util.StringTokenizer
...正如import
行显示的那样
org.apache.commons.text.StringTokenizer
def str = ...
line是一种在Groovy中生成String
的方法,其中包含两者
单引号和双引号,无需转入StringMatcherFactory
here:正如您所看到的,INSTANCE
可以为您提供
一堆不同的StringMatcher
。你甚至可以自己动手:
但您需要检查StringMatcherFactory
源代码
看看它是如何完成的。PS为什么使用Apache Commons比任何其他解决方案更好? 除了重新发明轮子这一事实之外,我至少可以想到两个原因:
PPS没有什么可以让你把Apache代码视为神秘的&#34;黑盒子&#34;。来源是开放的,通常完美地写入&#34;可访问&#34; Java的。因此,您可以自由地检查您的内容是如何完成的。这样做通常很有启发性。
<强> 后 强>
对ArtB的问题充满好奇,我看了一眼来源:
我们在StringMatcherFactory.java中看到:
private static final AbstractStringMatcher.CharSetMatcher QUOTE_MATCHER = new AbstractStringMatcher.CharSetMatcher(
"'\"".toCharArray());
......相当沉闷...
所以这导致人们看看StringTokenizer.java:
public StringTokenizer setQuoteMatcher(final StringMatcher quote) {
if (quote != null) {
this.quoteMatcher = quote;
}
return this;
}
好的......然后,在同一个java文件中:
private int readWithQuotes(final char[] srcChars ...
包含评论:
// If we've found a quote character, see if it's followed by a second quote. If so, then we need to actually put the quote character into the token rather than end the token.
......我无法再进一步了解这些线索。你可以选择:你的&#34; hackish&#34;解决方案,系统地预处理你的字符串,然后提交它们进行标记,将| \\\&#34; | s转换为| \&#34; \&#34; | s ...(即你替换每个字符串的地方) | \&#34; | with | &#34;&#34; |)...
或者......你检查org.apache.commons.text.StringTokenizer.java以弄清楚如何调整代码。它是一个小文件。我不认为那会很困难。然后你编译,基本上是一个Apache代码的分支。
我认为它无法配置。但是如果你找到了一个有意义的代码调整解决方案,你可以将它提交给Apache,然后它可能会被接受用于代码的下一次迭代,并且你的名字至少会出现在&#34;功能请求中。 Apache的一部分:这可能是kleos的一种形式,通过它你可以实现编程永生......
答案 4 :(得分:1)
最近遇到了类似的问题,其中必须将命令行参数忽略引号link进行拆分。
一种可能的情况:
“ / opt / jboss-eap / bin / jboss-cli.sh --connect --controller = localhost:9990 -c命令= \”部署/app/jboss-eap-7.1/standalone/updates/sample。战争--force \“”
这必须拆分为
/opt/jboss-eap/bin/jboss-cli.sh
--connect
--controller=localhost:9990
-c
command="deploy /app/jboss-eap-7.1/standalone/updates/sample.war --force"
只需添加到@polygenelubricants的答案中,在引号匹配器之前和之后都可以有任何非空格字符。
"\\S*\"([^\"]*)\"\\S*|(\\S+)"
示例:
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Tokenizer {
public static void main(String[] args){
String a = "/opt/jboss-eap/bin/jboss-cli.sh --connect --controller=localhost:9990 -c command=\"deploy " +
"/app/jboss-eap-7.1/standalone/updates/sample.war --force\"";
String b = "Hello \"Stack Overflow\"";
String c = "cmd=\"abcd efgh ijkl mnop\" \"apple\" banana mango";
String d = "abcd ef=\"ghij klmn\"op qrst";
String e = "1 2 \"333 4\" 55 6 \"77\" 8 999";
List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("\\S*\"([^\"]*)\"\\S*|(\\S+)");
Matcher regexMatcher = regex.matcher(a);
while (regexMatcher.find()) {
matchList.add(regexMatcher.group());
}
System.out.println("matchList="+matchList);
}
}
输出:
matchList = [/ opt / jboss-eap / bin / jboss-cli.sh,--connect,--controller = localhost:9990,-c,command =“ deploy /app/jboss-eap-7.1/standalone /updates/sample.war --force“]
答案 5 :(得分:0)
你在这里的例子只需要用双引号字符分开。
答案 6 :(得分:0)
这是一个老问题,但这是我作为有限状态机的解决方案。
高效,可预测且没有花哨的技巧。
100%的测试覆盖率。
拖放到您的代码中。
/**
* Splits a command on whitespaces. Preserves whitespace in quotes. Trims excess whitespace between chunks. Supports quote
* escape within quotes. Failed escape will preserve escape char.
*
* @return List of split commands
*/
static List<String> splitCommand(String inputString) {
List<String> matchList = new LinkedList<>();
LinkedList<Character> charList = inputString.chars()
.mapToObj(i -> (char) i)
.collect(Collectors.toCollection(LinkedList::new));
// Finite-State Automaton for parsing.
CommandSplitterState state = CommandSplitterState.BeginningChunk;
LinkedList<Character> chunkBuffer = new LinkedList<>();
for (Character currentChar : charList) {
switch (state) {
case BeginningChunk:
switch (currentChar) {
case '"':
state = CommandSplitterState.ParsingQuote;
break;
case ' ':
break;
default:
state = CommandSplitterState.ParsingWord;
chunkBuffer.add(currentChar);
}
break;
case ParsingWord:
switch (currentChar) {
case ' ':
state = CommandSplitterState.BeginningChunk;
String newWord = chunkBuffer.stream().map(Object::toString).collect(Collectors.joining());
matchList.add(newWord);
chunkBuffer = new LinkedList<>();
break;
default:
chunkBuffer.add(currentChar);
}
break;
case ParsingQuote:
switch (currentChar) {
case '"':
state = CommandSplitterState.BeginningChunk;
String newWord = chunkBuffer.stream().map(Object::toString).collect(Collectors.joining());
matchList.add(newWord);
chunkBuffer = new LinkedList<>();
break;
case '\\':
state = CommandSplitterState.EscapeChar;
break;
default:
chunkBuffer.add(currentChar);
}
break;
case EscapeChar:
switch (currentChar) {
case '"': // Intentional fall through
case '\\':
state = CommandSplitterState.ParsingQuote;
chunkBuffer.add(currentChar);
break;
default:
state = CommandSplitterState.ParsingQuote;
chunkBuffer.add('\\');
chunkBuffer.add(currentChar);
}
}
}
if (state != CommandSplitterState.BeginningChunk) {
String newWord = chunkBuffer.stream().map(Object::toString).collect(Collectors.joining());
matchList.add(newWord);
}
return matchList;
}
private enum CommandSplitterState {
BeginningChunk, ParsingWord, ParsingQuote, EscapeChar
}
答案 7 :(得分:0)
另一种老派的方式是:
public static void main(String[] args) {
String text = "One two \"three four\" five \"six seven eight\" nine \"ten\"";
String[] splits = text.split(" ");
List<String> list = new ArrayList<>();
String token = null;
for(String s : splits) {
if(s.startsWith("\"") ) {
token = "" + s;
} else if (s.endsWith("\"")) {
token = token + " "+ s;
list.add(token);
token = null;
} else {
if (token != null) {
token = token + " " + s;
} else {
list.add(s);
}
}
}
System.out.println(list);
}
输出: - [一,二,&#34;三四&#34;,五,&#34;六七八&#34;,九]
答案 8 :(得分:0)
private static void findWords(String str) {
boolean flag = false;
StringBuilder sb = new StringBuilder();
for(int i=0;i<str.length();i++) {
if(str.charAt(i)!=' ' && str.charAt(i)!='"') {
sb.append(str.charAt(i));
}
else {
System.out.println(sb.toString());
sb = new StringBuilder();
if(str.charAt(i)==' ' && !flag)
continue;
else if(str.charAt(i)=='"') {
if(!flag) {
flag=true;
}
i++;
while(i<str.length() && str.charAt(i)!='"') {
sb.append(str.charAt(i));
i++;
}
flag=false;
System.out.println(sb.toString());
sb = new StringBuilder();
}
}
}
}
答案 9 :(得分:0)
这就是我本人用于在命令行中拆分参数之类的东西。
它易于调整以用于多个定界符和引号,它可以处理单词内的引号(例如al' 'pha
),它支持转义(引号和空格),而且非常宽大。
public final class StringUtilities {
private static final List<Character> WORD_DELIMITERS = Arrays.asList(' ', '\t');
private static final List<Character> QUOTE_CHARACTERS = Arrays.asList('"', '\'');
private static final char ESCAPE_CHARACTER = '\\';
private StringUtilities() {
}
public static String[] splitWords(String string) {
StringBuilder wordBuilder = new StringBuilder();
List<String> words = new ArrayList<>();
char quote = 0;
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
if (c == ESCAPE_CHARACTER && i + 1 < string.length()) {
wordBuilder.append(string.charAt(++i));
} else if (WORD_DELIMITERS.contains(c) && quote == 0) {
words.add(wordBuilder.toString());
wordBuilder.setLength(0);
} else if (quote == 0 && QUOTE_CHARACTERS.contains(c)) {
quote = c;
} else if (quote == c) {
quote = 0;
} else {
wordBuilder.append(c);
}
}
if (wordBuilder.length() > 0) {
words.add(wordBuilder.toString());
}
return words.toArray(new String[0]);
}
}
答案 10 :(得分:-1)
试试这个:
String str = "One two \"three four\" five \"six seven eight\" nine \"ten\"";
String strArr[] = str.split("\"|\s");
这有点棘手,因为你需要逃避双引号。这个正则表达式应该使用空格(\ s)或双引号来标记字符串。
您应该使用String的split
方法,因为它接受正则表达式,而StringTokenizer
中的分隔符的构造函数参数不接受。在上面提供的内容结尾处,您可以添加以下内容:
String s;
for(String k : strArr) {
s += k;
}
StringTokenizer strTok = new StringTokenizer(s);
答案 11 :(得分:-1)
试试这个:
String str = "One two \"three four\" five \"six seven eight\" nine \"ten\"";
String[] strings = str.split("[ ]?\"[ ]?");
答案 12 :(得分:-1)
我不知道你尝试做什么的上下文,但看起来你试图解析命令行参数。总的来说,这对于所有逃避问题都非常棘手;如果这是你的目标,我会亲自看看像JCommander这样的东西。