Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
FULL PRODUCT VERSION : java version "1.4.2_06" Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_06-b03) Java HotSpot(TM) Client VM (build 1.4.2_06-b03, mixed mode) java version "1.5.0_06" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05) Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode) java version "1.6.0-rc" Java(TM) SE Runtime Environment (build 1.6.0-rc-b98) Java HotSpot(TM) Client VM (build 1.6.0-rc-b98, mixed mode, sharing) ADDITIONAL OS VERSION INFORMATION : Microsoft Windows XP [Version 5.1.2600] or any other Windows A DESCRIPTION OF THE PROBLEM : On Windows platform Runtime.exec(String[] cmdarray) does not pass correctly command line arguments, if one of them contains double quotes ("). Examples Passing/Expected --> Actual { "ab\"c", "d\"ef" } --> { "abc def" } { "a b \" c", "d \" e f" } --> { "a b ", "c d", "e f " } { "a", "", "b" } --> { "a", "b" } { "\" a" } --> java.lang.IllegalArgumentException at java.lang.ProcessImpl.<init>(ProcessImpl.java:69) at java.lang.ProcessImpl.start(ProcessImpl.java:30) at java.lang.ProcessBuilder.start(ProcessBuilder.java:452) at java.lang.Runtime.exec(Runtime.java:593) at java.lang.Runtime.exec(Runtime.java:466) The problem is that on Windows platform command line arguments are passed not as separated strings but as one string. And it is up to launched program to parse this command line string. Taking this into account it becomes clear that just concatenating cmdarray arguments is not enough. The quote characters have to be escaped correctly. STEPS TO FOLLOW TO REPRODUCE THE PROBLEM : javac Main.java rem expected java CmdLine ab\"c d\"ef java CmdLine "a b \" c" "d \" e f" java CmdLine a "" b java CmdLine "\" a" rem actual java Main ab\"c d\"ef java Main "a b \" c" "d \" e f" java Main a "" b java Main "\" a" EXPECTED VERSUS ACTUAL BEHAVIOR : EXPECTED - expected the same array as passed 1. { "ab\"c", "d\"ef" } 2. { "a b \" c", "d \" e f" } 3. { "a", "", "b" } 4. { "\" a" } ACTUAL - 1. { "abc def" } 2. { "a b ", "c d", "e f " } 3. { "a", "b" } 4. java.lang.IllegalArgumentException at java.lang.ProcessImpl.<init>(ProcessImpl.java:69) at java.lang.ProcessImpl.start(ProcessImpl.java:30) at java.lang.ProcessBuilder.start(ProcessBuilder.java:452) at java.lang.Runtime.exec(Runtime.java:593) at java.lang.Runtime.exec(Runtime.java:466) REPRODUCIBILITY : This bug can be reproduced always. ---------- BEGIN SOURCE ---------- import java.io.*; class CmdLine { public static void main(String[] args) { for (int i = 0, n = args.length; i < n; ++i) System.out.println(args[i]); } } public class Main { public static void main(String[] args) throws IOException { String[] lines = new String[args.length + 2]; lines[0] = "java"; lines[1] = "CmdLine"; System.arraycopy(args, 0, lines, 2, args.length); readOut(Runtime.getRuntime().exec(lines)); } private static void readOut(Process process) throws IOException { InputStream is = process.getInputStream(); try { int b; while ((b = is.read()) != -1) System.out.write(b); } finally { is.close(); } } } ---------- END SOURCE ---------- Submitter adds: FIX FOR BUG NUMBER: 6468220 A DESCRIPTION OF THE FIX : Bug number : 6468220 Bug Description : Runtime.exec(String[] cmdarray) does not pass command line arguments correctly Diff Baseline : JDK 6 b98. Diff : diff -u -r old/java/lang/ProcessImpl.java new/java/lang/ProcessImpl.java --- old/java/lang/ProcessImpl.java 2006-09-04 14:58:47.240001400 +0300 +++ new/java/lang/ProcessImpl.java 2006-09-06 18:05:59.132961600 +0300 @@ -47,32 +47,7 @@ // Win32 CreateProcess requires cmd[0] to be normalized cmd[0] = new File(cmd[0]).getPath(); - StringBuilder cmdbuf = new StringBuilder(80); - for (int i = 0; i < cmd.length; i++) { - if (i > 0) { - cmdbuf.append(' '); - } - String s = cmd[i]; - if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) { - if (s.charAt(0) != '"') { - cmdbuf.append('"'); - cmdbuf.append(s); - if (s.endsWith("\\")) { - cmdbuf.append("\\"); - } - cmdbuf.append('"'); - } else if (s.endsWith("\"")) { - /* The argument has already been quoted. */ - cmdbuf.append(s); - } else { - /* Unmatched quote for the argument. */ - throw new IllegalArgumentException(); - } - } else { - cmdbuf.append(s); - } - } - String cmdstr = cmdbuf.toString(); + String cmdstr = Runtime.mergeCommandLine(cmd); stdin_fd = new FileDescriptor(); stdout_fd = new FileDescriptor(); diff -u -r old/java/lang/Runtime.java new/java/lang/Runtime.java --- old/java/lang/Runtime.java 2006-09-04 14:57:37.818685800 +0300 +++ new/java/lang/Runtime.java 2006-09-06 18:05:51.604923600 +0300 @@ -8,7 +8,9 @@ package java.lang; import java.io.*; -import java.util.StringTokenizer; + +import java.util.List; +import java.util.ArrayList; /** * Every Java application has a single instance of class @@ -25,6 +27,11 @@ */ public class Runtime { + private static final char SPACE = ' '; + private static final char TAB = '\t'; + private static final char DOUBLEQUOTE = '\"'; + private static final char BACKSLASH = '\\'; + private static Runtime currentRuntime = new Runtime(); /** @@ -369,6 +376,117 @@ return exec(command, envp, null); } + /** + * Splits single line into separate command line arguments with following rules: + * <ul> + * <li>Arguments are delimited by white space, which is either a space or a tab.</li> + * <li>The caret character (\n) is not recognized as an escape character or delimiter. The character is handled completely + * by the command-line parser in the operating system before being passed to the args array in the program.</li> + * <li>A string surrounded by double quotation marks ("string") is interpreted as a single argument, regardless of white + * space contained within. A quoted string can be embedded in an argument.</li> + * <li>A double quotation mark preceded by a backslash (\") is interpreted as a literal double quotation mark character (").</li> + * <li>Backslashes are interpreted literally, unless they immediately precede a double quotation mark.</li> + * <li>If an even number of backslashes is followed by a double quotation mark, one backslash is placed in the args array for every + * pair of backslashes, and the double quotation mark is interpreted as a string delimiter.</li> + * <li>If an odd number of backslashes is followed by a double quotation mark, one backslash is placed in the args array + * for every pair of backslashes, and the double quotation mark is escaped by the remaining backslash, + * causing a literal double quotation mark (") to be placed in args. </li> + * </ul> + * </p> + * <p>Example + * <table border="1"> + * <tr><th>line</th><th>args[0]</th><th>args[1]</th><th>args[2]</th></tr> + * <tr><td>"abc" d e</td><td>abc</td><td>d</td><td>e</td></tr> + * <tr><td>a\\\b d"e f"g h</td><td>a\\\b</td><td>de fg</td><td>h</td></tr> + * <tr><td>a\\\"b c d</td><td>a\"b</td><td>c</td><td>d</td></tr> + * <tr><td>a\\\\"b c" d e</td><td>a\\b c</td><td>d</td><td>e</td></tr> + * </table> + * + * @see #mergeCommandLine(String[]) + * @param line - line to be splitted + * @return separate arguments + */ + public static String[] splitCommandLine(String line) { + List<String> list = new ArrayList<String>(); + StringBuilder buffer = new StringBuilder(); + boolean inquote = false; + boolean flushed = true; + int backslashcount = 0; + for (int i = 0, n = line.length(); i < n; ++i) { + char ch = line.charAt(i); + if (ch == BACKSLASH) { + ++backslashcount; + } else { + int m = (ch == DOUBLEQUOTE)? backslashcount / 2: backslashcount; + for (int j = 0; j < m; ++j) + buffer.append(BACKSLASH); + + if ((ch == SPACE) || (ch == TAB)) { + if (inquote) { + buffer.append(ch); + flushed = false; + } else { + if (!flushed) { + list.add(buffer.toString()); + buffer.setLength(0); + flushed = true; + } + } + } else { + if ((ch == DOUBLEQUOTE) && (backslashcount % 2 == 0)) + inquote = !inquote; + else + buffer.append(ch); + flushed = false; + } + backslashcount = 0; + } + } + for (int j = 0, m = backslashcount; j < m; ++j) + buffer.append(BACKSLASH); + if (!flushed) + list.add(buffer.toString()); + return list.toArray(new String[list.size()]); + } + + /** + * Merges separate command line arguments into single line. + * This is opposite to <code>{@link #splitCommandLine(String)}</code>. + * + * @see #splitCommandLine(String) + * @param args - arguments to be merged + * @return single line + */ + public static String mergeCommandLine(String... args) { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = args.length; i < n; ++i) { + if (i > 0) + buffer.append(SPACE); + String arg = args[i]; + boolean quote = ((arg.length() == 0) || (arg.indexOf(SPACE) != -1) || (arg.indexOf(TAB) != -1)); + if (quote) + buffer.append(DOUBLEQUOTE); + int backslashcount = 0; + for (int j = 0, m = arg.length(); j < m; ++j) { + char ch = arg.charAt(j); + if (ch == BACKSLASH) { + ++backslashcount; + } else { + int l = (ch == DOUBLEQUOTE)? backslashcount * 2 + 1: backslashcount; + for (int k = 0; k < l; ++k) + buffer.append(BACKSLASH); + buffer.append(ch); + backslashcount = 0; + } + } + for (int k = 0; k < backslashcount; ++k) + buffer.append(BACKSLASH); + if (quote) + buffer.append(DOUBLEQUOTE); + } + return buffer.toString(); + } + /** * Executes the specified string command in a separate process with the * specified environment and working directory. @@ -381,11 +499,7 @@ * <code>command</code>. * * <p>More precisely, the <code>command</code> string is broken - * into tokens using a {@link StringTokenizer} created by the call - * <code>new {@link StringTokenizer}(command)</code> with no - * further modification of the character categories. The tokens - * produced by the tokenizer are then placed in the new string - * array <code>cmdarray</code>, in the same order. + * into tokens using a {@link #splitCommandLine(String)}. * * @param command a specified system command. * @@ -424,11 +538,7 @@ if (command.length() == 0) 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); + return exec(splitCommandLine(command), envp, dir); } /** Note: The command line arguments are merged in java.lang.ProcessImpl into single line in assumption that this command line will be parsed later according to the rules described here: http://msdn.microsoft.com/library/en-us/vclang98/html/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.asp Namely, 1. Arguments are delimited by white space, which is either a space or a tab. 2. The caret character is not recognized as an escape character or delimiter. The character is handled completely by the command-line parser in the operating system before being passed to the argv array in the program. 3. A string surrounded by double quotation marks ("string") is interpreted as a single argument, regardless of white space contained within. A quoted string can be embedded in an argument. 4. A double quotation mark preceded by a backslash ( \") is interpreted as a literal double quotation mark character ("). 5. Backslashes are interpreted literally, unless they immediately precede a double quotation mark. 6. If an even number of backslashes is followed by a double quotation mark, one backslash is placed in the argv array for every pair of backslashes, and the double quotation mark is interpreted as a string delimiter. 7. If an odd number of backslashes is followed by a double quotation mark, one backslash is placed in the argv array for every pair of backslashes, and the double quotation mark is escaped by the remaining backslash, causing a literal double quotation mark (") to be placed in argv. So, java.lang.ProcessImpl (windows specific class) uses Runtime.mergeCommandLine. However, changes only in java.lang.ProcessImpl may affect windows users that use Runtime.exec(String command, ...) instead of Runtime.exec(String[] cmdarray, ...). Despite Runtime.exec(String command, ...) somtimes tokenizes command line arguments incorrectly, these arguments were gathered up so, they could be parsed more correctly. For example, these command "a \"b c\" d" was divided into 4 tokens { "a", "\"b", "c\"", "d" } and then they were connected to the single line "a \"b c\" d", which was parsed by launched program into 3 args { "a", "b c", "d" } as most users expect. Thus we have also change tokenization algorithm to meet Runtime.exec(String command, ...) users expectation. So, Runtime.exec(String command, ...) has to use splitCommandLine instead of StringTokenizer. From now, these command "a \"b c\" d" is divided into 3 tokens { "a", "b c", "d" } and then they are connected to the single line "a \"b c\" d", which is parsed by launched program into 3 args { "a", "b c", "d" } as most users expect and as it was before. As for backward compatibility, using StringTokenizer is relevant only for command line arguments without spaces and doublequotes. For these cases splitCommandLine works equally. JUnit TESTCASE : import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import junit.framework.TestCase; class DumpCommandLine { public static void main(String... args) { for (String arg : args) { System.out.println(arg); } } } public class RuntimeExecTest extends TestCase { private static final String[] PROGRAM = { "cmd", "/c", "%JDK6_HOME%\\bin\\java", DumpCommandLine.class.getName() }; static void check(String... args) throws IOException { String[] cmdarray = new String[PROGRAM.length + args.length]; System.arraycopy(PROGRAM, 0, cmdarray, 0, PROGRAM.length); System.arraycopy(args, 0, cmdarray, PROGRAM.length, args.length); Process process = Runtime.getRuntime().exec(cmdarray); List<String> actual = new ArrayList<String>(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); try { String line; while ((line = reader.readLine()) != null) actual.add(line); } finally { reader.close(); } assertEquals(Arrays.asList(args), actual); } public void testSimple() throws Exception { check("a", "bc", "def"); } public void testNothing() throws Exception { check(); } public void testEmptySole() throws Exception { check(""); } public void testEmptyLast() throws Exception { check("ab", ""); } public void testEmptyFirst() throws Exception { check("", "cd"); } public void testQuoteFirstOnly() throws Exception { check("\" x"); } public void testSlashAndQuote() throws Exception { check("a\\\"b", "c", "d"); } public void testSlashSlash() throws Exception { check("a\\\\b c", "d", "e"); } public void testThreeSlash() throws Exception { check("a\\\\\\b", "de fg", "h"); } public void testQuoteThenQuote() throws Exception { check("ab\"c", "d\"ef"); } public void testQuoteThenQuoteWithSpaces() throws Exception { check("a b \" c", "d \" e f"); } public static void main(String[] args) { junit.textui.TestRunner.run(RuntimeExecTest.class); } } Here are the latest versions of the submitters diff and test: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import junit.framework.TestCase; class DumpCommandLine { public static void main(String... args) { for (String arg : args) { System.out.println(arg); } } } public class RuntimeExecTest extends TestCase { private static final String[] PROGRAM = { "cmd", "/c", "%JDK6_HOME%\\bin\\java", DumpCommandLine.class.getName() }; static void check(String... args) throws IOException { String[] cmdarray = new String[PROGRAM.length + args.length]; System.arraycopy(PROGRAM, 0, cmdarray, 0, PROGRAM.length); System.arraycopy(args, 0, cmdarray, PROGRAM.length, args.length); Process process = Runtime.getRuntime().exec(cmdarray); List<String> actual = new ArrayList<String>(); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); try { String line; while ((line = reader.readLine()) != null) actual.add(line); } finally { reader.close(); } assertEquals(Arrays.asList(args), actual); } public void testSimple() throws Exception { check("a", "bc", "def"); } public void testNothing() throws Exception { check(); } public void testEmptySole() throws Exception { check(""); } public void testEmptyLast() throws Exception { check("ab", ""); } public void testEmptyFirst() throws Exception { check("", "cd"); } public void testQuoteFirstOnly() throws Exception { check("\" x"); } public void testSlashAndQuote() throws Exception { check("a\\\"b", "c", "d"); } public void testSlashSlash() throws Exception { check("a\\\\b c", "d", "e"); } public void testThreeSlash() throws Exception { check("a\\\\\\b", "de fg", "h"); } public void testQuoteThenQuote() throws Exception { check("ab\"c", "d\"ef"); } public void testQuoteThenQuoteWithSpaces() throws Exception { check("a b \" c", "d \" e f"); } public void testQuotedWithTrailingSlash1() throws Exception { check("c:\\Program Files\\"); } public void testQuotedWithTrailingSlash() throws Exception { check("c:\\Program Files\\", "padding"); } public static void main(String[] args) { junit.textui.TestRunner.run(RuntimeExecTest.class); } } diff -u -r old/java/lang/ProcessImpl.java new/java/lang/ProcessImpl.java --- old/java/lang/ProcessImpl.java 2006-09-04 14:58:47.240001400 +0300 +++ new/java/lang/ProcessImpl.java 2006-09-06 18:05:59.132961600 +0300 @@ -47,32 +47,7 @@ // Win32 CreateProcess requires cmd[0] to be normalized cmd[0] = new File(cmd[0]).getPath(); - StringBuilder cmdbuf = new StringBuilder(80); - for (int i = 0; i < cmd.length; i++) { - if (i > 0) { - cmdbuf.append(' '); - } - String s = cmd[i]; - if (s.indexOf(' ') >= 0 || s.indexOf('\t') >= 0) { - if (s.charAt(0) != '"') { - cmdbuf.append('"'); - cmdbuf.append(s); - if (s.endsWith("\\")) { - cmdbuf.append("\\"); - } - cmdbuf.append('"'); - } else if (s.endsWith("\"")) { - /* The argument has already been quoted. */ - cmdbuf.append(s); - } else { - /* Unmatched quote for the argument. */ - throw new IllegalArgumentException(); - } - } else { - cmdbuf.append(s); - } - } - String cmdstr = cmdbuf.toString(); + String cmdstr = Runtime.mergeCommandLine(cmd); stdin_fd = new FileDescriptor(); stdout_fd = new FileDescriptor(); diff -u -r old/java/lang/Runtime.java new/java/lang/Runtime.java --- old/java/lang/Runtime.java 2006-09-04 14:57:37.818685800 +0300 +++ new/java/lang/Runtime.java 2006-09-15 10:23:40.994669200 +0300 @@ -8,7 +8,9 @@ package java.lang; import java.io.*; -import java.util.StringTokenizer; + +import java.util.List; +import java.util.ArrayList; /** * Every Java application has a single instance of class @@ -25,6 +27,11 @@ */ public class Runtime { + private static final char SPACE = ' '; + private static final char TAB = '\t'; + private static final char DOUBLEQUOTE = '\"'; + private static final char BACKSLASH = '\\'; + private static Runtime currentRuntime = new Runtime(); /** @@ -369,6 +376,118 @@ return exec(command, envp, null); } + /** + * Splits single line into separate command line arguments with following rules: + * <ul> + * <li>Arguments are delimited by white space, which is either a space or a tab.</li> + * <li>The caret character (\n) is not recognized as an escape character or delimiter. The character is handled completely + * by the command-line parser in the operating system before being passed to the args array in the program.</li> + * <li>A string surrounded by double quotation marks ("string") is interpreted as a single argument, regardless of white + * space contained within. A quoted string can be embedded in an argument.</li> + * <li>A double quotation mark preceded by a backslash (\") is interpreted as a literal double quotation mark character (").</li> + * <li>Backslashes are interpreted literally, unless they immediately precede a double quotation mark.</li> + * <li>If an even number of backslashes is followed by a double quotation mark, one backslash is placed in the args array for every + * pair of backslashes, and the double quotation mark is interpreted as a string delimiter.</li> + * <li>If an odd number of backslashes is followed by a double quotation mark, one backslash is placed in the args array + * for every pair of backslashes, and the double quotation mark is escaped by the remaining backslash, + * causing a literal double quotation mark (") to be placed in args. </li> + * </ul> + * </p> + * <p>Example + * <table border="1"> + * <tr><th>line</th><th>args[0]</th><th>args[1]</th><th>args[2]</th></tr> + * <tr><td>"abc" d e</td><td>abc</td><td>d</td><td>e</td></tr> + * <tr><td>a\\\b d"e f"g h</td><td>a\\\b</td><td>de fg</td><td>h</td></tr> + * <tr><td>a\\\"b c d</td><td>a\"b</td><td>c</td><td>d</td></tr> + * <tr><td>a\\\\"b c" d e</td><td>a\\b c</td><td>d</td><td>e</td></tr> + * </table> + * + * @see #mergeCommandLine(String[]) + * @param line - line to be splitted + * @return separate arguments + */ + public static String[] splitCommandLine(String line) { + List<String> list = new ArrayList<String>(); + StringBuilder buffer = new StringBuilder(); + boolean inquote = false; + boolean flushed = true; + int backslashcount = 0; + for (int i = 0, n = line.length(); i < n; ++i) { + char ch = line.charAt(i); + if (ch == BACKSLASH) { + ++backslashcount; + } else { + int m = (ch == DOUBLEQUOTE)? backslashcount / 2: backslashcount; + for (int j = 0; j < m; ++j) + buffer.append(BACKSLASH); + + if ((ch == SPACE) || (ch == TAB)) { + if (inquote) { + buffer.append(ch); + flushed = false; + } else { + if (!flushed) { + list.add(buffer.toString()); + buffer.setLength(0); + flushed = true; + } + } + } else { + if ((ch == DOUBLEQUOTE) && (backslashcount % 2 == 0)) + inquote = !inquote; + else + buffer.append(ch); + flushed = false; + } + backslashcount = 0; + } + } + for (int j = 0, m = backslashcount; j < m; ++j) + buffer.append(BACKSLASH); + if (!flushed) + list.add(buffer.toString()); + return list.toArray(new String[list.size()]); + } + + /** + * Merges separate command line arguments into single line. + * This is opposite to <code>{@link #splitCommandLine(String)}</code>. + * + * @see #splitCommandLine(String) + * @param args - arguments to be merged + * @return single line + */ + public static String mergeCommandLine(String... args) { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = args.length; i < n; ++i) { + if (i > 0) + buffer.append(SPACE); + String arg = args[i]; + boolean quote = ((arg.length() == 0) || (arg.indexOf(SPACE) != -1) || (arg.indexOf(TAB) != -1)); + if (quote) + buffer.append(DOUBLEQUOTE); + int backslashcount = 0; + for (int j = 0, m = arg.length(); j < m; ++j) { + char ch = arg.charAt(j); + if (ch == BACKSLASH) { + ++backslashcount; + } else { + int l = (ch == DOUBLEQUOTE)? backslashcount * 2 + 1: backslashcount; + for (int k = 0; k < l; ++k) + buffer.append(BACKSLASH); + buffer.append(ch); + backslashcount = 0; + } + } + int l = quote? backslashcount * 2: backslashcount; + for (int k = 0; k < l; ++k) + buffer.append(BACKSLASH); + if (quote) + buffer.append(DOUBLEQUOTE); + } + return buffer.toString(); + } + /** * Executes the specified string command in a separate process with the * specified environment and working directory. @@ -381,11 +500,7 @@ * <code>command</code>. * * <p>More precisely, the <code>command</code> string is broken - * into tokens using a {@link StringTokenizer} created by the call - * <code>new {@link StringTokenizer}(command)</code> with no - * further modification of the character categories. The tokens - * produced by the tokenizer are then placed in the new string - * array <code>cmdarray</code>, in the same order. + * into tokens using a {@link #splitCommandLine(String)}. * * @param command a specified system command. * @@ -424,11 +539,7 @@ if (command.length() == 0) 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); + return exec(splitCommandLine(command), envp, dir); } /**
|