> 文章列表 > Runtime命令参数字符串和数组比较

Runtime命令参数字符串和数组比较

Runtime命令参数字符串和数组比较

问题

最近有个问题本地执行

ssh -p 8084 root@10.224.122.51 \\"ssh -p 22 root@192.168.5.157 'mkdir -p /opt/dw-release/pdld-admin'\\"

程序执行总是报错: No such file or directory
但是直接在终端执行正常,这就很奇怪。肯定能推出是程序执行做了什么导致执行失败。
用的自带Runtime.exec(String)方法执行,后面网上搜到另外参数String[] 当时感觉是这个问题。

比较

参数是字符串
Runtime.java

public Process exec(String command) throws IOException {return exec(command, null, null);}public Process exec(String command, String[] envp, File dir)throws IOException {if (command.isEmpty())throw new IllegalArgumentException("Empty command");StringTokenizer st = new StringTokenizer(command);String[] cmdarray = new String[st.countTokens()];for (int i = 0; st.hasMoreTokens(); i++)cmdarray[i] = st.nextToken();return exec(cmdarray, envp, dir);}

参数是字符串数组

public Process exec(String cmdarray[]) throws IOException {return exec(cmdarray, null, null);}public Process exec(String[] cmdarray, String[] envp, File dir)throws IOException {return new ProcessBuilder(cmdarray).environment(envp).directory(dir).start();}

通过观察可以看到参数是字符串时候,做个new StringTokenizer(command) 处理,转成字符串数组,在调用字符串数组方式执行。
出现问题的地方就是在这。

继续看StringTokenizer类处理(构造方法)

 /*** Constructs a string tokenizer for the specified string. The* tokenizer uses the default delimiter set, which is* <code>"&nbsp;\t\n\r\f"</code>: the space character,* the tab character, the newline character, the carriage-return character,* and the form-feed character. Delimiter characters themselves will* not be treated as tokens.** @param   str   a string to be parsed.* @exception NullPointerException if str is <CODE>null</CODE>*/
public StringTokenizer(String str) {this(str, " \\t\\n\\r\\f", false);}

从注释中可以看" \\t\\n\\r\\f" 有这参数,感觉到是对这个进行作为分隔符

回头看看Runtime.java

StringTokenizer st = new StringTokenizer(command);String[] cmdarray = new String[st.countTokens()];for (int i = 0; st.hasMoreTokens(); i++)cmdarray[i] = st.nextToken();

调用netxToken方法获取字符串数组

public String nextToken() {/** If next position already computed in hasMoreElements() and* delimiters have changed between the computation and this invocation,* then use the computed value.*/currentPosition = (newPosition >= 0 && !delimsChanged) ?newPosition : skipDelimiters(currentPosition);/* Reset these anyway */delimsChanged = false;newPosition = -1;if (currentPosition >= maxPosition)throw new NoSuchElementException();int start = currentPosition;currentPosition = scanToken(currentPosition);return str.substring(start, currentPosition);}

看最后三行,就是截取子串方式。看起始和结束位置
结束位置

/*** Skips ahead from startPos and returns the index of the next delimiter* character encountered, or maxPosition if no such delimiter is found.*/private int scanToken(int startPos) {int position = startPos;while (position < maxPosition) {if (!hasSurrogates) {char c = str.charAt(position);if ((c <= maxDelimCodePoint) && (delimiters.indexOf(c) >= 0))break;position++;} else {int c = str.codePointAt(position);if ((c <= maxDelimCodePoint) && isDelimiter(c))break;position += Character.charCount(c);}}if (retDelims && (startPos == position)) {if (!hasSurrogates) {char c = str.charAt(position);if ((c <= maxDelimCodePoint) && (delimiters.indexOf(c) >= 0))position++;} else {int c = str.codePointAt(position);if ((c <= maxDelimCodePoint) && isDelimiter(c))position += Character.charCount(c);}}return position;}

注释简单翻译就是起始位置到下个分隔符位置
(这里只是简单了解功能,没详细看实现过程)

验证

public static void main(String[] args) {String command = "ssh -p 8084 root@10.224.122.51 \\"ssh -p 22 root@192.168.5.157 'mkdir -p /opt/dw-release/pdld-admin'\\"";StringTokenizer st = new StringTokenizer(command);String[] cmdarray = new String[st.countTokens()];for (int i = 0; st.hasMoreTokens(); i++) {cmdarray[i] = st.nextToken();}Arrays.stream(cmdarray).forEach(System.out::println);}

结果:
Runtime命令参数字符串和数组比较
看出解析出来后字符数组是存在问题的

总结

使用Runtime执行命令,尽可能使用命令字符串数组方式作为参数,而不是使用字符串
或者可以自己重写Runtime的exec方法中分隔字符串实现