Предыстория: в tcsh мы можем использовать одинарные кавычки внутри одинарных кавычек вот так (как сказано здесь):
echo 'It'''s Shell Programming'
Я хочу создать метод, который избегает одинарных кавычек и печатает его в tcsh-скрипте, который я создаю на лету с помощью Java. Это должно делать:
- Если есть
'
(два символа) в строке, она будет экранирована так:'''
. - Если есть
'
(один символ безперед ним) в строке, он ускользнет от него, поэтому
'''
.
Я написал для этого следующий метод:
private static String escapeStr(final String str) {
String result = "";
for (int index = 0; index < str.length(); ++index) {
if (str.charAt(index) == '\') {
if (index + 1 < str.length() && str.charAt(index + 1) == ''') {
result += "\'\''";
index++;
} else {
result += str.charAt(index);
}
} else if (str.charAt(index) == ''') {
result += "'\''";
} else {
result += str.charAt(index);
}
}
return result;
}
Похоже, он выполняет свою работу, но мне действительно не нравится, как я его реализовал. Трудно читать, так как в нем довольно много символы. Также я делаю
index++
внутри цикла, который выглядит как плохой дизайн. Есть ли здесь лучший подход?
Добавлю, что хочу вызвать метод так:
fileWriter.write("echo '" + escapeStr(cmd) + "'n");
Вся суть этой части в том, что при побеге будет напечатан cmd
как есть. Я завершаю команду '
поэтому он не будет оценивать такие вещи, как переменные среды. Поэтому, если cmd уже содержит одинарную кавычку, он потерпит неудачу. Мне нужно будет избегать одинарных кавычек и того, что я пытался сделать.
2 ответа
Я бы предложил решение, основанное на регулярные выражения так:
import static org.junit.Assert.assertThat;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
public class EscapeQuotesTest {
Pattern escapedQuotePattern = Pattern.compile("(\\)?(')(')");
private String escapeString(String input) {
Matcher escapedQuote = escapedQuotePattern.matcher(input);
escapedQuote.find();
Optional<String> escapeChar = Optional.ofNullable(escapedQuote.group(1));
String result = String.format("%s%s\'%s", escapeChar.orElse(""), escapedQuote.group(2), escapedQuote.group(3));
return result;
}
@Test
public void preseveLeadingBackslash() {
String input = "\''";
String result = escapeString(input);
assertThat(" The escape char survived", result, CoreMatchers.containsString("\'\''"));
}
@Test
public void noLeadingBackslash() {
String input = "''";
String result = escapeString(input);
assertThat("no escape char at beginning", result, CoreMatchers.containsString("'\''"));
}
}
Когда вы найдете '
последовательность, вы справляетесь со всем за один раз. Похоже, это источник ваших index++
проблема. Это также означает, что у вас есть повторяющиеся else
пункт там. Небольшим улучшением будет обработка только когда вы встречаетесь
'
, затем обработайте последующие '
на следующем проходе по петле. Насколько я могу судить, обработка '
на самом деле не зависит от того, какой персонаж был до этого. Итак, вы получите:
private static String escapeStr(final String str) {
String result = "";
for (int index = 0; index < str.length(); ++index) {
if (str.charAt(index) == '\' &&
(index + 1 < str.length() && str.charAt(index + 1) == ''')) {
result += "\";
} else if (str.charAt(index) == ''') {
result += "'\''";
} else {
result += str.charAt(index);
}
}
return result;
}
Вы также создаете String
в цикле, а не с помощью StringBuilder
. Для маленьких струн это, вероятно, не будет иметь большого значения, но может сложиться для больших. Если вы хотите использовать StringBuilder
это будет выглядеть так:
private static String escapeStr(final String str) {
StringBuilder result = new StringBuilder();
for (int index = 0; index < str.length(); ++index) {
if (str.charAt(index) == '\' &&
(index + 1 < str.length() && str.charAt(index + 1) == ''')) {
result.append("\");
} else if (str.charAt(index) == ''') {
result.append("'\''");
} else {
result.append(str.charAt(index));
}
}
return result.toString();
}
Здесь рекомендуется использовать StringBuilder? Если да, то как бы вы это сделали?
– vesii
@vesii Я добавил версию StringBuilder. Как правило, StringBuilder работает быстрее, когда вы создаете большие строки, бит за битом, потому что он оптимизирует множество перераспределений, которые требуются неизменяемым
Strings
. Когда вы выполняете небольшое число, нет большой разницы, и иногда использование строк будет быстрее, но в целом, если вы зацикливаете, я бы ожидалStringBuilder
(но вы лучше представляете, насколько велики сценарии и действительно ли это будет иметь значение).StringBuilder
Некоторым людям может быть труднее читать, чем обычноString
конкатенация.— оборона
Его все еще трудно читать, и вам нужно будет добавить try-catch no?
– vesii
@vesii «Все еще трудно читать» да, вы не можете обойти комбинированное экранирование для специальных символов Java Strings и Regular Expression. — «вам нужно будет добавить пробную ловушку, нет?» Я написал SSCCE, так что нет,
try/catch
блок не нужен.— Тимоти Тракл