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?

Lii
źródło
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();
Milhous
źródło
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 .

To nie jest błąd
źródło
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ń) .

Jack Leow
źródło
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?

Chris Ballance
źródło
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());
        }
    }
Desta Haileselassie Hagos
źródło
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;
}
Lionel Yan
źródło
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);
JANKES
źródło
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!");
    }
}
anataliocs
źródło
3

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

new ProcessBuilder("pathToYourShellScript").start();
Vladimir Bosyi
źródło
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
Lii
źródło
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
Andrushenko Alexander
źródło
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.

Kekoa
źródło
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
źródło
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.

Reaz Murshed
źródło
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.

Saul
źródło
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.

DayaMoon
źródło
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<>());
Ali Bagheri
źródło