Java执行在JAR中打包的Shell脚本

21

可能重复的问题:
如何在JAR文件中执行一个shell脚本?如何提取?

我将一个shell脚本打包进了我的“engine”包内的JAR文件。

在我的程序中,我使用Process和ProcessBuilder运行一个shell命令。这一切都很好地工作。

如果我在我的电脑上指定shell脚本的路径,那么程序就可以正常工作。但是,如果我将shell脚本打包进我的JAR文件并像这样访问它:

scriptLocation = this.getClass().getResource("/engine/shell-script.sh").toString();

运行程序后,我得到以下错误:

java.io.IOException: Cannot run program "file:/Users/USERNAME/Desktop/Application-Name.jar!/engine/shell-script.sh": error=2, No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:460)
    at java.lang.Runtime.exec(Runtime.java:593)
    at java.lang.Runtime.exec(Runtime.java:431)
    at java.lang.Runtime.exec(Runtime.java:328)
    at engine.Download$1.run(Download.java:39)
    at java.lang.Thread.run(Thread.java:680)
Caused by: java.io.IOException: error=2, No such file or directory
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:53)
    at java.lang.ProcessImpl.start(ProcessImpl.java:91)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:453)
    ... 5 more

我开始认为这似乎做不到,但我非常希望将此shell脚本作为应用程序的一部分提供,这可行吗?

提前感谢大家。

======== 更新 ==========

最终,我解决了这个问题。Shell无法在JAR包中执行脚本,这与Java无关,而是与shell有关。我通过以下方式解决了这个问题:

Java有一个createTempFile()方法(选项是在应用程序终止后删除文件),调用该方法,并将文件写入您想要访问此临时文件的JAR包中。然后,您就可以在本地文件系统上拥有shell脚本,并能够执行该脚本。

3个回答

11

这不是关于Java的问题,而是关于shell的问题。据我所知,shell解释器无法执行存储在zip文件中的脚本。因此,在这里您有几个选择:

  1. 从java程序中作为文本文件读取shell脚本。 创建一个临时文件(在temp目录或任何其他相关位置),将文件的内容复制到该文件中,然后调用带有该临时脚本的shell解释器。 我认为这是最好的方法。

  2. 由于jar文件是一种zip文件,您可以从shell中解压缩它,找到脚本并执行shell。再次说明,这更加麻烦,而且有些错误,但从技术上讲,应该可以正常工作。

  3. 如果您的脚本中没有底层功能,则可以考虑将逻辑重写为Groovy脚本(也许您会发现Groosh项目很有用)。 然后从内存而不是文件中调用Groovy代码。

好了,我的想法已经用完了。如果我是您,我会实现第一个解决方案 :) 祝您好运,希望这可以帮助到您。


谢谢您的回答,我会看看如何从jar文件中提取shell脚本到临时位置,然后再从那里提取它,您知道我该怎么做或者有任何链接吗?非常感谢您的回答! - Cristian
正如我在其他地方提到的那样,您可以执行以下操作:unzip -p JARFILE SCRIPTFILE | bash。这将从JAR文件中提取文件,但实际上不会创建文件。 - Jay
1
我希望我早两天看到了这个答案 :( - Anwarvic
第一种解决方案是最容易实现的,如果有人需要从Java程序运行shell脚本。以下是一个示例实现:https://stackoverflow.com/a/56557639/2563765 - Steven

5
最简单的解决方案是让您的应用程序将shell脚本提取到临时位置并从那里执行。
您可以通过调用File.createTempFile(String, String)在Java中生成一个临时文件。
另一件可能做的事情是将从jar加载的脚本内容作为shell的标准输入传递。
作为简化,如果我有一个包含脚本a.sh的档案a.zip,这将执行它:
$ unzip -p a.zip a.sh | bash

从Java程序中,您可以执行以下操作:

import java.io.*;
class Test{

  public static void main(String[]args) throws Exception {

    StringBuilder sb = new StringBuilder();

    // You're not strictly speaking executing a shell script but
    // rather programatically typing commands into the shell
    // sb.append("#!/bin/bash\n");
    sb.append("echo Hello world!\n");
    sb.append("cd /tmp\n");
    sb.append("echo current directory: `pwd`\n");

    // Start the shell
    ProcessBuilder pb = new ProcessBuilder("/bin/bash");
    Process bash = pb.start();

    // Pass commands to the shell
    PrintStream ps = new PrintStream(bash.getOutputStream());
    ps.println(sb);
    ps.close();

    // Get an InputStream for the stdout of the shell
    BufferedReader br = new BufferedReader(
        new InputStreamReader(bash.getInputStream()));

    // Retrieve and print output
    String line;
    while (null != (line = br.readLine())) {
      System.out.println("> "+line);
    }
    br.close();

    // Make sure the shell has terminated, print out exit value
    System.out.println("Exit code: " + bash.waitFor());

  }

}

我更希望不必将文件复制到用户的系统中,那么我该如何将脚本内容传递给程序使用? - Cristian
在我看来,这是一个非常有趣的解决方案,感谢分享。 - Rodislav Moldovan

0

我通常只是...

$ jar xf myapp.jar run.sh && ./run.sh && rm run.sh

如果需要的话,您可以从Runtime.exec()中调用它。


如果可以从命令行运行该jar文件那就太好了,但它是可运行的jar文件。 - Cristian
通过Runtime.exec()执行它应该能够解决这个问题,尽管这很丑陋。 - rich

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