Запуск команд Bash в Java
У меня есть следующий класс. Это позволяет мне выполнять команды через java.
public class ExecuteShellCommand {
public String executeCommand(String command) {
StringBuffer output = new StringBuffer();
Process p;
try {
p = Runtime.getRuntime().exec(command);
p.waitFor();
BufferedReader reader =
new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
while ((line = reader.readLine())!= null) {
output.append(line + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
}
Когда я запускаю команды, результат предыдущей команды не сохраняется. Например:
public static void main(String args[]) {
ExecuteShellCommand com = new ExecuteShellCommand();
System.out.println(com.executeCommand("ls"));
System.out.println(com.executeCommand("cd bin"));
System.out.println(com.executeCommand("ls"));
}
Выдает вывод:
bin
src
bin
src
Почему вторая команда 'ls' не показывает содержимое каталога bin?
Ответы
Ответ 1
Вы начинаете новый процесс с Runtime.exec(command)
. У каждого процесса есть рабочий каталог. Обычно это каталог, в котором запущен родительский процесс, но вы можете изменить каталог, в котором запущен ваш процесс.
Я бы порекомендовал использовать ProcessBuilder
ProcessBuilder pb = new ProcessBuilder("ls");
pb.inheritIO();
pb.directory(new File("bin"));
pb.start();
Если вы хотите выполнить несколько команд в оболочке, лучше создать временный сценарий оболочки и запустить его.
public void executeCommands() throws IOException {
File tempScript = createTempScript();
try {
ProcessBuilder pb = new ProcessBuilder("bash", tempScript.toString());
pb.inheritIO();
Process process = pb.start();
process.waitFor();
} finally {
tempScript.delete();
}
}
public File createTempScript() throws IOException {
File tempScript = File.createTempFile("script", null);
Writer streamWriter = new OutputStreamWriter(new FileOutputStream(
tempScript));
PrintWriter printWriter = new PrintWriter(streamWriter);
printWriter.println("#!/bin/bash");
printWriter.println("cd bin");
printWriter.println("ls");
printWriter.close();
return tempScript;
}
Конечно, вы также можете использовать любой другой скрипт в вашей системе. Генерация скрипта во время выполнения имеет смысл, например, если выполняемые команды должны измениться. Но сначала вы должны попытаться создать один сценарий, который можно вызывать с аргументами, а не генерировать его динамически во время выполнения.
Также может быть разумно использовать шаблонизатор, такой как скорость, если генерация скрипта сложна.
EDIT
Вам также следует подумать о том, чтобы скрыть всю сложность компоновщика процессов за простым интерфейсом.
Отделите то, что вам нужно (интерфейс) от того, как это делается (реализация).
public interface FileUtils {
public String[] listFiles(String dirpath);
}
Затем вы можете предоставить реализации, которые используют построитель процессов или, возможно, собственные методы для выполнения работы, и вы можете предоставить различные реализации для разных сред, таких как Linux или Windows.
Наконец, такой интерфейс также проще для проверки в модульных тестах.
Ответ 2
Каждый вызов выполняет в своей собственной оболочке. Таким образом, "cd" второго вызова не рассматривается третьим.
Смотрите: https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exec(java.lang.String).
Это означает, что команда запускается в отдельном процессе. Таким образом, вы создали 3 процесса.
Если вы хотите, чтобы все 3 в одном и том же процессе, попробуйте следующее:
com.executeCommand("ls; cd bin; ls");
Ответ 3
Вы можете создать одну сложную команду bash, которая делает все: "ls; cd bin; ls". Для выполнения этой работы вам необходимо явно вызвать bash. Этот подход должен дать вам всю мощь командной строки bash (обработка цитаты, расширение $, трубы и т.д.).
/**
* Execute a bash command. We can handle complex bash commands including
* multiple executions (; | && ||), quotes, expansions ($), escapes (\), e.g.:
* "cd /abc/def; mv ghi 'older ghi '$(whoami)"
* @param command
* @return true if bash got started, but your command may have failed.
*/
public static boolean executeBashCommand(String command) {
boolean success = false;
System.out.println("Executing BASH command:\n " + command);
Runtime r = Runtime.getRuntime();
// Use bash -c so we can handle things like multi commands separated by ; and
// things like quotes, $, |, and \. My tests show that command comes as
// one argument to bash, so we do not need to quote it to make it one thing.
// Also, exec may object if it does not have an executable file as the first thing,
// so having bash here makes it happy provided bash is installed and in path.
String[] commands = {"bash", "-c", command};
try {
Process p = r.exec(commands);
p.waitFor();
BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
while ((line = b.readLine()) != null) {
System.out.println(line);
}
b.close();
success = true;
} catch (Exception e) {
System.err.println("Failed to execute bash with command: " + command);
e.printStackTrace();
}
return success;
}
Ответ 4
каждая выполняемая вами команда имеет свою собственную оболочку bash, поэтому, как только вы подключитесь к этому каталогу, и для следующей команды вы откроете новую оболочку bash
попробуйте изменить команду на
ls bin
Ответ 5
Каждая команда выполняется индивидуально. Они не разделяют контекст.
Ответ 6
Вы можете использовать команду bash "pmset -g batt", как в методе ниже, ведьма возвращает процент заряда батареи
public int getPercentage() {
Process process = null;
try {
process = Runtime.getRuntime().exec("pmset -g batt");
} catch (IOException e) {
e.printStackTrace();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(
process.getInputStream()));
String s = null;
String y = "";
while (true) {
try {
if (!((s = reader.readLine()) != null)) break;
} catch (IOException e) {
e.printStackTrace();
}
y += s;
System.out.println("Script output: " + s);
}
return Integer.parseInt(y.substring(y.indexOf(')') + 2, y.indexOf('%')));
}
Ответ 7
для справок в будущем: запуск bash команд после cd, в подкаталоге:
import java.io.BufferedReader;
import java.io.InputStreamReader;
/*
$ ( D=somewhere/else ; mkdir -p $D ; cd $D ; touch file1 file2 ; )
$ javac BashCdTest.java && java BashCdTest
.. stdout: -rw-r--r-- 1 ubuntu ubuntu 0 Dec 28 12:47 file1
.. stdout: -rw-r--r-- 1 ubuntu ubuntu 0 Dec 28 12:47 file2
.. stderr: /bin/ls: cannot access isnt_there: No such file or directory
.. exit code:2
*/
class BashCdTest
{
static void execCommand(String[] commandArr)
{
String line;
try
{
Process p = Runtime.getRuntime().exec(commandArr);
BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((line = stdoutReader.readLine()) != null) {
// process procs standard output here
System.out.println(" .. stdout: "+line);
}
BufferedReader stderrReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
while ((line = stderrReader.readLine()) != null) {
// process procs standard error here
System.err.println(" .. stderr: "+line);
}
int retValue = p.waitFor();
System.out.println(" .. exit code:"+Integer.toString(retValue));
}
catch(Exception e)
{ System.err.println(e.toString()); }
}
public static void main(String[] args)
{
String flist = "file1 file2 isnt_there";
String outputDir = "./somewhere/else";
String[] cmd = {
"/bin/bash", "-c",
"cd "+outputDir+" && /bin/ls -l "+flist+" && /bin/rm "+flist
};
execCommand(cmd);
}
}