Как запустить скрипт оболочки Unix из кода Java?

155

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

Runtime.getRuntime().exec(myCommand);

Но возможно ли запустить сценарий оболочки Unix из кода Java? Если да, будет ли хорошей практикой запускать сценарий оболочки из кода Java?

Lii
источник
3
Вещи становятся интересными, если этот сценарий оболочки является интерактивным.
Эрнесто
что такое переменная myCommand это та строка? если да, то он не будет работать, метод 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? Есть проблемы с производительностью?
Каутуксани
1
Обратите внимание, что вам может потребоваться указать программу / bin / bash или sh для выполнения скрипта в зависимости от конфигурации Java (см. Stackoverflow.com/questions/25647806/… )
Бен Холланд,
@ Milhous Я знаю, что уже довольно поздно и что-то может измениться, но для текущей документации по Java-процессам этот метод не рекомендуется для сценариев оболочки: docs.oracle.com/javase/8/docs/api/java/lang/Process.html " Методы, которые создают процессы, могут не работать для специальных процессов на определенных собственных платформах, таких как собственные процессы управления окнами, процессы-демоны, процессы Win16 / DOS в Microsoft Windows или сценарии оболочки ».
Харман
24

Ты можешь использовать библиотеку Apache Commons exec .

Пример :

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. Я хочу знать, возможно ли это сделать
Kranthi Sama
@KranthiSama Вы можете установить OutputStreamдля DefaultExecuterиспользования DefaultExecuter.setStreamHandlerметоды для вывода захвата в OutputStream. Пожалуйста, обратитесь к этой теме для получения дополнительной информации: Как я могу захватить вывод команды ...
Не ошибка
1
Ссылка для добавления зависимости библиотеки Apache Commons Exec в ваш проект - commons.apache.org/proper/commons-exec/dependency-info.html
aunlead
2
лучшее решение
Dev
23

Я бы сказал, что запускать скрипт оболочки из Java не в духе Java. Java должен быть кроссплатформенным, и запуск сценария оболочки ограничит его использование только UNIX.

С учетом сказанного, безусловно, можно запускать сценарий оболочки из Java. Вы бы использовали тот же синтаксис, который вы перечислили (я сам не пробовал, но попробуйте выполнить скрипт оболочки напрямую, и если это не сработает, запустите саму оболочку, передав скрипт в качестве параметра командной строки) ,

Джек Леу
источник
43
Да, но во многих отношениях эта мантра «дух Явы» или «пиши однажды, беги повсюду» - это миф.
BobbyShaftoe
15
Что в этом мифического?
Крис Балланс
15
что мифический о нем, что вы обычно в конечном итоге писать многочисленные switchesи ifзаявления , чтобы обойти все нюансы , которые не работают точно так же на различных платформах , несмотря на все усилия людей , которые пришли с Java основные библиотеки.
Брайан Суини
2
Довольно удивительно! Я согласен со всеми комментариями выше и ответом!
AnBisw
11
@BobbyShaftoe Я пишу Java уже 16 лет и всегда разрабатывал для Windows, все мои приложения всегда развертываются на Unix-системах со соляриком / IBM или Oracle, поэтому я понятия не имею, о чем вы говорите
Kalpesh Soni
21

Я думаю, что вы ответили на свой вопрос

Runtime.getRuntime().exec(myShellScript);

Что касается того, является ли это хорошей практикой ... что вы пытаетесь сделать с помощью сценария оболочки, который вы не можете сделать с Java?

Крис Балланс
источник
Я столкнулся с подобной ситуацией, когда мне нужно rsync несколько файлов на разных серверах, когда в моем коде Java возникает определенное условие. Есть ли другой способ лучше?
v kumar
1
@Chris Ballance ... Я знаю, что этот комментарий почти через 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!");
    }
}
anataliocs
источник
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 на стандартный вывод
  • Улучшена обработка тайм-аутов
  • Улучшена проверка кодов выхода
  • Улучшенный API
    • Один лайнер для довольно сложных случаев использования
    • Один лайнер, чтобы получить вывод процесса в строку
    • Доступ к объекту Process доступен
    • Поддержка асинхронных процессов ( Будущее )
  • Улучшено ведение журнала с SLF4J API
  • Поддержка нескольких процессов
Lii
источник
2

Вот пример того, как запустить скрипт Unix bash или 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 - это простой сценарий Windows cmd 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 в свой долгосрочный список задач.

Kekoa
источник
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
источник
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"); 

Проверка операционной системы может управлять сценариями оболочки / bash, если таковые поддерживаются. если есть необходимость сделать код переносимым.

DayaMoon
источник
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<>());
Али Багери
источник