Computer >> 컴퓨터 >  >> 프로그래밍 >> Bash 프로그래밍

Bash 스크립트 템플릿 만들기: 체계적인 셸 스크립팅의 시작

이 시리즈의 첫 번째 글에서는 한 줄짜리 아주 간단한 Bash 스크립트를 만들어 보았고, 셸 스크립트를 만드는 이유와 컴파일된 프로그램보다 시스템 관리자에게 왜 가장 효율적인 선택인지 살펴보았습니다.

이 두 번째 글에서는 다른 Bash 스크립트의 출발점으로 활용할 수 있는 Bash 스크립트 템플릿을 만들기 시작합니다. 이 템플릿에는 최종적으로 도움말(Help) 기능, 라이선스 문구, 몇 가지 기본 함수, 그리고 이러한 옵션들을 처리하는 로직이 포함되며, 이 템플릿을 기반으로 만들어질 스크립트에 필요할 수 있는 다른 요소들도 담게 됩니다.

왜 템플릿을 만들어야 할까?

자동화 전반과 마찬가지로, 템플릿을 만드는 핵심 아이디어는 '게으른(=효율적인) 시스템 관리자'가 되는 것입니다. 템플릿에는 모든 스크립트에 공통으로 들어가길 원하는 기본 구성 요소들이 담겨 있습니다. 새 스크립트를 만들 때마다 매번 같은 요소들을 추가하는 것보다 시간이 절약되고, 새 스크립트 작업을 손쉽게 시작할 수 있게 해줍니다.

명령줄 Bash 문 몇 개를 파일에 대충 모아 실행 권한만 주는 것이 더 편해 보일 수 있지만, 장기적으로는 오히려 역효과를 낳습니다. 도움말 기능과 명령줄 옵션 처리 능력을 갖춘, 잘 작성되고 주석이 충실한 Bash 프로그램은 그 프로그램을 유지보수하는 시스템 관리자에게 훌륭한 출발점이 됩니다. 물론 여러분이 직접 작성하고 관리하는 프로그램도 그 대상에 포함됩니다.

요구 사항 정의

어떤 프로젝트든 항상 요구 사항 목록부터 만들어야 합니다. 스크립트도 예외가 아니며, 단 두세 항목으로 이루어진 간단한 목록이라도 좋습니다. 저는 요구 사항 명세가 없거나 제대로 작성되지 않아서 완전히 실패하거나 고객의 요구를 충족하지 못한 수많은 프로젝트를 경험했습니다.

이번 Bash 템플릿의 요구 사항은 꽤 단순합니다:

  1. 향후 Bash 프로그래밍 프로젝트의 출발점으로 사용할 수 있는 템플릿을 만든다.
  2. 템플릿은 표준 Bash 프로그래밍 관례를 따라야 한다.
  3. 다음 항목들을 반드시 포함해야 한다:
    • 프로그램의 기능 설명과 변경 이력(changelog)을 담을 수 있는 헤더 섹션
    • 라이선스 문구
    • 함수 섹션
    • 도움말(Help) 함수
    • 프로그램 사용자가 root인지 검사하는 함수
    • 명령줄 옵션을 평가하는 방법

기본 구조

기본적인 Bash 스크립트는 세 개의 섹션으로 이루어집니다. Bash에는 섹션을 구분 짓는 별도의 문법이 없지만, 섹션 사이의 경계는 암묵적으로 존재합니다.

  • 모든 스크립트는 반드시 셔뱅(#!)으로 시작해야 하며, 이것은 어떤 Bash 프로그램에서든 첫 번째 줄이어야 합니다.
  • 함수 섹션은 셔뱅 뒤에서 프로그램 본문(body) 앞까지 위치합니다. 모든 것을 문서화하려는 저의 습관상, 각 함수 앞에 그 함수가 무엇을 하는지 짧게 설명하는 주석을 붙입니다. 함수 내부에도 내용을 더 자세히 설명하는 주석을 넣습니다. 짧고 단순한 프로그램이라면 함수가 필요 없을 수도 있습니다.
  • 프로그램의 메인 부분은 함수 섹션 뒤에 옵니다. 단 한 줄의 Bash 문장일 수도 있고, 수천 줄의 코드일 수도 있습니다. 제 프로그램 중 하나는 주석을 제외하고 약 200줄 조금 넘는 코드인데, 같은 프로그램에 주석은 600줄이 넘습니다.

이것이 전부입니다. 어떤 Bash 프로그램이든 구조상 세 개의 섹션만 존재합니다.

헤더 주석

저는 여러 가지 이유로 여기에 더 많은 내용을 추가합니다. 먼저 셔뱅 바로 뒤에 두 개의 주석 섹션을 넣습니다. 이 주석 섹션들은 선택 사항이지만, 실제로 매우 유용하다고 생각합니다.

첫 번째 주석 섹션은 프로그램 이름과 설명, 변경 이력입니다. 저는 IBM 재직 당시 이 형식을 배웠는데, 프로그램의 장기적인 개발 과정과 적용된 수정 사항들을 기록하는 방법을 제공합니다. 프로그램 문서화의 중요한 출발점입니다.

두 번째 주석 섹션은 저작권 및 라이선스 문구입니다. 저는 GPLv2를 사용하며, GPLv2로 라이선싱된 프로그램의 표준 문구로 보입니다. 다른 오픈소스 라이선스를 사용해도 전혀 문제없지만, 라이선스에 대한 혼란을 없애기 위해 코드에 명시적인 문구를 추가할 것을 권장합니다. Scott Peterson의 글 The source code is the license(소스 코드가 곧 라이선스다)이 그 근거를 잘 설명해 줍니다.

그럼 지금까지의 스크립트는 다음과 같습니다:

#!/bin/bash
################################################################################
# scriptTemplate #
# #
# Use this template as the beginning of a new program. Place a short #
# description of the script here. #
# #
# Change History #
# 11/11/2019 David Both Original code. This is a template for creating #
# new Bash shell scripts. #
# Add new history entries as needed. #
# #
################################################################################
################################################################################
# #
# Copyright (C) 2007, 2019 David Both #
# LinuxGeek46@both.org #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 2 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
################################################################################

echo "hello world!"

수정된 프로그램을 실행해서 여전히 의도대로 동작하는지 확인해 보세요.

테스트에 관하여

테스트에 대해 이야기하기 좋은 타이밍입니다.

"버그는 언제나 하나 더 있다."

— 루바르스키(Lubarsky)의 사이버네틱 곤충학 법칙

루바르스키가 누구든 간에, 그 말은 맞습니다. 코드의 모든 버그를 찾는 일은 불가능합니다. 하나의 버그를 찾으면 또 다른 버그가 나타나곤 하고, 보통은 가장 곤란한 순간에 나타납니다.

테스트는 단순히 프로그램에 국한되지 않습니다. 하드웨어, 소프트웨어, 혹은 사용자들이 발견하는 끝없는 문제 유발 방식 등으로 인해 발생한 문제들이 실제로 해결되었는지 검증하는 것이기도 합니다. 그만큼 중요하게, 테스트는 코드가 사용하기 쉽고 인터페이스가 사용자에게 직관적인지 확인하는 과정이기도 합니다.

셸 스크립트를 작성하고 테스트할 때 잘 정의된 프로세스를 따르면 일관성 있고 품질 높은 결과물을 얻을 수 있습니다. 제 프로세스는 단순합니다:

  1. 간단한 테스트 계획을 세운다.
  2. 개발 초반부터 바로 테스트를 시작한다.
  3. 코드가 완성되면 최종 테스트를 수행한다.
  4. 운영 환경으로 옮긴 후에도 계속 테스트한다.

테스트 계획

테스트 계획에는 다양한 형식이 있습니다. 저는 머릿속에만 담아두는 수준부터, 종이 한 장에 적어둔 몇 줄의 메모, 그리고 각 테스트에 대한 상세 설명, 테스트 대상 기능 코드, 테스트 목표, 입력값과 기대 결과까지 요구하는 복잡한 서식 세트에 이르기까지 모든 범위를 경험해 보았습니다.

테스터였던(지금은 아닙니다) 시스템 관리자로서 말씀드리면, 저는 중간 지점을 택하려고 노력합니다. 짧은 서면 테스트 계획이라도 있으면 테스트를 반복할 때마다 일관성을 유지할 수 있습니다. 어느 정도의 디테일이 필요한지는 개발 및 테스트 업무가 얼마나 격식을 차리는지에 따라 달라집니다.

Google에서 찾은 샘플 테스트 계획 문서들은 대부분 복잡했고, 매우 격식 있는 개발·테스트 프로세스를 갖춘 대형 조직을 위한 것이었습니다. 직함에 '테스트'가 들어가는 분들에게는 좋겠지만, 시스템 관리자의 좀 더 즉흥적이고 시간에 쫓기는 업무 환경에는 잘 맞지 않습니다. 이 업종의 다른 모든 면에서처럼, 시스템 관리자는 창의력을 발휘해야 합니다. 따라서 테스트 계획에 포함할 만한 항목들을 간단히 정리해 보았습니다. 필요에 맞게 수정하세요:

  • 테스트 대상 소프트웨어의 이름과 간단한 설명
  • 테스트할 소프트웨어 기능에 대한 설명
  • 각 테스트의 시작 조건
  • 각 테스트에서 따를 절차
  • 각 테스트의 기대 결과에 대한 설명
  • 부정(negative) 결과를 검증하기 위해 설계된 특정 테스트
  • 프로그램이 예상치 못한 입력을 처리하는 방식에 대한 테스트
  • 각 테스트의 통과/실패 판정 기준에 대한 명확한 설명
  • 퍼지(fuzzy) 테스트 — 아래에서 설명

이 목록이 여러분의 테스트 계획을 세우는 데 아이디어를 줄 것입니다. 대부분의 시스템 관리자는 단순하고 비교적 비격식적인 수준을 유지하는 것이 좋습니다.

일찍 테스트하고, 자주 테스트하라

저는 실행 가능한 첫 부분이 완성되는 즉시 셸 스크립트 테스트를 시작합니다. 짧은 명령줄 프로그램을 작성할 때든, 실행 파일 형태의 스크립트를 작성할 때든 마찬가지입니다.

새 프로그램을 만들 때는 보통 셸 스크립트 템플릿에서 시작합니다. 도움말(Help) 함수 코드를 작성하고 테스트합니다. 이것은 대개 지극히 간단한 단계지만, 작업을 시작하는 데 도움이 되고 템플릿의 요소들이 처음부터 제대로 동작하는지 확인해 줍니다. 이 시점에서는 스크립트의 템플릿 부분에 생긴 문제를 고치거나, 표준 템플릿이 충족하지 못하는 요구 사항에 맞게 수정하기가 쉽습니다.

템플릿과 도움말 함수가 정상 동작하면, 프로그램 명세를 충족하는 데 필요한 프로그래밍 단계를 문서화하는 주석을 추가하면서 프로그램 본문 작성으로 넘어갑니다. 그런 다음 각 주석에 명시된 요구 사항을 충족하는 코드를 추가하기 시작합니다. 이 코드에는 템플릿의 해당 섹션에서 초기화되는 변수들을 추가해야 할 가능성이 높습니다. 이제 템플릿이 점점 하나의 셸 스크립트로 변해가는 것입니다.

여기서 테스트는 단순히 데이터를 입력하고 결과를 확인하는 것 이상입니다. 약간의 추가 작업이 필요합니다. 때로는 방금 작성한 코드의 중간 결과를 출력하는 명령을 추가해서 그 값을 검증하기도 합니다. 더 복잡한 스크립트의 경우 '테스트 모드'를 의미하는 -t 옵션을 추가합니다. 이 경우 내부 테스트 코드는 명령줄에서 -t 옵션이 입력될 때만 실행됩니다.

최종 테스트

코드가 완성되면 알려진 입력값으로 특정 출력을 만들어내는지 확인하며 모든 기능과 함수를 전체 테스트합니다. 또한 무작위 입력 몇 가지를 던져 프로그램이 예상치 못한 입력을 처리할 수 있는지 확인합니다.

최종 테스트는 프로그램이 의도한 대로 작동하는지 검증하는 것이 목적입니다. 최종 테스트의 큰 부분은 개발 초기에 정상 동작했던 함수들이 개발 후반부에 추가되거나 변경된 코드 때문에 깨지지 않았는지 확인하는 것입니다.

새 코드를 추가할 때마다 테스트를 해왔다면 최종 테스트에서 놀랄 일이 없을 거라고 생각할 수 있습니다. 틀렸습니다! 최종 테스트에서는 언제나 놀라운 일이 일어납니다. 항상 그렇습니다. 그 놀라움을 예상하고, 수정에 시간을 쓸 준비를 하세요. 최종 테스트에서 버그가 한 번도 발견되지 않는다면 굳이 최종 테스트를 할 이유가 없겠지요?

운영 환경에서의 테스트

네? 뭐라고요?

"프로그램이 운영 환경에서 최소 6개월간 돌아가기 전까지는 가장 치명적인 오류가 발견되지 않는다."

— 트라우트먼(Troutman)의 프로그래밍 공리

네, 운영 환경에서의 테스트는 이제 정상적이고 바람직한 것으로 간주됩니다. 제가 테스터였던 경험으로 볼 때도 합리적으로 들립니다. "잠깐만요! 그건 위험하지 않나요?"라고 하실 수 있습니다. 제 경험상, 그것은 전용 테스트 환경에서 진행하는 광범위하고 엄격한 테스트보다 더 위험하지 않습니다. 경우에 따라서는 선택의 여지가 없습니다. 테스트 환경이 없고 운영 환경만 존재하기 때문입니다.

시스템 관리자에게 운영 환경에서 신규 또는 수정된 스크립트를 테스트해야 하는 일은 낯설지 않습니다. 스크립트가 운영 환경에 배포되는 순간, 그것이 곧 최종 테스트가 됩니다. 운영 환경은 그 테스트의 가장 중요한 부분을 차지합니다. 테스터가 테스트 환경에서 아무리 고안해 낸다 해도 실제 운영 환경을 완벽히 재현할 수는 없습니다.

'운영 환경 테스트'라는 새로운 유행처럼 여겨지는 것은 사실 시스템 관리자들이 늘 알고 있던 사실을 인정한 것에 불과합니다. 최고의 테스트는 운영 환경입니다 — 단, 그것이 유일한 테스트가 되지 않는 한 말입니다.

퍼지(Fuzzy) 테스트

처음 듣고 눈을 치켜세웠던 유행어 중 하나입니다. 본질적인 의미는 단순합니다. 누군가 키보드를 마구 두드려서 무슨 일이 일어나게 만들고, 프로그램이 그걸 얼마나 잘 처리하는지 보는 것입니다. 하지만 실제로는 그 이상의 의미가 있습니다.

퍼지 테스트는 제 아들이 무작위 입력으로 게임 코드를 1분도 안 되게 깨뜨렸던 때를 연상시킵니다. 그 일로 아들을 위한 게임을 만들려는 시도는 끝이 났지요.

대부분의 테스트 계획은 특정 결과나 출력을 생성하는 매우 구체적인 입력을 사용합니다. 테스트가 긍정적 결과를 성공으로 정의하든 부정적 결과를 성공으로 정의하든, 여전히 통제된 상태이며 입력과 결과는 명시되고 예상된 것입니다. 특정 실패 모드에 대한 특정 오류 메시지 같은 것들이지요.

퍼지 테스트는 테스트의 모든 측면에서 무작위성을 다루는 것입니다. 시작 조건, 매우 무작위적이고 예상 밖의 입력, 무작위로 조합된 옵션 선택, 낮은 메모리 상황, 다른 프로그램들과의 높은 CPU 경합, 테스트 대상 프로그램의 다중 인스턴스 실행, 그리고 테스트에 적용할 수 있다고 생각되는 그 밖의 모든 무작위 조건들이 해당됩니다.

저는 처음부터 어느 정도의 퍼지 테스트를 시도합니다. Bash 스크립트가 초기 단계에서 상당한 무작위성을 처리하지 못한다면, 코드를 더 추가해도 나아지지 않을 가능성이 높습니다. 코드가 아직 비교적 단순할 때 이런 문제를 잡아 고치기 좋은 시기입니다. 각 단계마다 약간의 퍼지 테스트를 수행하면, 더 많은 코드가 쌓여 문제가 가려지기 전에 미리 발견하는 데도 도움이 됩니다.

코드가 완성된 후에는 좀 더 폭넓은 퍼지 테스트를 하는 것을 좋아합니다. 항상 어느 정도의 퍼지 테스트를 하세요. 저 역시 그 결과에 여러 번 놀랐습니다. 예상되는 것들을 테스트하는 건 쉽지만, 사용자들은 스크립트를 우리가 예상하는 방식으로 사용하지 않는 법입니다.

다음 편 예고

이 글에서는 템플릿 생성 작업을 조금 진행했지만, 사실 대부분은 테스트에 관한 이야기를 했습니다. 테스트는 어떤 종류의 프로그램을 만들든 필수적인 부분이기 때문입니다. 이 시리즈의 다음 글에서는 -h 같은 옵션을 감지하고 처리하는 코드와 함께 기본적인 도움말(Help) 함수를 Bash 스크립트 템플릿에 추가하게 됩니다.

참고 자료

  • Bash로 프로그래밍하기: 문법과 도구
  • Bash로 프로그래밍하기: 논리 연산자와 셸 확장
  • Bash로 프로그래밍하기: 반복문

이 시리즈의 글은 David Both의 3부작 Linux 자습 과정 'Using and Administering Linux—Zero to SysAdmin' 2권 10장의 내용을 일부 기반으로 작성되었습니다.