如何从Java代码中运行Unix shell脚本?

179

从Java运行Unix命令非常简单。

Runtime.getRuntime().exec(myCommand);

但是能否从Java代码中运行Unix shell脚本呢?如果可以,从Java代码中运行shell脚本是否是一种好的实践方式呢?


4
如果那个shell脚本是交互式的,事情会变得有趣。 - ernesto
我的 myCommand 变量是 String 类型的吗?如果是,那么它将无法工作。exec 方法需要 String[] 和参数,请参见下面的答案,它可以完美地工作。 - Girdhar Singh Rathore
18个回答

186

你真的应该看一下Process Builder。它确实是为这种情况而构建的。

ProcessBuilder pb = new ProcessBuilder("myshellScript.sh", "myArg1", "myArg2");
 Map<String, String> env = pb.environment();
 env.put("VAR1", "myValue");
 env.remove("OTHERVAR");
 env.put("VAR2", env.get("VAR1") + "suffix");
 pb.directory(new File("myDir"));
 Process p = pb.start();

3
从JAVA调用脚本是一个好的实践吗?是否存在性能问题? - kautuksahni
1
请注意,根据Java的配置,您可能需要指定程序/bin/bash或sh来执行脚本(请参见https://dev59.com/BYPba4cB1Zd3GeqPnBG4)。 - Ben Holland
@Milhous 我知道这可能有点晚了,事情可能已经发生了变化,但根据当前的Java进程文档,这不是shell脚本的推荐方法:https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html“创建进程的方法可能对某些本地平台上的特殊进程(如本地窗口进程、守护进程、Microsoft Windows上的Win16/DOS进程或shell脚本)效果不佳。” - Harman

28
你可以使用Apache Commons exec 库

示例:

package testShellScript;

import java.io.IOException;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;

public class TestScript {
    int iExitValue;
    String sCommandString;

    public void runScript(String command){
        sCommandString = command;
        CommandLine oCmdLine = CommandLine.parse(sCommandString);
        DefaultExecutor oDefaultExecutor = new DefaultExecutor();
        oDefaultExecutor.setExitValue(0);
        try {
            iExitValue = oDefaultExecutor.execute(oCmdLine);
        } catch (ExecuteException e) {
            System.err.println("Execution failed.");
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("permission denied.");
            e.printStackTrace();
        }
    }

    public static void main(String args[]){
        TestScript testScript = new TestScript();
        testScript.runScript("sh /root/Desktop/testScript.sh");
    }
}

更多参考资料可以在Apache Doc中找到示例。


我可以在Windows上运行这个吗? - AKB
@KisHanSarsecHaGajjar,我们能否捕获shell脚本的输出并在Java UI中显示它。我想知道是否有可能这样做。 - Kranthi Sama
@KranthiSama,您可以使用DefaultExecuter.setStreamHandler方法为DefaultExecuter设置OutputStream以捕获OutputStream中的输出。请参考此线程获取更多信息:如何捕获命令的输出... - Not a bug
1
将Apache Commons Exec库添加到您的项目中的链接-https://commons.apache.org/proper/commons-exec/dependency-info.html - aunlead
2
最佳解决方案。 - Dev

23

我认为您已经用以下方式回答了自己的问题:

Runtime.getRuntime().exec(myShellScript);

关于是否需要使用Shell脚本...你用Shell脚本做什么事情,用Java无法实现呢?


我曾经遇到过类似的情况,当我的Java代码满足某个条件时,我需要在不同的服务器上同步几个文件。是否有更好的方法? - v kumar
1
@Chris Ballance...我知道这条评论已经过去将近10年了:) 但是为了回答你的问题,如果我的程序必须与半打下游和上游通道进行交互,并且取决于它们接受的通信模式。特别是当你正在处理与许多奇怪通道交互的项目时 :) - Stunner
如果没有其他方法可以在Java中完成工作,将功能推到shell脚本中将是最后的努力。 如果您将工作推出到shell脚本,则协调状态和依赖关系将变得非常复杂。 有时,shell脚本是唯一的方法,或时间框架使其成为实现某项工作的唯一合理方法,因此这是一种实现该目标的方式。 - Chris Ballance
另一个使用案例是在生产环境中进行调试。例如,如果存在某些无法重现的间歇性问题,并且您需要在问题发生时捕获网络统计信息,则必须从主Java应用程序以编程方式完成此操作。 - Zoomzoom

22

我认为,在Java的精神中,从Java运行shell脚本并不是一个好主意。Java旨在跨平台,而运行shell脚本会将其使用限制在UNIX系统上。

话虽如此,从Java中运行shell脚本是完全可能的。您可以使用与您列出的语法完全相同的方式(我自己尚未尝试过,但可以尝试直接执行shell脚本,如果不起作用,则执行shell本身,并将脚本作为命令行参数传递给它)。


46
是的,但从许多方面来看,“Java精神”或“一次编写多处运行”的口号本身就是一个神话。 - BobbyShaftoe
17
神话般的是,通常你最终需要编写大量的switchesif语句来处理所有微妙之处,在不同平台上这些微妙之处可能并不完全相同,尽管Java核心库的开发人员已经尽力解决这个问题。 - Brian Sweeney
3
很意外!我同意上面所有的评论和答案! - Anjan Biswas
1
Java的精神在于发明基于XML的Shell替代品,而不是运行Shell脚本。 - kolen
12
@BobbyShaftoe 我已经写了16年的Java代码,总是在Windows上开发,但所有的应用程序都部署在Solaris/IBM或Oracle风格的Unix机器上,所以我不知道你在谈论什么。 - Kalpesh Soni
4
@KalpeshSoni,这就是原因;你总是有一个固定的目标部署平台。当你需要同时部署到Windows、Linux、Mac和Solaris时情况就不同了。 - WesternGun

15

是的,这是可能的。这对我很有效。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.omg.CORBA.portable.InputStream;

public static void readBashScript() {
        try {
            Process proc = Runtime.getRuntime().exec("/home/destino/workspace/JavaProject/listing.sh /"); //Whatever you want to execute
            BufferedReader read = new BufferedReader(new InputStreamReader(
                    proc.getInputStream()));
            try {
                proc.waitFor();
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }
            while (read.ready()) {
                System.out.println(read.readLine());
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

9

这是我的例子,希望它有意义。

public static void excuteCommand(String filePath) throws IOException{
    File file = new File(filePath);
    if(!file.isFile()){
        throw new IllegalArgumentException("The file " + filePath + " does not exist");
    }
    if(isLinux()){
        Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", filePath}, null);
    }else if(isWindows()){
        Runtime.getRuntime().exec("cmd /c start " + filePath);
    }
}
public static boolean isLinux(){
    String os = System.getProperty("os.name");  
    return os.toLowerCase().indexOf("linux") >= 0;
}

public static boolean isWindows(){
    String os = System.getProperty("os.name");
    return os.toLowerCase().indexOf("windows") >= 0;
}

5
为了避免硬编码绝对路径,您可以使用以下方法,在根目录中查找并执行您的脚本。
public static void runScript() throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("./nameOfScript.sh");
    //Sets the source and destination for subprocess standard I/O to be the same as those of the current Java process.
    processBuilder.inheritIO();
    Process process = processBuilder.start();

    int exitValue = process.waitFor();
    if (exitValue != 0) {
        // check for errors
        new BufferedInputStream(process.getErrorStream());
        throw new RuntimeException("execution of script failed!");
    }
}

5

是的,这是可能的,而且你已经回答了它!关于良好的实践,我认为最好从文件中启动命令,而不是直接从您的代码中启动。因此,您需要让Java在现有的.bat.sh.ksh……文件中执行一系列命令(或一个命令)。

以下是在文件MyFile.sh中执行一系列命令的示例:

    String[] cmd = { "sh", "MyFile.sh", "\pathOfTheFile"};
    Runtime.getRuntime().exec(cmd);

3

以下是如何在Java中运行Unix Bash或Windows批处理/cmd脚本的示例。可以在脚本上传递参数并接收来自脚本的输出。该方法可接受任意数量的参数。

public static void runScript(String path, String... args) {
    try {
        String[] cmd = new String[args.length + 1];
        cmd[0] = path;
        int count = 0;
        for (String s : args) {
            cmd[++count] = args[count - 1];
        }
        Process process = Runtime.getRuntime().exec(cmd);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        try {
            process.waitFor();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
        while (bufferedReader.ready()) {
            System.out.println("Received from script: " + bufferedReader.readLine());
        }
    } catch (Exception ex) {
        System.out.println(ex.getMessage());
        System.exit(1);
    }
}

在Unix/Linux上运行时,路径必须类似于Unix(使用“/”作为分隔符),在Windows上运行时 - 使用“\”。以下是一个bash脚本(test.sh)的示例,它接收任意数量的参数并将每个参数加倍:

#!/bin/bash
counter=0
while [ $# -gt 0 ]
do
  echo argument $((counter +=1)): $1
  echo doubling argument $((counter)): $(($1+$1))
  shift
done

当调用

runScript("path_to_script/test.sh", "1", "2")

在Unix/Linux上,输出结果如下:

Received from script: argument 1: 1
Received from script: doubling argument 1: 2
Received from script: argument 2: 2
Received from script: doubling argument 2: 4

这里有一个简单的cmd Windows脚本test.cmd,用于计算输入参数的数量:
@echo off
set a=0
for %%x in (%*) do Set /A a+=1 
echo %a% arguments received

在Windows上调用脚本时,
  runScript("path_to_script\\test.cmd", "1", "2", "3")

输出结果为:
Received from script: 3 arguments received

3

这是一个晚回答。然而,我认为将我为了让一个shell脚本从Spring-Boot应用程序中执行所必须承受的困难记录下来,以供未来开发人员参考。

  1. I was working in Spring-Boot and I was not able to find the file to be executed from my Java application and it was throwing FileNotFoundFoundException. I had to keep the file in the resources directory and had to set the file to be scanned in pom.xml while the application was being started like the following.

    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
                <include>**/*.sh</include>
            </includes>
        </resource>
    </resources>
    
  2. After that I was having trouble executing the file and it was returning error code = 13, Permission Denied. Then I had to make the file executable by running this command - chmod u+x myShellScript.sh

最终,我可以使用以下代码片段来执行该文件。

public void runScript() {
    ProcessBuilder pb = new ProcessBuilder("src/main/resources/myFile.sh");
    try {
        Process p;
        p = pb.start();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

希望这能解决某些人的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接