Як запустити скрипт оболонки Unix з коду Java?


155

Запустити команду Unix з Java досить просто.

Runtime.getRuntime().exec(myCommand);

Але чи можна запустити скрипт оболонки Unix з коду Java? Якщо так, то чи було б гарною практикою запускати скрипт оболонки з коду Java?


3
Речі стають цікавими, якщо цей сценарій оболонки інтерактивний.
ernesto

що змінна myCommand - це String? якщо так, то це не спрацює, метод exec вимагає String [] та аргумент, дивіться нижче в моєму ансваурі, він працює чудово
Girdhar Singh Rathore

Відповіді:


174

Ви дійсно повинні подивитися на 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
Зауважте, що вам може знадобитися вказати програму / bin / bash або sh для виконання сценарію в залежності від конфігурації Java (див. Stackoverflow.com/questions/25647806/… )
Ben Holland

@Milhous Я знаю, що це досить пізно, і все може змінитися, але в поточній документації Java Process це не рекомендується метод для скриптів оболонки: docs.oracle.com/javase/8/docs/api/java/lang/Process.html " Методи, що створюють процеси, можуть не працювати добре для спеціальних процесів на певних нативних платформах, таких як натурні процеси вікон, демон-процеси, процеси Win16 / DOS в Microsoft Windows або сценарії оболонки. "
Харман

24

Ви також можете використовувати бібліотеку exec Apache Commons .

Приклад:

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?
bekur

@KisHanSarsecHaGajjar чи можемо ми також захопити вихід із оболонки оболонки та відобразити її у java ui Хочу знати, чи можна це зробити
Кранті Сама

@KranthiSama Ви можете встановити OutputStreamдля DefaultExecuterвикористання DefaultExecuter.setStreamHandlerметоди для виведення захоплення в OutputStream. Будь ласка, зверніться до цієї теми для отримання додаткової інформації: Як я можу отримати дані про команду ...
Не помилка

1
Посилання, щоб додати залежність бібліотеки Apache Commons Exec до вашого проекту - commons.apache.org/proper/commons-exec/dependency-info.html
aunlead

2
найкраще рішення.
Дев

23

Я б сказав, що не в дусі Java запускати скрипт оболонки з Java. Java призначена для крос-платформи, а запуск сценарію оболонки обмежить її використання лише UNIX.

З огляду на це, безумовно, можна запустити скрипт оболонки з Java. Ви б використовували абсолютно той самий синтаксис, який ви вказали (я сам не пробував його, але спробуйте виконати скрипт оболонки безпосередньо, і якщо це не працює, виконайте оболонку, передавши скрипт у параметр командного рядка) .


43
Так, але багато в чому мантра «дух Яви» або «писати один раз бігай скрізь» - це міф.
BobbyShaftoe

15
Що в цьому міфічного?
Кріс Баланс

15
в чому міфічне, що ти зазвичай закінчуєш писати численні switchesта ifзаяви, щоб обійти всі нюанси, які не працюють абсолютно однаково на різних платформах, незважаючи на зусилля людей, які придумали основні бібліотеки Java.
Брайан Свіні

2
Дивно, але! Я згоден з усіма коментарями вище та відповіддю!
AnBisw

11
@BobbyShaftoe Я пишу Java протягом 16 років і завжди розроблявся на Windows, усі мої додатки завжди розгорнуті на коробках Unix з ароматом solaris / ibm або oracle, тому я не маю поняття, про що ви говорите
Kalpesh Soni

21

Я думаю, що ти відповів на власне запитання

Runtime.getRuntime().exec(myShellScript);

Щодо хорошої практики ... що ви намагаєтеся зробити зі скриптом оболонки, який ви не можете зробити з Java?


Я стикався з подібною ситуацією, коли мені потрібно rsync кілька файлів на різних серверах, коли в моєму коді Java виникає певна умова. Чи є ще якийсь кращий спосіб?
v kumar

1
@Chris Balance ... Я знаю, що цей коментар майже через 10 років :), але щоб відповісти на ваше запитання, що робити, якщо моя програма повинна взаємодіяти з півдесятка каналів низхідного та нижнього течії та залежати від прийнятого способу спілкування. Особливо, коли ви працюєте над проектом, який взаємодіє з такою кількістю дивних каналів :)
Stunner

Висунення функціоналу до сценарію оболонки буде зусиллям останнього канаву, якщо немає іншого способу виконати роботу на Java. Координація стану та залежностей буде обов'язково складно, якщо ви підштовхуєте роботу до сценарію оболонки. Іноді сценарій оболонки - єдиний спосіб, або часовий кадр робить його єдиним розумним способом виконати трохи роботи, тому це спосіб зробити це.
Кріс Баланс

11

Так, це можна зробити. Це вийшло для мене.

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

Ось мій приклад. Сподіваюся, це має сенс.

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

Так, це можливо, і ви відповіли на це! Щодо передового досвіду, я думаю, що краще запускати команди з файлів, а не безпосередньо зі свого коду. Таким чином , ви повинні зробити Java виконати список команд (або однієї команди) в існуючих .bat, .sh, .ksh... файлах. Ось приклад виконання списку команд у файлі MyFile.sh:

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

4

Щоб уникнути необхідності жорсткого коду абсолютного шляху, ви можете використовувати наступний метод, який знайде та виконає ваш скрипт, якщо він знаходиться у вашій кореневій директорії.

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

Як на мене, все має бути простим. Для запуску сценарію просто потрібно виконати

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

3

Процес Виконавець ZT бібліотека є альтернативою Apache Commons Exec. Він має функціонал для запуску команд, фіксації їх виводу, встановлення тайм-аутів тощо.

Я його ще не використовував, але він виглядає досить добре зафіксованим.

Приклад з документації: Виконання команди, перекачування stderr до реєстратора, повернення виводу у вигляді рядка UTF8.

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

У його документації перелічені наступні переваги перед Commons Exec:

  • Покращене поводження з потоками
    • Читання / запис до потоків
    • Перенаправлення stderr на stdout
  • Покращена обробка тайм-аутів
  • Поліпшена перевірка кодів виходу
  • Вдосконалений API
    • Один вкладиш для досить складних випадків використання
    • Один вкладиш для отримання результату процесу в String
    • Доступ до об'єкта Process доступний
    • Підтримка процесів асинхронізації ( майбутнє )
  • Поліпшення ведення журналу за допомогою API SLF4J
  • Підтримка декількох процесів

2

Ось приклад того, як запустити скрипт для bash Unix або Windows bat / cmd з Java. Аргументи можна передавати на скрипт і виводити з нього сценарій. Метод приймає довільну кількість аргументів.

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 - використовувати '\'. Hier - приклад 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

Hier - це простий 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

1

Можна, просто виконайте це як будь-яку іншу програму. Просто переконайтеся, що ваш сценарій має належний номер #! (she-bang) рядок як перший рядок сценарію, і переконайтеся, що на файл є дозволи на виконання.

Наприклад, якщо це скрипт bash, поставте #! / Bin / bash у верхній частині сценарію, також chmod + x.

Крім того, якщо це хороша практика, ні, це не так, особливо для Java, але якщо це економить вам багато часу на перенесення великого сценарію, і ви не отримуєте додаткові гроші за це;) заощадите свій час, виконайте сценарій та перенесіть перенесення на Java у свій довгостроковий список todo.


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

Це пізня відповідь. Однак я подумав про те, щоб укласти боротьбу, яку мені довелося перенести, щоб отримати сценарій оболонки, виконаний із програми Spring-Boot для майбутніх розробників.

  1. Я працював у Spring-Boot, і мені не вдалося знайти файл, який потрібно виконати з моєї програми Java, і він кидав FileNotFoundFoundException. Мені довелося зберігати файл у resourcesкаталозі, і мені довелося встановити файл, який потрібно сканувати, pom.xmlпід час запуску програми, як описано нижче.

    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
                <include>**/*.sh</include>
            </includes>
        </resource>
    </resources>
  2. Після цього у мене виникли проблеми із виконанням файлу, і він повертався error code = 13, Permission Denied. Тоді мені довелося зробити файл виконуваним, запустивши цю команду -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();
    }
}

Сподіваюся, що вирішує чиюсь проблему.


0

Так само, що Solaris 5.10 працює так, це ./batchstart.shхитрість, я не знаю, чи ваша ОС приймає це використовувати \\. batchstart.shзамість цього. Цей подвійний нахил може допомогти.


0

Я думаю, с

System.getProperty("os.name"); 

Перевірка операційної системи може керувати скриптами shell / bash, якщо такі підтримуються. якщо є необхідність зробити код переносним.


0

для використання в 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);
    }
}

і для використання;

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

АБО

ShellAssistance.runShell("", "pg_dump", new String[]{"-U", "aliAdmin", "-f", "/home/Backup.sql", "StoresAssistanceDB"}, new Hashmap<>());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.