유닉스 초기의 비전 중 하나는 일반 사용자가 자신만의 작업 방식에 맞게 컴퓨터를 세밀하게 조정할 수 있도록 돕는 것이었습니다. 그러나 수십 년이 흐르는 동안 컴퓨터 커스터마이징에 대한 기대는 줄어들었고, 많은 사용자는 앱과 웹사이트 모음을 곧 "나만의 환경"으로 여기게 되었습니다. 그 이유 중 하나는 대다수 운영체제의 구성 요소가 공개되어 있지 않아 일반 사용자가 소스 코드에 접근할 수 없기 때문입니다.
하지만 리눅스 사용자에게는 커스텀 프로그램이 훨씬 가깝습니다. 시스템 전체가 터미널을 통해 사용할 수 있는 명령어를 기반으로 구축되어 있기 때문입니다. 터미널은 단순히 빠른 명령 입력이나 깊이 있는 문제 해결을 위한 인터페이스가 아니라, 지루한 반복 작업을 대신 처리해 업무 부담을 덜어주는 스크립팅 환경이기도 합니다.
프로그래밍 학습 방법
프로그래밍이 처음이라면 두 가지 과제로 나누어 생각하면 좋습니다. 하나는 코드를 어떻게 작성하는지 이해하는 것이고, 다른 하나는 어떤 코드를 작성해야 하는지 이해하는 것입니다. 문법(syntax)은 배울 수 있지만, 해당 언어에서 어떤 단어들을 사용할 수 있는지 모른다면 큰 진전을 이루기 어렵습니다. 실제로는 두 개념을 동시에 배우게 됩니다. 배열할 단어 없이는 문법을 익힐 수 없기 때문입니다. 처음에는 기본 명령어와 기본 프로그래밍 구조로 간단한 작업부터 시작하고, 기본기에 익숙해지면 언어의 더 많은 기능을 탐색하며 점점 더 의미 있는 프로그램을 만들 수 있습니다.
Bash에서 사용하는 대부분의 단어는 리눅스 명령어이고, 문법은 Bash 자체입니다. 이미 Bash를 자주 사용한다면 Bash 프로그래밍으로의 전환은 비교적 수월합니다. Bash를 사용해 본 적이 없더라도 걱정하지 마세요. Bash는 명확성과 단순함을 위해 설계된 간결한 언어입니다.
대화형 설계의 힘
프로그래밍을 배울 때 가장 어려운 부분 중 하나는 컴퓨터가 무엇을 도와줄 수 있는지 파악하는 일입니다. 물론 컴퓨터가 스스로 모든 일을 처리할 수 있다면 다시는 컴퓨터를 만질 필요가 없겠지만, 현실적으로 사람의 역할은 여전히 중요합니다. 컴퓨터가 도와줄 수 있는 작업을 찾는 핵심은 한 주 동안 반복해서 수행하는 작업에 주목하는 것입니다. 컴퓨터는 반복 작업을 특히 잘 처리합니다.
그리고 컴퓨터에게 무언가를 시키려면 먼저 그 방법을 스스로 알아야 합니다. 바로 이 지점에서 Bash가 빛을 발합니다. 대화형(interactive) 프로그래밍입니다. 터미널에서 작업을 수행하는 과정 자체가 곧 그 작업을 스크립트로 만드는 법을 배우는 과정이 됩니다.
실제 사례를 들어 보겠습니다. 저는 한때 수많은 PDF 전자책을 잉크 소모가 적고 인쇄에 최적화된 버전으로 변환하는 임무를 맡았습니다. 한 가지 방법은 PDF 편집기에서 파일을 열고 수백 개의 이미지(페이지 배경과 질감까지 모두 이미지로 계산됩니다)를 하나씩 선택해 삭제한 뒤 새 PDF로 저장하는 것입니다. 이 방식으로는 책 한 권을 처리하는 데만 반나절이 걸립니다.
처음에는 PDF 편집기를 스크립트로 제어하는 방법을 찾았지만, 며칠간 조사한 끝에도 스크립트로 자동화할 수 있는 PDF 편집 애플리케이션은 찾지 못했습니다(지저분한 마우스 자동화 트릭은 제외하고요). 그래서 터미널 안에서 이 작업을 해결하는 방법에 주목했고, 그 결과 GhostScript(PDF의 기반이 되는 프린터 언어 PostScript의 오픈소스 구현체)를 비롯한 여러 새로운 도구를 발견하게 되었습니다. 며칠간 GhostScript로 작업을 진행한 결과, 이것이 제 문제에 대한 해결책임을 확인했습니다.
명령어를 실행하는 기본 스크립트를 만드는 일은 PDF에서 이미지를 제거할 때 사용했던 명령어와 옵션을 복사해 텍스트 파일에 붙여넣는 수준의 간단한 작업이었습니다. 이 파일을 스크립트로 실행하면 동일한 결과를 얻을 수 있습니다.
Bash 스크립트에 인수 전달하기
터미널에서 명령어를 실행하는 것과 셸 스크립트에서 명령어를 실행하는 것의 차이는 전자가 대화형이라는 점입니다. 터미널에서는 진행 상황에 따라 즉석에서 조정할 수 있습니다. 예를 들어 example_1.pdf를 처리한 뒤 다음 문서를 처리할 때는 명령어에서 파일 이름만 바꾸면 됩니다.
반면 셸 스크립트는 대화형이 아닙니다. 사실 셸 스크립트가 존재하는 이유 자체가 사용자가 일일이 개입하지 않아도 되도록 하기 위함입니다. 바로 이 때문에 명령어(그리고 이를 실행하는 셸 스크립트)는 인수(argument)를 받습니다.
셸 스크립트에는 스크립트가 시작되는 방식을 반영하는 몇 가지 사전 정의 변수가 있습니다. 첫 번째 변수는 $0으로, 스크립트를 실행하기 위해 입력한 명령어를 나타냅니다. 다음 변수인 $1은 셸 스크립트에 전달된 첫 번째 "인수"를 나타냅니다. 예를 들어 echo hello라는 명령어에서 echo는 $0, hello는 $1입니다. echo hello world라면 echo가 $0, hello가 $1, world가 $2가 됩니다.
대화형 셸에서는 다음과 같이 동작합니다:
$ echo hello world
hello world
대화형이 아닌 셸 스크립트에서도 문자 그대로 같은 결과를 낼 수 있습니다. 아래 텍스트를 텍스트 파일에 입력하고 hello.sh로 저장하세요:
echo hello world
이제 스크립트를 실행합니다:
$ bash hello.sh
hello world
동작은 하지만, 스크립트가 입력을 받을 수 있다는 장점은 활용하지 못합니다. hello.sh를 다음과 같이 수정해 보세요:
echo $1
따옴표로 묶어 하나의 인수로 전달하며 스크립트를 실행합니다:
$ bash hello.sh "hello bash"
hello bash
PDF 경량화 프로젝트에서 저는 이런 비대화형 방식이 실제로 필요했습니다. 각 PDF를 압축하는 데 몇 분씩 걸렸기 때문입니다. 하지만 입력을 받는 스크립트를 만들었기 때문에 여러 PDF 파일을 한꺼번에 넘겨줄 수 있었습니다. 스크립트가 각 파일을 순차적으로 처리하는 데 30분 이상 걸릴 수도 있었지만, 그 30분은 다른 작업에 활용할 수 있는 귀중한 시간이었습니다.
흐름 제어로 스크립트를 똑똑하게
반복해야 할 작업을 수행했던 과정을 그대로 옮긴 트랜스크립트 형태의 Bash 스크립트를 만드는 것도 전혀 문제없습니다. 하지만 정보가 스크립트를 통과하는 흐름을 제어하면 훨씬 더 강력한 스크립트를 만들 수 있습니다. 스크립트가 데이터에 대응하는 방식을 관리하는 대표적인 방법은 다음과 같습니다:
- if/then 문
- for 루프
- while 루프
- case 문
컴퓨터는 지능적이지 않지만 데이터를 비교하고 분석하는 데는 능숙합니다. 스크립트에 데이터 판별 로직을 추가하면 훨씬 똑똑해 보이게 만들 수 있습니다. 예를 들어 기본 hello.sh 스크립트는 출력할 내용이 있든 없든 무조건 실행됩니다:
$ bash hello.sh foo
foo
$ bash hello.sh
$
입력이 없을 때 도움말 메시지를 보여주는 편이 훨씬 사용자 친화적일 것입니다. 이것이 바로 if/then 문입니다. Bash를 기본적인 용도로만 사용해 왔다면 이런 문법이 존재한다는 사실을 몰랐을 수도 있습니다. 하지만 프로그래밍의 일부는 언어를 배우는 것이고, 조금만 검색해 보면 if/then 문의 사용법을 금방 익힐 수 있습니다:
if [ "$1" = "" ]; then
echo "syntax: $0 WORD"
echo "If you provide more than one word, enclose them in quotes."
else
echo "$1"
fi
새 버전의 hello.sh를 실행하면 다음과 같은 결과를 얻습니다:
$ bash hello.sh
syntax: hello.sh WORD
If you provide more than one word, enclose them in quotes.
$ bash hello.sh "hello world"
hello world
스크립트 완성도를 높여 가는 과정
PDF 파일에서 이미지를 제거하는 도구든, 어수선한 다운로드 폴더를 정리하는 도구든, Kubernetes 이미지를 생성하고 프로비저닝하는 도구든, Bash 스크립팅을 배우는 것은 Bash를 꾸준히 사용해 보고, 단순한 명령어 나열에 불과했던 스크립트를 입력에 반응하는 프로그램으로 발전시키는 방법을 익히는 과정입니다. 대개는 발견의 연속입니다. 텍스트 명령어로 가능하리라고 상상도 못했던 작업을 처리하는 새로운 리눅스 명령어를 만나게 되고, 다양한 상황에 맞게 스크립트가 유연하게 동작하도록 해주는 Bash의 숨은 기능들을 하나씩 찾게 됩니다.
이런 노하우를 익히는 한 가지 방법은 다른 사람들이 작성한 스크립트를 읽는 것입니다. 사람들이 자신의 시스템에서 반복적인 명령어를 어떻게 자동화하는지 감을 잡아 보세요. 익숙한 부분과 낯선 부분을 구분하고, 낯선 요소에 대해 추가로 학습하면 됩니다.
또 다른 방법은 Bash 프로그래밍 입문 eBook을 내려받는 것입니다. 이 책은 Bash에 특화된 프로그래밍 개념을 소개하며, 배운 구성 요소를 바탕으로 자신만의 명령어를 만들기 시작할 수 있습니다. 무료로 다운로드할 수 있고 Creative Commons 라이선스로 제공되니 지금 바로 받아 보세요.