절대로 실패하지 않게 만들기 - 셸 명령이 항상 성공하도록 설정하는 법
Snakemake는 유닉스(UNIX) 종료 코드(exit codes)를 사용하여 셸 명령의 성공 여부를 판단합니다. 종료 코드는 프로그램이 실행을 마치고 반환하는 숫자 값입니다. 값 0은 성공을 나타내며, 0이 아닌 모든 값은 오류를 의미합니다.
유닉스 종료 코드란 무엇이며 어떻게 해석해야 할까요?
유닉스 “종료 코드” 또는 “종료 상태”는 하위 프로세스가 종료될 때 호출한 프로그램(셸 또는 워크플로 프로그램)에게 반환하는 단일 숫자입니다. 이는 하위 프로그램의 실행 성공 또는 실패를 알리는 기본적인 방식입니다.
일반적으로 종료 코드 0은 성공을 의미합니다. 이는 리눅스(Linux) 및 맥OS(Mac OS X)와 같은 POSIX 시스템에서 항상 적용되는 규칙입니다. 또한 많은 프로그램의 기반이 되는 GNU libc 라이브러리에 의해 표준화되어 있습니다.
유닉스의 bash 셸에서는 이전 명령의 종료 상태가 $? 변수에 저장되며, 다음과 같이 확인할 수 있습니다.
$ if [ $? -eq 0 ] ...또는 &&를 사용하여 첫 번째 명령이 “성공”(코드 0으로 종료)했을 때만 두 번째 명령을 실행할 수 있습니다.
$ program && echo success반대로 ||를 사용하면 첫 번째 명령이 실패(0이 아닌 코드로 종료)했을 때만 두 번째 명령을 실행합니다.
$ program || echo failed왜 0이 성공을 나타낼까요? 정확한 기원을 찾기는 어렵지만, 0이 오류가 없음을 나타내는 가장 특별하고 눈에 띄는 단일 값이기 때문인 것으로 추측됩니다.
더 자세한 내용은 종료 상태에 대한 위키백과 항목 및 GNU libc 매뉴얼 섹션을 참조하십시오.
때로는 셸 명령어가 구조적인 이유로 인해 “실패”해야만 하는 경우가 있습니다. 예를 들어, 파이프(|)를 사용하여 명령의 출력을 자르는 경우, 유닉스는 파이프를 받는 쪽이 입력을 중단하면 보내는 쪽의 명령도 중단시킵니다. 압축된 파일에서 처음 1,000,000줄만 추출하는 다음 명령을 살펴보세요.
gunzip -c large_file.gz | head -1000000 만약 large_file.gz가 100만 줄보다 많다면, head가 100만 줄 이후 입력을 중단하기 때문에 gunzip이 파이프에 데이터를 더 이상 쓸 수 없게 되어 “실패”로 처리될 수 있습니다.
또 다른 상황으로는 여러분이 제어할 수 없는 외부 스크립트나 프로그램이 어떤 이유에서인지 종료 상태 코드 0을 반환하지 않는 경우가 있습니다.
이럴 때 shell: 블록의 명령이 항상 성공하도록 보장하려면 다음과 같이 작성하면 됩니다.
shell command || true이 코드는 shell command를 먼저 실행하고, 만약 종료 코드가 0이 아니라면(실패), 항상 종료 코드 0(성공)을 반환하는 true 명령을 실행합니다.
하지만 주의해야 할 점이 있습니다. 파일이 실제로 생성되지 않았거나 심각한 오류가 발생했더라도 Snakemake는 이를 성공으로 간주하고 다음 단계로 넘어가 버릴 수 있습니다. 따라서 이 방식은 오류 메시지를 직접 확인해야 하는 번거로움이 따르지만, 특정 상황에서는 꼭 필요한 기술이기도 합니다.
다음은 존재하지 않는 스크립트를 실행하려고 시도하지만, || true를 사용하여 전체 셸 블록이 성공하도록 만드는 간단한 Snakemake 예시입니다.
rule always_succeed:
shell: """
./does-not-exist.sh || true
"""(이 예시는 존재하지 않는 명령을 성공으로 처리하는 위험성도 동시에 보여줍니다. 원래는 당연히 실패해야 하는 상황이기 때문입니다!)