1. 개요
이 기사에서는 Java 애플리케이션에서 쉘 명령을 실행하는 방법을 배웁니다 .
먼저 . Runtime 클래스가 제공 하는 exec() 메소드 . 그런 다음 더 많은 사용자 정의가 가능한 ProcessBuilder 에 대해 알아봅니다 .
2. 운영 체제 의존성
셸 명령은 동작이 시스템마다 다르기 때문에 OS에 따라 다릅니다 . 따라서 쉘 명령을 실행할 프로세스 를 생성하기 전에 JVM이 실행 중인 운영 체제를 알아야 합니다.
또한 Windows에서 셸은 일반적으로 cmd.exe 라고 합니다 . 대신 Linux 및 macOS에서는 셸 명령이 /bin/sh 를 사용하여 실행됩니다 . 이러한 서로 다른 시스템에서의 호환성을 위해 Windows 시스템에서는 cmd.exe 를 프로그래밍 방식으로 추가하고 그렇지 않으면 /bin/ sh 를 추가할 수 있습니다 . 예를 들어 시스템 클래스 에서 "os.name" 속성을 읽어 코드가 실행 중인 컴퓨터가 Windows 컴퓨터인지 확인할 수 있습니다 .
boolean isWindows = System.getProperty("os.name")
.toLowerCase().startsWith("windows");
3. 입출력
종종 프로세스의 입력 및 출력 스트림을 연결해야 합니다. 구체적으로 InputStream 은 표준 입력 역할을 하고 OutputStream 은 프로세스의 표준 출력 역할을 합니다. 우리는 항상 출력 스트림을 소비해야 합니다. 그렇지 않으면 프로세스가 반환되지 않고 영원히 중단됩니다.
InputStream 을 사용하는 일반적으로 사용되는 StreamGobbler 클래스를 구현해 보겠습니다 .
private static class StreamGobbler implements Runnable {
private InputStream inputStream;
private Consumer<String> consumer;
public StreamGobbler(InputStream inputStream, Consumer<String> consumer) {
this.inputStream = inputStream;
this.consumer = consumer;
}
@Override
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines()
.forEach(consumer);
}
}
이 클래스는 Runnable 인터페이스를 구현합니다. 즉, 모든 Executor 가 실행할 수 있습니다.
4. 런타임.exec()
다음으로 .exec() 메서드를 사용 하여 새 프로세스를 생성하고 이전에 만든 StreamGobler 를 사용합니다 .
예를 들어 사용자의 홈 디렉토리에 있는 모든 디렉토리를 나열한 다음 콘솔에 출력할 수 있습니다.
String homeDirectory = System.getProperty("user.home");
Process process;
if (isWindows) {
process = Runtime.getRuntime()
.exec(String.format("cmd.exe /c dir %s", homeDirectory));
} else {
process = Runtime.getRuntime()
.exec(String.format("/bin/sh -c ls %s", homeDirectory));
}
StreamGobbler streamGobbler =
new StreamGobbler(process.getInputStream(), System.out::println);
Future<?> future = Executors.newSingleThreadExecutor().submit(streamGobbler);
int exitCode = process.waitFor();
assert exitCode == 0;
future.get(); // waits for streamGobbler to finish
여기에서 .newSingleThreadExecutor() 를 사용 하여 새 하위 프로세스를 만든 다음 .submit() 을 사용 하여 쉘 명령을 포함하는 프로세스 를 실행 했습니다. 또한 .submit() 은 프로세스 결과를 확인하는 데 사용하는 Future 객체를 반환합니다 . 또한 계산이 완료될 때까지 기다리려면 반환된 객체에서 .get() 메서드 를 호출해야 합니다 .
참고: JDK 18 은 Runtime 클래스 에서 .exec(String command) 를 더 이상 사용하지 않습니다.
4.1. 핸들 파이프
현재 .exec() 로 파이프를 처리할 방법이 없습니다 . 다행스럽게도 파이프는 쉘 기능입니다. 따라서 파이프를 사용하려는 전체 명령을 만들고 .exec() 에 전달할 수 있습니다 .
if (IS_WINDOWS) {
process = Runtime.getRuntime()
.exec(String.format("cmd.exe /c dir %s | findstr \"Desktop\"", homeDirectory));
} else {
process = Runtime.getRuntime()
.exec(String.format("/bin/sh -c ls %s | grep \"Desktop\"", homeDirectory));
}
여기에서 사용자 홈의 모든 디렉토리를 나열하고 "Desktop" 폴더를 검색합니다.
5. 프로세스 빌더
또는 문자열 명령을 실행하는 대신 사용자 정의할 수 있기 때문에 런타임 접근 방식 보다 선호되는 ProcessBuilder 를 사용할 수 있습니다.
요컨대, 이 접근 방식을 통해 다음을 수행할 수 있습니다.
- 를 사용하여 쉘 명령이 실행되는 작업 디렉토리를 변경하십시오. 예배 규칙서()
- .environment() 에 키-값 맵을 제공하여 환경 변수를 변경합니다.
- Custom 방식으로 입력 및 출력 스트림 리디렉션
- .inheritIO() 를 사용하여 둘 다 현재 JVM 프로세스의 스트림으로 상속합니다.
마찬가지로 이전 예제와 동일한 셸 명령을 실행할 수 있습니다.
ProcessBuilder builder = new ProcessBuilder();
if (isWindows) {
builder.command("cmd.exe", "/c", "dir");
} else {
builder.command("sh", "-c", "ls");
}
builder.directory(new File(System.getProperty("user.home")));
Process process = builder.start();
StreamGobbler streamGobbler =
new StreamGobbler(process.getInputStream(), System.out::println);
Future<?> future = Executors.newSingleThreadExecutor().submit(streamGobbler);
int exitCode = process.waitFor();
assert exitCode == 0;
future.get(10, TimeUnit.SECONDS)
6. 결론
이 빠른 사용방법(예제)에서 살펴본 것처럼 두 가지 고유한 방법으로 Java에서 셸 명령을 실행할 수 있습니다.
일반적으로 생성된 프로세스의 실행을 사용자 지정하려는 경우(예: 작업 디렉터리 변경) ProcessBuilder 사용을 고려해야 합니다 .
항상 그렇듯이 소스는 GitHub에서 사용할 수 있습니다 .