Как запустить скрипт оболочки Unix из кода Java?
Запустить команду Unix из Java довольно просто.
Runtime.getRuntime().exec(myCommand);
Но возможно ли запустить скрипт оболочки Unix из кода Java? Если да, будет ли хорошей практикой запускать сценарий оболочки из кода Java?
18 ответов
Вы действительно должны посмотреть на 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();
Вы также можете использовать библиотеку 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.
Я бы сказал, что запускать скрипт оболочки из Java не в духе Java. Java должен быть кроссплатформенным, и запуск сценария оболочки ограничит его использование только UNIX.
С учетом сказанного, безусловно, можно запускать сценарий оболочки из Java. Вы бы использовали тот же синтаксис, который вы перечислили (я сам не пробовал, но попробуйте выполнить сценарий оболочки напрямую, и если это не сработает, запустите саму оболочку, передав сценарий в качестве параметра командной строки),
Я думаю, что вы ответили на свой вопрос
Runtime.getRuntime().exec(myShellScript);
Что касается того, является ли это хорошей практикой... что вы пытаетесь сделать с помощью сценария оболочки, который вы не можете сделать с Java?
Да, это возможно. Это сработало для меня.
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());
}
}
Вот мой пример. Надеюсь, это имеет смысл.
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(this.isLinux()){
Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", filePath}, null);
}else if(this.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;
}
Чтобы избежать жесткого кодирования абсолютного пути, вы можете использовать следующий метод, который найдет и выполнит ваш скрипт, если он находится в корневом каталоге.
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!");
}
}
Да, это возможно, и вы ответили на это! Что касается хороших практик, я думаю, что лучше запускать команды из файлов, а не напрямую из вашего кода. Таким образом, вы должны заставить Java выполнять список команд (или одну команду) в существующих.bat, .sh, .ksh ... файлах. Вот пример выполнения списка команд в файле "MyFile.sh":
String[] cmd = { "sh", "MyFile.sh", "\pathOfTheFile"};
Runtime.getRuntime().exec(cmd);
Вот пример того, как запустить скрипт 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 - это простой 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
Что касается меня, все должно быть просто. Для запуска скрипта просто необходимо выполнить
new ProcessBuilder("pathToYourShellScript").start();
Библиотека ZT Process Executor является альтернативой 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
- Поддержка нескольких процессов
Это поздний ответ. Тем не менее, я подумал о том, чтобы изо всех сил пытаться заставить сценарий оболочки выполняться из приложения Spring-Boot для будущих разработчиков.
Я работал в 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>
- После этого у меня были проблемы с выполнением файла, и он возвращался
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();
}
}
Надеюсь, что это решает чью-то проблему.
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();
}
Я думаю с
System.getProperty("os.name");
Проверка операционной системы может управлять сценариями оболочки /bash, если таковые поддерживаются. если есть необходимость сделать код переносимым.
Это возможно, просто выполнить его как любую другую программу. Просто убедитесь, что ваш скрипт имеет правильный #! (she-bang) в качестве первой строки скрипта и убедитесь, что для файла есть разрешения на выполнение.
Например, если это скрипт bash, поместите #!/ Bin/bash в начало скрипта, также chmod +x .
Кроме того, если это хорошая практика, то нет, особенно для Java, но если это сэкономит вам много времени на перенос большого сценария, и вам не будут платить за это;) сэкономьте свое время, выполните сценарий, и поместите перенос на Java в свой долгосрочный список задач.
Точно так же, как в Solaris 5.10 он работает так ./batchstart.sh
есть трюк, я не знаю, если ваша ОС принимает его использовать \\. batchstart.sh
вместо. Эта двойная косая черта может помочь.
Вы также можете использовать мою небольшую библиотеку 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();
Он не только выполнит команду оболочки, но также отправит ее вывод в журнал Slf4j, дождется ее завершения, перенаправит стандартный вывод на стандартный вывод и обеспечит возникновение исключения, если код выхода не равен нулю.
Для использования в 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<>());