diff --git a/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java b/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java index d275946a6a..922ba64803 100644 --- a/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java +++ b/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.StringTokenizer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CountDownLatch; @@ -218,7 +219,10 @@ private void execProcess(String[] process, ProcessResult processresult) throws H // execute process try { if (!meta.isArgumentsInFields()) { - p = data.runtime.exec(new String[] {process[0]}); + // Match historical Runtime.exec(String) whitespace tokenization without using the + // deprecated String overload. A single-element String[] would treat the whole command + // line (e.g. "/bin/echo hop-single") as the executable name. + p = data.runtime.exec(tokenizeCommandLine(process[0])); } else { p = data.runtime.exec(process); } @@ -323,6 +327,19 @@ private void execProcess(String[] process, ProcessResult processresult) throws H } } + /** + * Tokenize a command line the same way Runtime.exec(String) historically did (whitespace via + * {@link StringTokenizer}). + */ + static String[] tokenizeCommandLine(String command) { + StringTokenizer st = new StringTokenizer(command); + String[] cmdArray = new String[st.countTokens()]; + for (int i = 0; st.hasMoreTokens(); i++) { + cmdArray[i] = st.nextToken(); + } + return cmdArray; + } + private String getOutputString(BufferedReader b) throws IOException { StringBuilder returnValueBuffer = new StringBuilder(); String line; diff --git a/plugins/transforms/execprocess/src/test/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcessTest.java b/plugins/transforms/execprocess/src/test/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcessTest.java index 7cfd1e7c7a..832673cbcd 100644 --- a/plugins/transforms/execprocess/src/test/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcessTest.java +++ b/plugins/transforms/execprocess/src/test/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcessTest.java @@ -25,6 +25,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import java.util.Arrays; +import java.util.List; import org.apache.hop.core.HopEnvironment; import org.apache.hop.core.IRowSet; import org.apache.hop.core.QueueRowSet; @@ -215,6 +217,14 @@ void processRow_failWhenNotSuccess_setsErrors() throws HopException { assertEquals(1, transform.getErrors()); } + @Test + void tokenizeCommandLine_splitsOnWhitespaceLikeRuntimeExecString() { + assertEquals( + Arrays.asList("/bin/echo", "hop-single"), + Arrays.asList(ExecProcess.tokenizeCommandLine("/bin/echo hop-single"))); + assertEquals(List.of("cmd"), Arrays.asList(ExecProcess.tokenizeCommandLine("cmd"))); + } + @Test void processRow_emptyProcessField_throws() throws HopException { HopEnvironment.init();