From d5b7788baa5c648f1c47710a627f025594e40eb0 Mon Sep 17 00:00:00 2001 From: wantaek Date: Thu, 13 Aug 2026 19:00:58 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20TaskletShellStep=EC=9D=B4=20=EC=A4=84=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EA=B2=B0=EA=B3=BC=EB=A1=9C=20=EC=9E=98?= =?UTF-8?q?=EB=AA=BB=EB=90=9C=20=EB=AA=85=EB=A0=B9=EC=9D=84=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=ED=95=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 두 가지를 함께 고친다. 둘 다 같은 줄 분리 코드가 원인이다. 1) 정규식이 개행이 아닌 물음표에서도 자른다 줄 분리에 쓰는 정규식이 "[\r?\n]+"인데, 문자 클래스 안에서 ?는 수량자가 아니라 리터럴이다. \r?\n(선택적 캐리지리턴 + 개행)을 의도한 것으로 보이지만 실제로는 \r, ?, \n 세 문자 중 아무거나에서 잘린다. 그래서 물음표가 든 한 줄짜리 명령이 여러 조각으로 나뉘어 각각 별도 프로세스로 실행된다. 물음표는 파일명 패턴이나 URL 질의 문자열에 흔히 들어가고 ShellScriptSupport가 막는 메타문자 목록에도 없어 그대로 통과한다. echo a?b -> ["echo a", "b"] -> b를 프로그램으로 실행 시도 문자 클래스에서 ?를 빼면 \r과 \n만 구분자가 된다. +가 붙어 있어 CRLF와 연속 개행은 종전대로 하나로 묶인다. 의도로 보이는 \r?\n으로 바꾸지 않은 것은 \r만으로 줄을 나눈 스크립트가 아예 분리되지 않고, 연속된 빈 줄이 빈 문자열 원소를 만들어 shellCmd가 IllegalArgumentException으로 죽기 때문이다. 2) 빈 줄이 빈 명령으로 실행된다 정규식을 고쳐도 스크립트가 개행으로 시작하면 첫 원소가 빈 문자열이 된다. XML 설정에서 안에 줄바꿈을 넣는 건 흔한 형태다. "\necho hello" -> ["", "echo hello"] -> IllegalArgumentException: command must not be null or empty "echo a\n \necho b" -> ["echo a", " ", "echo b"] -> IllegalArgumentException: Empty command 공백만 있는 원소는 shellCmd의 빈 문자열 가드도 통과한 뒤 trim() 결과가 비어 Runtime.exec에서 죽는다. 루프에서 빈 줄을 건너뛰도록 했다. arrCmdLine.length == 0 분기도 함께 지웠다. 이 분기는 수정 전에는 도달 가능했지만 ("???"는 전부 구분자로 취급돼 빈 배열이 된다) 정규식을 고치면 구분자가 \r\n뿐이라 전부 구분자인 문자열은 위쪽 trim() 가드에서 이미 걸러진다. 분기 본문도 원소가 하나일 때의 루프와 결과가 같다. --- .../rte/bat/core/step/TaskletShellStep.java | 16 +++---- .../bat/core/step/TaskletShellStepTest.java | 44 +++++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/step/TaskletShellStepTest.java diff --git a/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/step/TaskletShellStep.java b/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/step/TaskletShellStep.java index 843278dd..bda252e7 100755 --- a/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/step/TaskletShellStep.java +++ b/Batch/org.egovframe.rte.bat.core/src/main/java/org/egovframe/rte/bat/core/step/TaskletShellStep.java @@ -37,21 +37,17 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon throw new UnexpectedJobExecutionException("Shell Script is Empty!"); } - String[] arrCmdLine = shellScript.split("[\\r?\\n]+"); + String[] arrCmdLine = shellScript.split("[\\r\\n]+"); int resultShellScript = 0; - if (arrCmdLine.length == 0) { // single line - resultShellScript = ShellScriptSupport.shellCmd(shellScript, encoding); + for (String s : arrCmdLine) { + if (s.trim().isEmpty()) { + continue; + } + resultShellScript = ShellScriptSupport.shellCmd(s, encoding); if (resultShellScript > 0) { throw new UnexpectedJobExecutionException("Error Executing shell script!"); } - } else { // multiline - for (String s : arrCmdLine) { - resultShellScript = ShellScriptSupport.shellCmd(s, encoding); - if (resultShellScript > 0) { - throw new UnexpectedJobExecutionException("Error Executing shell script!"); - } - } } return RepeatStatus.FINISHED; diff --git a/Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/step/TaskletShellStepTest.java b/Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/step/TaskletShellStepTest.java new file mode 100644 index 00000000..3de75963 --- /dev/null +++ b/Batch/org.egovframe.rte.bat.core/src/test/java/org/egovframe/rte/bat/core/step/TaskletShellStepTest.java @@ -0,0 +1,44 @@ +package org.egovframe.rte.bat.core.step; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.batch.repeat.RepeatStatus; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * TaskletShellStep의 셸 스크립트 줄 분리 테스트 + */ +public class TaskletShellStepTest { + + private TaskletShellStep step(String shellScript) { + TaskletShellStep step = new TaskletShellStep(); + step.setShellScript(shellScript); + step.setEncoding("UTF-8"); + return step; + } + + @Test + @DisplayName("물음표가 든 한 줄 명령은 한 번에 실행된다") + public void executeSingleLineContainingQuestionMark() throws Exception { + assertEquals(RepeatStatus.FINISHED, step("echo a?b").execute(null, null)); + } + + @Test + @DisplayName("줄바꿈으로 구분된 여러 줄은 줄마다 실행된다") + public void executeMultipleLines() throws Exception { + assertEquals(RepeatStatus.FINISHED, step("echo first\necho second").execute(null, null)); + } + + @Test + @DisplayName("스크립트가 개행으로 시작해도 빈 명령을 실행하지 않는다") + public void executeScriptStartingWithNewline() throws Exception { + assertEquals(RepeatStatus.FINISHED, step("\necho hello").execute(null, null)); + } + + @Test + @DisplayName("공백만 있는 줄은 건너뛴다") + public void executeScriptWithBlankLine() throws Exception { + assertEquals(RepeatStatus.FINISHED, step("echo first\n \necho second").execute(null, null)); + } +}