178

It is quite simple to run a Unix command from Java.

Runtime.getRuntime().exec(myCommand);

But is it possible to run a Unix shell script from Java code? If yes, would it be a good practice to run a shell script from within Java code?

Lii
  • 11,553
  • 8
  • 64
  • 88

18 Answers18

186

You should really look at Process Builder. It is really built for this kind of thing.

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();
djeikyb
  • 4,470
  • 3
  • 35
  • 42
Milhous
  • 14,473
  • 16
  • 63
  • 82
  • 3
    Is it good practice to call scripts from JAVA? Any performance issues? – kautuksahni Sep 18 '17 at 05:59
  • 1
    Note that that you may need to specify the program /bin/bash or sh to execute the script within depending on Java's configuration (see https://stackoverflow.com/questions/25647806/running-shell-script-from-external-directory-no-such-file-or-directory) – Ben Holland Feb 27 '18 at 03:00
  • @Milhous I know this is quite late and things might have changed but per current Java Process documentation this is not recommended method for shell scripts: https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html "The methods that create processes may not work well for special processes on certain native platforms, such as native windowing processes, daemon processes, Win16/DOS processes on Microsoft Windows, or shell scripts." – Harman Nov 10 '19 at 10:27
28

You can use Apache Commons exec library also.

Example :

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");
    }
}

For further reference, An example is given on Apache Doc also.

Not a bug
  • 4,286
  • 2
  • 40
  • 80
  • Can I run this in Windows? – AKB Apr 04 '14 at 18:01
  • @KisHanSarsecHaGajjar can we also capture the output from shellscript and display it in the java ui. I want to know is it possible to do so – Kranthi Sama Jul 09 '15 at 09:09
  • @KranthiSama You can set `OutputStream` for `DefaultExecuter` using `DefaultExecuter.setStreamHandler` method to capture output in `OutputStream`. Please refer this thread for more info : [How can I capture the output of a command...](http://stackoverflow.com/a/6295923/1686291) – Not a bug Oct 13 '15 at 07:58
  • 1
    Link to add Apache Commons Exec library dependency to your project - https://commons.apache.org/proper/commons-exec/dependency-info.html – aunlead Mar 18 '16 at 17:35
  • 2
    the best solution. – Dev Mar 26 '18 at 11:25
23

I think you have answered your own question with

Runtime.getRuntime().exec(myShellScript);

As to whether it is good practice... what are you trying to do with a shell script that you cannot do with Java?

Hosam Aly
  • 41,555
  • 36
  • 141
  • 182
Chris Ballance
  • 33,810
  • 26
  • 104
  • 151
  • I've faced a similar situation where I need to rsync a few files on different servers when a certain condition occurs in my java code. Is there any other better way ? – v kumar Feb 16 '16 at 05:26
  • 1
    @Chris Ballance... I Know this comment is almost after 10 years:) but to answer your question , what if my program has to interact with half a dozen downstream and upstream channels and dependent on their accepted mode of communication. Especially when you are working on a project which interacts with so many odd channels :) – Stunner Jun 25 '18 at 12:45
  • Pushing the functionality out to a shell script would be a last-ditch effort if there's no other way to do the work in Java. It will be necessarily complicated to coordinate state and dependencies if you're pushing the work out to a shell script. Occasionally, a shell script is the only way or timeframe makes it the only reasonable way to accomplish a bit of work, so this is a way to do that. – Chris Ballance Jun 27 '18 at 14:04
  • Another use case is debugging in production. What if there is some intermittent issue that cannot be reproduced, and you need to capture network stats whenever the issue occurs, for example. It has to be done programmatically from the main java app. – Zoomzoom Feb 02 '23 at 14:13
22

I would say that it is not in the spirit of Java to run a shell script from Java. Java is meant to be cross platform, and running a shell script would limit its use to just UNIX.

With that said, it's definitely possible to run a shell script from within Java. You'd use exactly the same syntax you listed (I haven't tried it myself, but try executing the shell script directly, and if that doesn't work, execute the shell itself, passing the script in as a command line parameter).

Jack Leow
  • 21,945
  • 4
  • 50
  • 55
  • 46
    Yes, but in many ways that "spirit of Java" or "write once run everywhere" mantra is a myth anyway. – BobbyShaftoe Feb 08 '09 at 05:51
  • 17
    what's mythical about it is, that you usually end up writing numerous `switches` and `if` statements to get around all of the nuances that don't work exactly the same on different platforms despite the best efforts of the people who came up with the java core libraries. – Brian Sweeney Nov 17 '11 at 19:32
  • 3
    Surprisingly enough! I agree with all the comments above and the answer! – Anjan Biswas Nov 21 '12 at 23:00
  • 1
    Spirit of Java is to invent xml-based shell replacement instead of running shell script. – kolen Aug 04 '15 at 10:14
  • 12
    @BobbyShaftoe I have been writing java for 16 years, and always developed on windows, all my apps always deployed on solaris/ibm or oracle flavored unix boxes, so I have no idea what you are talking about – Kalpesh Soni Aug 31 '16 at 17:11
  • 4
    @KalpeshSoni that is the reason; you always have a fixed target deploy platform; case is different when you have to deploy to Windows, Linux, Mac and Solaris simultaneously. – WesternGun Jan 15 '18 at 08:54
15

Yes it is possible to do so. This worked out for me.

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());
        }
    }
Desta Haileselassie Hagos
  • 23,140
  • 7
  • 48
  • 53
9

Here is my example. Hope it make sense.

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;
}
Lionel Yan
  • 101
  • 1
  • 4
5

Yes, it is possible and you have answered it! About good practises, I think it is better to launch commands from files and not directly from your code. So you have to make Java execute the list of commands (or one command) in an existing .bat, .sh , .ksh ... files. Here is an example of executing a list of commands in a file MyFile.sh:

    String[] cmd = { "sh", "MyFile.sh", "\pathOfTheFile"};
    Runtime.getRuntime().exec(cmd);
Dmitriy Popov
  • 2,150
  • 3
  • 25
  • 34
YANKEE
  • 59
  • 1
  • 2
5

To avoid having to hardcode an absolute path, you can use the following method that will find and execute your script if it is in your root directory.

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!");
    }
}
anataliocs
  • 10,427
  • 6
  • 56
  • 72
3

As for me all things must be simple. For running script just need to execute

new ProcessBuilder("pathToYourShellScript").start();
Not a bug
  • 4,286
  • 2
  • 40
  • 80
Vladimir Bosyi
  • 433
  • 1
  • 5
  • 7
3

The ZT Process Executor library is an alternative to Apache Commons Exec. It has functionality to run commands, capturing their output, setting timeouts, etc.

I have not used it yet, but it looks reasonably well-documented.

An example from the documentation: Executing a command, pumping the stderr to a logger, returning the output as UTF8 string.

 String output = new ProcessExecutor().command("java", "-version")
    .redirectError(Slf4jStream.of(getClass()).asInfo())
    .readOutput(true).execute()
    .outputUTF8();

Its documentation lists the following advantages over Commons Exec:

  • Improved handling of streams
    • Reading/writing to streams
    • Redirecting stderr to stdout
  • Improved handling of timeouts
  • Improved checking of exit codes
  • Improved API
    • One liners for quite complex use cases
    • One liners to get process output into a String
    • Access to the Process object available
    • Support for async processes ( Future )
  • Improved logging with SLF4J API
  • Support for multiple processes
jatin_ghataliya
  • 144
  • 1
  • 1
  • 16
Lii
  • 11,553
  • 8
  • 64
  • 88
3

This is a late answer. However, I thought of putting the struggle I had to bear to get a shell script to be executed from a Spring-Boot application for future developers.

  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

Finally, I could execute the file using the following code snippet.

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

Hope that solves someone's problem.

Reaz Murshed
  • 23,691
  • 13
  • 78
  • 98
3

Here is an example how to run an Unix bash or Windows bat/cmd script from Java. Arguments can be passed on the script and output received from the script. The method accepts arbitrary number of arguments.

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);
    }
}

When running on Unix/Linux, the path must be Unix-like (with '/' as separator), when running on Windows - use '\'. Hier is an example of a bash script (test.sh) that receives arbitrary number of arguments and doubles every argument:

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

When calling

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

on Unix/Linux, the output is:

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

Hier is a simple cmd Windows script test.cmd that counts number of input arguments:

@echo off
set a=0
for %%x in (%*) do Set /A a+=1 
echo %a% arguments received

When calling the script on Windows

  runScript("path_to_script\\test.cmd", "1", "2", "3")

The output is

Received from script: 3 arguments received
Andrushenko Alexander
  • 1,839
  • 19
  • 14
1

I think with

System.getProperty("os.name"); 

Checking the operating system on can manage the shell/bash scrips if such are supported. if there is need to make the code portable.

DayaMoon
  • 357
  • 2
  • 7
1
  String scriptName = PATH+"/myScript.sh";
  String commands[] = new String[]{scriptName,"myArg1", "myArg2"};

  Runtime rt = Runtime.getRuntime();
  Process process = null;
  try{
      process = rt.exec(commands);
      process.waitFor();
  }catch(Exception e){
      e.printStackTrace();
  }  
Girdhar Singh Rathore
  • 5,030
  • 7
  • 49
  • 67
1

It is possible, just exec it as any other program. Just make sure your script has the proper #! (she-bang) line as the first line of the script, and make sure there are execute permissions on the file.

For example, if it is a bash script put #!/bin/bash at the top of the script, also chmod +x .

Also as for if it's good practice, no it's not, especially for Java, but if it saves you a lot of time porting a large script over, and you're not getting paid extra to do it ;) save your time, exec the script, and put the porting to Java on your long-term todo list.

Kekoa
  • 27,892
  • 14
  • 72
  • 91
0

Just the same thing that Solaris 5.10 it works like this ./batchstart.sh there is a trick I don´t know if your OS accept it use \\. batchstart.sh instead. This double slash may help.

Jason Plank
  • 2,336
  • 5
  • 31
  • 40
Saul
  • 1
0

for linux use

public static void runShell(String directory, String command, String[] args, Map<String, String> environment)
{
    try
    {
        if(directory.trim().equals(""))
            directory = "/";

        String[] cmd = new String[args.length + 1];
        cmd[0] = command;

        int count = 1;

        for(String s : args)
        {
            cmd[count] = s;
            count++;
        }

        ProcessBuilder pb = new ProcessBuilder(cmd);

        Map<String, String> env = pb.environment();

        for(String s : environment.keySet())
            env.put(s, environment.get(s));

        pb.directory(new File(directory));

        Process process = pb.start();

        BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        BufferedWriter outputReader = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
        BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));

        int exitValue = process.waitFor();

        if(exitValue != 0) // has errors
        {
            while(errReader.ready())
            {
                LogClass.log("ErrShell: " + errReader.readLine(), LogClass.LogMode.LogAll);
            }
        }
        else
        {
            while(inputReader.ready())
            {
                LogClass.log("Shell Result : " + inputReader.readLine(), LogClass.LogMode.LogAll);
            }
        }
    }
    catch(Exception e)
    {
        LogClass.log("Err: RunShell, " + e.toString(), LogClass.LogMode.LogAll);
    }
}

public static void runShell(String path, String command, String[] args)
{
    try
    {
        String[] cmd = new String[args.length + 1];

        if(!path.trim().isEmpty())
            cmd[0] = path + "/" + command;
        else
            cmd[0] = command;

        int count = 1;

        for(String s : args)
        {
            cmd[count] = s;
            count++;
        }

        Process process = Runtime.getRuntime().exec(cmd);

        BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        BufferedWriter outputReader = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
        BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));

        int exitValue = process.waitFor();

        if(exitValue != 0) // has errors
        {
            while(errReader.ready())
            {
                LogClass.log("ErrShell: " + errReader.readLine(), LogClass.LogMode.LogAll);
            }
        }
        else
        {
            while(inputReader.ready())
            {
                LogClass.log("Shell Result: " + inputReader.readLine(), LogClass.LogMode.LogAll);
            }
        }
    }
    catch(Exception e)
    {
        LogClass.log("Err: RunShell, " + e.toString(), LogClass.LogMode.LogAll);
    }
}

and for usage;

ShellAssistance.runShell("", "pg_dump", new String[]{"-U", "aliAdmin", "-f", "/home/Backup.sql", "StoresAssistanceDB"});

OR

ShellAssistance.runShell("", "pg_dump", new String[]{"-U", "aliAdmin", "-f", "/home/Backup.sql", "StoresAssistanceDB"}, new Hashmap<>());
Ali Bagheri
  • 3,068
  • 27
  • 28
0

You can also use my small library called jaxec:

import com.yegor256.Jaxec;
String stdout = new Jaxec("ls", "-al", "/tmp")
    .withHome("/home/me") // run it in this directory
    .withRedirect(false) // don't redirect STDERR to STDOUT
    .exec();

It will not only execute the shell command, but also will send its output to Slf4j log, wait for its completion, redirect stderr to stdout, and make sure an exception is raised if the exit code is not equal to zero.

yegor256
  • 102,010
  • 123
  • 446
  • 597