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

问题

最近有个问题本地执行

ssh -p 8084 [email protected] \"ssh -p 22 [email protected] '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
     * " \t\n\r\f": 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 null
     */
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 [email protected] \"ssh -p 22 [email protected] '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命令参数字符串和数组比较_第1张图片
看出解析出来后字符数组是存在问题的

总结

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

你可能感兴趣的:(java,SE,java,服务器)