Jak uruchomić skrypt powłoki Unix z kodu Java?


155

Uruchomienie polecenia Unix z poziomu Javy jest dość proste.

Runtime.getRuntime().exec(myCommand);

Ale czy można uruchomić skrypt powłoki Unix z kodu Java? Jeśli tak, czy dobrą praktyką byłoby uruchomienie skryptu powłoki z poziomu kodu Java?


3
Rzeczy stają się interesujące, jeśli ten skrypt powłoki jest interaktywny.
ernesto

co to jest zmienna myCommand to String? jeśli tak, to nie zadziała, metoda exec wymaga String [] i argumentu, patrz poniżej moja answar, działa idealnie
Girdhar Singh Rathore

Odpowiedzi:


174

Powinieneś naprawdę spojrzeć na Process Builder . Jest naprawdę zbudowany do tego typu rzeczy.

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
Czy jest dobrą praktyką wywoływanie skryptów z JAVA? Jakieś problemy z wydajnością?
kautuksahni

1
Zwróć uwagę, że może być konieczne określenie programu / bin / bash lub sh w celu wykonania skryptu w zależności od konfiguracji Javy (patrz stackoverflow.com/questions/25647806/ ... )
Ben Holland

@Milhous Wiem, że to dość późno i mogło się to zmienić, ale zgodnie z aktualną dokumentacją procesu Java nie jest to zalecana metoda dla skryptów powłoki: docs.oracle.com/javase/8/docs/api/java/lang/Process.html " Metody tworzące procesy mogą nie działać dobrze w przypadku specjalnych procesów na niektórych natywnych platformach, takich jak natywne procesy okienkowe, procesy demonów, procesy Win16 / DOS w systemie Microsoft Windows lub skrypty powłoki ”.
Harman

24

Możesz użyć biblioteki exec Apache Commons .

Przykład:

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

W celu uzyskania dalszych informacji, podano również przykład w Apache Doc .


Czy mogę to uruchomić w systemie Windows?
bekur

@KisHanSarsecHaGajjar możemy również przechwycić dane wyjściowe z shellscript i wyświetlić je w java ui. Chcę wiedzieć, czy można to zrobić
Kranthi Sama

@KranthiSama można ustawić OutputStreamna DefaultExecuterużyciu DefaultExecuter.setStreamHandlermetody przechwytywania w wyjściu OutputStream. Zapoznaj się z tym wątkiem, aby uzyskać więcej informacji: Jak mogę przechwycić dane wyjściowe polecenia ...
To nie jest błąd

1
Link do dodania zależności biblioteki Apache Commons Exec do twojego projektu - commons.apache.org/proper/commons-exec/dependency-info.html
aunlead

2
najlepszym rozwiązaniem.
Dev

23

Powiedziałbym, że nie jest to duch Javy uruchamianie skryptu powłoki z Javy. Java ma być wieloplatformowa, a uruchomienie skryptu powłoki ograniczyłoby jej użycie tylko do systemu UNIX.

Mając to na uwadze, zdecydowanie możliwe jest uruchomienie skryptu powłoki z poziomu Javy. Użyłbyś dokładnie tej samej składni, którą podałeś (sam tego nie próbowałem, ale spróbuj wykonać skrypt powłoki bezpośrednio, a jeśli to nie zadziała, uruchom samą powłokę, przekazując skrypt jako parametr wiersza poleceń) .


43
Tak, ale pod wieloma względami ta mantra „duch Javy” lub „pisz raz, biegnij wszędzie” jest i tak mitem.
BobbyShaftoe

15
Co w tym jest mityczne?
Chris Ballance,

15
co o nim jest mityczny, które zwykle kończą się pisząc liczne switchesi ifsprawozdań, aby ominąć wszystkie niuanse, które nie działają dokładnie tak samo na różnych platformach mimo najlepszych starań ludzi, którzy wpadli na bibliotekach rdzenia java.
Brian Sweeney

2
Zaskakujące! Zgadzam się ze wszystkimi powyższymi komentarzami i odpowiedzią!
AnBisw

11
@BobbyShaftoe Piszę Javę od 16 lat i zawsze tworzyłem pod Windows, wszystkie moje aplikacje zawsze były wdrażane na skrzynkach unix o smaku solaris / ibm lub Oracle, więc nie mam pojęcia, o czym mówisz
Kalpesh Soni

21

Myślę, że odpowiedziałeś na swoje pytanie za pomocą

Runtime.getRuntime().exec(myShellScript);

Co do tego, czy jest to dobra praktyka ... co próbujesz zrobić ze skryptem powłoki, którego nie możesz zrobić w Javie?


Miałem do czynienia z podobną sytuacją, w której muszę zsynchronizować kilka plików na różnych serwerach, gdy wystąpi określony stan w moim kodzie java. Czy jest inny lepszy sposób?
v kumar

1
@Chris Ballance ... Wiem, że ten komentarz jest już prawie po 10 latach :) ale odpowiadając na twoje pytanie, co jeśli mój program będzie musiał współdziałać z pół tuzinem kanałów downstream i upstream i uzależniony od przyjętego przez nie sposobu komunikacji. Zwłaszcza gdy pracujesz nad projektem, który współdziała z tak wieloma dziwnymi kanałami :)
Stunner

Przekazanie funkcjonalności do skryptu powłoki byłoby wysiłkiem na ostatnią chwilę, gdyby nie było innego sposobu wykonania pracy w Javie. Koordynacja stanu i zależności będzie z konieczności skomplikowana, jeśli wypychasz pracę do skryptu powłoki. Czasami skrypt powłoki jest jedynym sposobem lub ramy czasowe sprawiają, że jest to jedyny rozsądny sposób na wykonanie jakiejś pracy, więc jest to sposób na zrobienie tego.
Chris Ballance

11

Tak, jest to możliwe. To mi się udało.

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

7

Oto mój przykład. Mam nadzieję, że to ma sens.

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

Tak, jest to możliwe i odpowiedziałeś na to! Co do dobrych praktyk, myślę, że lepiej jest uruchamiać polecenia z plików, a nie bezpośrednio z kodu. Więc trzeba zrobić Java wykonać listę poleceń (lub jednego polecenia) w istniejącej .bat, .sh, .ksh... plików. Oto przykład wykonania listy poleceń w pliku MyFile.sh:

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

4

Aby uniknąć konieczności kodowania bezwzględnej ścieżki na stałe, możesz użyć następującej metody, która znajdzie i uruchomi skrypt, jeśli znajduje się on w katalogu głównym.

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

3

Jeśli chodzi o mnie, wszystko musi być proste. Do uruchomienia skryptu wystarczy wykonać

new ProcessBuilder("pathToYourShellScript").start();

3

Wykonawca ZT Sposób jest alternatywą dla Apache Commons Exec. Posiada funkcję uruchamiania poleceń, przechwytywania ich danych wyjściowych, ustawiania limitów czasu itp.

Nie korzystałem jeszcze z niego, ale wygląda na dość dobrze udokumentowany.

Przykład z dokumentacji: Wykonanie polecenia, przepompowanie stderr do loggera, zwrócenie danych wyjściowych jako łańcuch znaków UTF8.

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

Jego dokumentacja wymienia następujące zalety w porównaniu z Commons Exec:

  • Poprawiona obsługa strumieni
    • Czytanie / pisanie do strumieni
    • Przekierowywanie stderr na stdout
  • Poprawiona obsługa limitów czasu
  • Ulepszone sprawdzanie kodów wyjścia
  • Ulepszony interfejs API
    • Jedna wkładka do dość złożonych przypadków użycia
    • Jeden liner, aby uzyskać dane wyjściowe procesu w łańcuchu
    • Dostęp do obiektu Process jest dostępny
    • Wsparcie dla procesów asynchronicznych ( przyszłość )
  • Ulepszone logowanie za pomocą SLF4J API
  • Obsługa wielu procesów

2

Oto przykład, jak uruchomić skrypt bash Unix lub Windows bat / cmd z poziomu Java. Argumenty mogą być przekazywane do skryptu i dane wyjściowe otrzymane ze skryptu. Metoda przyjmuje dowolną liczbę argumentów.

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

W przypadku pracy w systemie Unix / Linux ścieżka musi być podobna do systemu Unix (z „/” jako separatorem), w przypadku uruchamiania w systemie Windows - należy użyć „\”. Hier to przykład skryptu bash (test.sh), który otrzymuje dowolną liczbę argumentów i podwaja każdy argument:

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

Dzwoniąc

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

w systemie Unix / Linux dane wyjściowe to:

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 jest prostym skryptem cmd test.cmd, który zlicza argumenty wejściowe:

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

Podczas wywoływania skryptu w systemie Windows

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

Wynik jest

Received from script: 3 arguments received

1

Jest to możliwe, po prostu wykonaj to jak każdy inny program. Tylko upewnij się, że Twój skrypt ma poprawny #! (she-bang) jako pierwszą linię skryptu i upewnij się, że plik ma uprawnienia do wykonywania.

Na przykład, jeśli jest to skrypt bash, umieść #! / Bin / bash na górze skryptu, również chmod + x.

Również jeśli chodzi o dobrą praktykę, nie, zwłaszcza w przypadku Javy, ale jeśli oszczędza ci to dużo czasu przy przenoszeniu dużego skryptu i nie dostajesz za to dodatkowej zapłaty;) oszczędzaj czas, wykonaj script i umieść portowanie na Javę na długoterminowej liście rzeczy do zrobienia.


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

1

To jest późna odpowiedź. Pomyślałem jednak o podjęciu trudu, jaki musiałem znieść, aby skrypt powłoki był uruchamiany z aplikacji Spring-Boot dla przyszłych programistów.

  1. Pracowałem w Spring-Boot i nie mogłem znaleźć pliku do wykonania w mojej aplikacji Java i wyrzucał FileNotFoundFoundException. Musiałem zachować plik w resourceskatalogu i ustawić plik do skanowania pom.xmlpodczas uruchamiania aplikacji, jak poniżej.

    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
                <include>**/*.sh</include>
            </includes>
        </resource>
    </resources>
  2. Potem miałem problemy z uruchomieniem pliku i wracał error code = 13, Permission Denied. Następnie musiałem uczynić plik wykonywalnym, uruchamiając to polecenie -chmod u+x myShellScript.sh

Wreszcie mogłem uruchomić plik za pomocą następującego fragmentu kodu.

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

Mam nadzieję, że to rozwiązuje czyjś problem.


0

Tak samo, jak w Solarisie 5.10, działa w ten sposób ./batchstart.sh, jest też sztuczka, której nie wiem, jeśli Twój system operacyjny ją akceptuje \\. batchstart.sh. To podwójne ukośnik może pomóc.


0

Myślę z

System.getProperty("os.name"); 

Sprawdzenie systemu operacyjnego może zarządzać skryptami powłoki / bash, jeśli takie są obsługiwane. jeśli istnieje potrzeba, aby kod był przenośny.


0

do użytku w systemie Linux

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

i do użytku;

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

LUB

ShellAssistance.runShell("", "pg_dump", new String[]{"-U", "aliAdmin", "-f", "/home/Backup.sql", "StoresAssistanceDB"}, new Hashmap<>());
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.