Language/JAVA

[JAVA] java에서 shell command 실행

yjkim_97 2021. 11. 24. 17:38

2021.11.24

SpringBoot 백엔드 서버에서 데이터베이스에 등록된 command를 실행하고 그 응답을 받아오는 기능을 구현하게 되었다.

본격적으로 구현하기에 앞서 어떻게 실행하고 결과를 받아오는지 한번 간단한 예제를 만들어 보았다.

 

실행하고 결과를 받아오는 것은 생각보다 매우 쉬웠다. 분명 추가적인 예외처리는 필요 할 것이다.


개발환경

  • Spring Boot
  • JAVA 8
  • Mac OS

Java 코드로 shell 명령어를 실행하는 방법에는 두가지가 있다. 첫번째는 Runtime 클래스를 사용하는 것이고, 두번째는 ProcessBuilder 인스턴스를 사용하는 것이다. 

  • Runtime
  • ProcessBuilder (Spring boot에서는 ProcessBuilder를 사용하는 것을 권장한다.)

 

1. OS dependency

그러나, shell 명령어를 다루는 Process를 실행하기 전에 먼저 JVM이 올라가는 운영체제를 알아야한다.

그 이유로는 Window에서는 cmd.exe 사용해야 하고 그외 운영체제에서는 sh을 사용하기 때문이다.

 

운영체제를 알아내는 코드는 아래와 같다.

boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");

 

2. Input and Output

그 다음 shell command를 실행 후 결과를 받아오기 위한 클래스를 구현한다.

StreamGobbler 클래스를 생성하고 이 클래스는 별도의 Thread를 생성한다. StreamGobbler에서 InputStream은 Javadptj shell command실행한 process의 InputStream이다.

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);
	}
	
}

run 메소드를 보면 Process의 InputStream을 한줄 씩 읽어서 Buffer에 쓴 다음 consumer에 전달하고 있다.

 

3. Runntime.exec()

먼저 Runtime을 사용하여 shell command를 실행해 보도록 하겠다. 실행하는 명령어는 ls -l 이다.

private static void runtime(boolean isWindows) throws IOException, InterruptedException  
{
	System.out.println(":: START :: Use Runtime ");
	String homeDirectory = System.getProperty("user.home");
	System.out.println(":: homeDirectory is "+ homeDirectory);
	
	Process process;
	if (isWindows) 
	{
	    process = Runtime.getRuntime()
	      .exec(String.format("cmd.exe /c dir %s", homeDirectory));
	} 
	else 
	{
	    process = Runtime.getRuntime()
	      .exec(String.format("sh -c ls -l %s", homeDirectory));
	}
	StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println);
	Executors.newSingleThreadExecutor().submit(streamGobbler);
	int exitCode = process.waitFor();
	assert exitCode == 0;
}

실행 결과는 아래와 같다.

결과를 잘 보면 process = Runtime.getRuntime().exec(String.format("sh -c ls -l %s", homeDirectory)); 명령실행 디렉토리 경로를 지정해주었지만 현재 경로에서 실행되었다..

 

4. ProcessBuilder

이번에는 ProcessBuilder 인스턴스를 사용해보겠다.

This is preferred over the Runtime approach because we're able to customize some details.
For example we're able to:
1. change the working directory our shell command is running in using builder.directory()
2. set-up a custom key-value map as environment using builder.environment()
3. redirect input and output streams to custom replacements
4. inherit both of them to the streams of the current JVM process using builder.inheritIO()

SpringBoot 문서를 보면 ProcessBuilder를 사용하면 작업 디렉토리를 변경할 수 있고, 사용자가 일부 환경을 설정할 수 있는 등의 이유로 Runtime을 사용하는 것보다 ProcessBuilder 사용을 더 권장한다고 되어 있다. 

private static void processBuilder(boolean isWindows) throws IOException, InterruptedException 
{
	System.out.println(":: START :: Use ProcessBuilder ");
	String homeDirectory = System.getProperty("user.home");
	System.out.println(":: homeDirectory is "+ homeDirectory);
	
	ProcessBuilder builder = new ProcessBuilder();
	builder.directory(new File(homeDirectory));
	if (isWindows) 
	{
	    builder.command("cmd.exe", "/c", "dir");
	}
	else 
	{
	    builder.command("sh", "-c", "ls -l | grep P");
	}
	
	Process process = builder.start();
	StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println);
	Executors.newSingleThreadExecutor().submit(streamGobbler);
	
	int exitCode = process.waitFor();
	assert exitCode == 0;
	
}

결과는 아래 사진과 같다.

확인해 보면 Runtime과 다르게 지정한 경로에서 command가 실행되었다.

 

5. Sample 전체 로직

package com.innerwave.surfinn;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.Executors;
import java.util.function.Consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TestApplication {

	
	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);
		}
		
	}
	
	private static void runtime(boolean isWindows) throws IOException, InterruptedException  
	{
		System.out.println(":: START :: Use Runtime ");
		String homeDirectory = System.getProperty("user.home");
		System.out.println(":: homeDirectory is "+ homeDirectory);
		
		Process process;
		if (isWindows) 
		{
		    process = Runtime.getRuntime()
		      .exec(String.format("cmd.exe /c dir %s", homeDirectory));
		} 
		else 
		{
		    process = Runtime.getRuntime()
		      .exec(String.format("sh -c ls -l %s", homeDirectory));
		}
		StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println);
		Executors.newSingleThreadExecutor().submit(streamGobbler);
		int exitCode = process.waitFor();
		assert exitCode == 0;
	}
	
	private static void processBuilder(boolean isWindows) throws IOException, InterruptedException 
	{
		System.out.println(":: START :: Use ProcessBuilder ");
		String homeDirectory = System.getProperty("user.home");
		System.out.println(":: homeDirectory is "+ homeDirectory);
		
		ProcessBuilder builder = new ProcessBuilder();
		builder.directory(new File(homeDirectory));
		if (isWindows) 
		{
		    builder.command("cmd.exe", "/c", "dir");
		}
		else 
		{
		    builder.command("sh", "-c", "ls -l | grep P");
		}
		
		Process process = builder.start();
		StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println);
		Executors.newSingleThreadExecutor().submit(streamGobbler);
		
		int exitCode = process.waitFor();
		assert exitCode == 0;
		
	}
	
	public static void main(String[] args) throws IOException, InterruptedException {
		//SpringApplication.run(TestApplication.class, args);
		
		boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
		
		System.out.println(":: OS is "+ (isWindows ? "window" : "mac"));
		
		//TestApplication.runtime(isWindows);
		TestApplication.processBuilder(isWindows);
	}

}

 


결론은 ProcessBuilder를 사용하여 구현해야할 것 같다.

 


https://www.baeldung.com/run-shell-command-in-java