관리 메뉴

피터의 개발이야기

[Kotlin] kotlin에서 shell 실행하기 본문

Programming/Kotlin

[Kotlin] kotlin에서 shell 실행하기

기록하는 백앤드개발자 2024. 7. 17. 10:10
반응형

ㅁ 들어가며

ㅇ 대량 압축작업 시 OOME(Out Of Memory Exception)의 발생을 방지하기 위한 방법을 찾는 중이다.

ㅇ 지난 글, [Kotlin] ManagementFactory을 이용한 JVM 모니터링 방법에서는 JVM의 메모리 사용량을 검토하여 서킷브레이크 패턴을 적용하려고 검토하였다.

ㅇ 하지만 JVM의 힙메모리를 넘어서는 경우 커널에서 압축을 진행하는 방법도 검토하게 되면서 Kotlin에서 shell을 실행하는 방법을 공부하였다.

 

ㅁ shell 실행코드

fun runCommand(command: String) {
    try {
        val process = Runtime.getRuntime().exec(command)

        // 명령어 실행 결과 읽기
        val reader = BufferedReader(InputStreamReader(process.inputStream))
        var line: String?
        while (reader.readLine().also { line = it } != null) {
            println(line)
        }

        // 에러 스트림 읽기
        val errorReader = BufferedReader(InputStreamReader(process.errorStream))
        while (errorReader.readLine().also { line = it } != null) {
            System.err.println(line)
        }

        // 프로세스 종료 대기 및 종료 코드 확인
        val exitCode = process.waitFor()
        println("명령어 실행 완료. 종료 코드: $exitCode")

    } catch (e: Exception) {
        e.printStackTrace()
    }
}

fun main() {
    println("\n######################")
    println("# 정상 shell 명령어\n")
    runCommand("ls -l") // 실행할 shell 명령어
}

출력:
######################
# 정상 shell 명령어

total 8
-rw-r--r--@ 1 peterseo  staff  498 May  6 14:24 hello-kotlin.iml
drwxr-xr-x@ 3 peterseo  staff   96 May  6 14:17 out
drwxr-xr-x@ 3 peterseo  staff   96 May  6 14:51 src
drwxr-xr-x@ 3 peterseo  staff   96 Jul 11 07:20 zipDirRoot202407
명령어 실행 완료. 종료 코드: 0

 

ㅇ shell의 기본적인 명령어인 ls로 폴더의 파일 리스트를 확인하였다.

ㅇ Runtime.getRuntime().exec(command)를 사용하여 shell 명령어를 실행한다.
ㅇ process.inputStream을 통해 명령어의 표준 출력을 읽는다.
ㅇ process.errorStream을 통해 명령어의 에러 출력을 읽는다.
ㅇ process.waitFor()를 사용하여 프로세스가 종료될 때까지 대기하고, 종료 코드를 얻는다.
ㅇ 예외 처리를 통해 명령어 실행 중 발생할 수 있는 오류를 처리할 수 있다.

 

ㅁ 잘못된 shell 명령어

fun main() {
    println("\n######################")
    println("# error shell 명령어\n")
    runCommand("error") // 잘못된 shell 명령어)
}

출력:
######################
# error shell 명령어

java.io.IOException: Cannot run program "error": error=2, No such file or directory
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1170)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1089)
	at java.base/java.lang.Runtime.exec(Runtime.java:681)
	at java.base/java.lang.Runtime.exec(Runtime.java:491)
	at java.base/java.lang.Runtime.exec(Runtime.java:366)
	at ShellTestKt.runErrorCmd(ShellTest.kt:36)
	at ShellTestKt.main(ShellTest.kt:67)
	at ShellTestKt.main(ShellTest.kt)
Caused by: java.io.IOException: error=2, No such file or directory
	at java.base/java.lang.ProcessImpl.forkAndExec(Native Method)
	at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:295)
	at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:225)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1126)
	... 7 more

ㅇ 에러 스트림을 확인하기 위해 "error"라는 잘못된 명령어를 실행하였다.

 

ㅁ 파일 폴더를 압축하고 에러 처리가 가능한 shell(tar 버젼)

#!/bin/bash

# 압축할 폴더와 결과 파일 이름을 인자로 받음
SOURCE_DIR="$1"
OUTPUT_FILE="$2"

# 함수: 에러 메시지 출력 및 종료
error_exit() {
    echo "에러: $1" >&2
    exit 1
}

# 인자 개수 확인
if [ $# -ne 2 ]; then
    error_exit "사용법: $0 <압축할_폴더> <결과_파일.tar.gz>"
fi

# 소스 디렉토리 존재 확인
if [ ! -d "$SOURCE_DIR" ]; then
    error_exit "지정한 폴더가 존재하지 않습니다: $SOURCE_DIR"
fi

# 출력 파일 이름이 .tar.gz로 끝나는지 확인
if [[ "$OUTPUT_FILE" != *.tar.gz ]]; then
    OUTPUT_FILE="${OUTPUT_FILE}.tar.gz"
    echo "출력 파일 이름을 $OUTPUT_FILE로 변경했습니다."
fi

# 압축 진행: -C 
echo "압축 시작: $SOURCE_DIR -> $OUTPUT_FILE"
if tar -czf "$OUTPUT_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")" 2>/dev/null; then
    echo "압축 완료: $OUTPUT_FILE"
else
    error_exit "압축 과정에서 오류가 발생했습니다."
fi

# 압축 파일 크기 확인
FILE_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
echo "압축 파일 크기: $FILE_SIZE"

# 압축 파일 존재 확인
if [ -f "$OUTPUT_FILE" ]; then
    echo "압축 파일이 성공적으로 생성되었습니다: $OUTPUT_FILE"
else
    error_exit "압축 파일 생성에 실패했습니다."
fi

 

 옵션  설명
 -c  파일을 tar로 묶음
 -p  파일 권한을 저장
 -v  묶거나 파일을 풀 때 과정을 화면으로 출력
 -f  파일 이름을 지정
 -C  경로를 지정
 -x  tar 압축을 풂
 -z  gzip으로 압축하거나 해제함

출처: https://nota.tistory.com/53 [nota's story:티스토리]

 

ㅁ zip 버젼

#!/bin/bash

# 사용법 함수
usage() {
    echo "사용법: $0 <압축할_폴더_경로> [출력_zip_파일명]"
    echo "예: $0 /path/to/folder [output.zip]"
    exit 1
}

# 인자 개수 확인
if [ $# -lt 1 ]; then
    usage
fi

# 입력 폴더와 출력 파일명 설정
INPUT_FOLDER="$1"
OUTPUT_ZIP="${2:-${INPUT_FOLDER##*/}.zip}"

# 폴더 존재 여부 확인
if [ ! -d "$INPUT_FOLDER" ]; then
    echo "에러: '$INPUT_FOLDER' 폴더가 존재하지 않습니다."
    exit 1
fi

# ZIP 명령어 존재 여부 확인
if ! command -v zip &> /dev/null; then
    echo "에러: 'zip' 명령어를 찾을 수 없습니다. 설치가 필요합니다."
    exit 1
fi

# 압축 실행
echo "압축 중: $INPUT_FOLDER -> $OUTPUT_ZIP"
if zip -r "$OUTPUT_ZIP" "$INPUT_FOLDER"; then
    echo "압축 완료: $OUTPUT_ZIP"
else
    echo "에러: 압축 과정에서 문제가 발생했습니다."
    exit 1
fi

# 압축 파일 크기 확인
ZIP_SIZE=$(du -h "$OUTPUT_ZIP" | cut -f1)
echo "압축 파일 크기: $ZIP_SIZE"

# 압축 파일 무결성 검사
echo "압축 파일 무결성 검사 중..."
if unzip -t "$OUTPUT_ZIP" > /dev/null; then
    echo "무결성 검사 통과"
else
    echo "에러: 압축 파일 무결성 검사 실패"
    exit 1
fi

 

ㅁ 테스트 temp 폴더

[Kotlin] File들을 코드별 폴더로 압축하기에서 생성한 폴더이다.

 

ㅁ fieZipTest.sh 실행

fun main(){
println("\n######################")
    println("# fileZipTest\n")
    runCommand("sh fileZipTest.sh temp test.zip") // 압축할 폴더와 압축명을 인자로 전달
    
    println("\n######################")
    println("# fileZipTest error\n")
    runCommand("sh fileZipTest.sh wrong_temp test.zip") // 압축할 폴더와 압축명을 인자로 전달
}

출력: 
######################
# fileZipTest

압축 중: temp -> test.zip
  adding: temp/ (stored 0%)
  adding: temp/CODE2/ (stored 0%)
  adding: temp/CODE2/file3.txt (stored 0%)
  adding: temp/CODE2/file4.txt (stored 0%)
  adding: temp/CODE3/ (stored 0%)
  adding: temp/CODE3/file5.txt (stored 0%)
  adding: temp/CODE3/file6.txt (stored 0%)
  adding: temp/CODE1/ (stored 0%)
  adding: temp/CODE1/file2.txt (stored 0%)
  adding: temp/CODE1/file1.txt (stored 0%)
압축 완료: test.zip
압축 파일 크기: 4.0K
압축 파일 무결성 검사 중...
무결성 검사 통과
명령어 실행 완료. 종료 코드: 0

######################
# fileZipTest error

에러: 'wrong_temp' 폴더가 존재하지 않습니다.
명령어 실행 완료. 종료 코드: 1

 

ㅁ 마무리

ㅇ 위 코드는 기본적으로 시스템의 기본 shell을 실행할 수 있다.

ㅇ 복잡한 shell 스크립트를 실행하려면 별도의 실행 스크립트 파일을 만들고 그 파일을 실행하는 방식을 이용한다면, JVM에서 실행하기 힘든 압축작업을 충분히 수행할 수 있을 것이다.

반응형
Comments