모든 직업에는 그 분야의 전문가들이 가장 자주 사용하는 도구가 있습니다. 많은 시스템 관리자에게 그 도구는 바로 셸(shell)입니다. 대부분의 Linux 및 기타 Unix 계열 시스템에서 기본 셸로 사용되는 것이 바로 Bash입니다.
Bash는 1980년대 후반에 탄생한 꽤 오래된 프로그램이지만, 그보다 10년 이상 앞선 C 셸(csh)과 같은 훨씬 오래된 셸들의 전통 위에 세워졌습니다. 셸이라는 개념 자체가 워낙 오래되었기 때문에, 시스템 관리자의 업무를 한결 수월하게 만들어 줄 방대한 노하우가 곳곳에 숨어 있습니다.
지금부터 기본적인 내용들을 하나씩 살펴보겠습니다.
실수로 root 명령어를 실행하지 않도록 예방하기
한 번쯤은 의도치 않게 root 권한으로 명령어를 실행해 문제를 일으킨 적이 있으신가요? (손들기)
아마 우리 대부분은 한 번쯤은 그런 경험이 있을 겁니다. 정말 아찔한 순간이죠. 같은 실수를 반복하지 않도록 도와주는 아주 간단한 요령 몇 가지를 소개합니다.
별칭(alias) 활용하기
먼저 mv와 rm 같은 명령어에 mv -i와 rm -i를 가리키는 별칭을 설정하세요. 이렇게 하면 rm -f /boot를 실행하더라도 최소한 확인 절차를 거치게 됩니다. Red Hat Enterprise Linux에서는 root 계정 사용 시 이러한 별칭이 기본적으로 설정되어 있습니다.
일반 사용자 계정에도 동일한 별칭을 적용하고 싶다면, 홈 디렉터리의 .bashrc 파일에 다음 두 줄만 추가하면 됩니다(sudo 환경에서도 작동합니다):
alias mv='mv -i'
alias rm='rm -i'
root 프롬프트를 눈에 띄게 만들기
실수를 방지하는 또 다른 방법은 root 계정을 사용 중일 때 항상 인식할 수 있도록 하는 것입니다. 저는 평소 일상 업무용 프롬프트와 확연히 구분되도록 root 프롬프트 스타일을 다르게 지정해 둡니다.
root 홈 디렉터리의 .bashrc 파일에 다음 내용을 추가하면 검정 배경에 빨간 글씨로 표시되는 root 프롬프트를 얻을 수 있습니다. 덕분에 자신이든 다른 사람이든 신중하게 행동해야 한다는 사실이 한눈에 들어옵니다.
export PS1="\[$(tput bold)$(tput setab 0)$(tput setaf 1)\]\u@\h:\w # \[$(tput sgr0)\]"사실 가능한 한 root로 로그인하는 것을 삼가고, 대부분의 시스템 관리 명령어는 sudo를 통해 실행하는 것이 좋습니다. 하지만 그 이야기는 별도의 주제로 남겨두겠습니다.
root 계정 사용으로 인한 '의도치 않은 부작용'을 막는 몇 가지 소소한 요령을 살펴봤으니, 이제 일상 업무에서 Bash가 도와줄 수 있는 유용한 기능들을 알아보겠습니다.
명령어 히스토리 제어하기
Bash에서 위쪽 화살표 키를 누르면 이전에 실행한 명령어들을 확인하고 재사용할 수 있다는 것은 잘 알고 계실 겁니다. 그 이유는 해당 명령어들이 홈 디렉터리의 .bash_history 파일에 저장되기 때문입니다. 이 히스토리 파일에는 매우 유용하게 쓸 수 있는 다양한 설정과 명령어가 함께 제공됩니다.
먼저 history를 입력하면 최근 명령어 전체 목록을 확인할 수 있고, history 30처럼 입력하면 마지막 30개 명령어만 볼 수 있습니다. 하지만 이 정도는 기본에 불과합니다. Bash가 무엇을 어떻게 저장할지는 직접 제어할 수 있습니다.
예를 들어 .bashrc에 다음 내용을 추가하면 공백으로 시작하는 명령어는 히스토리에 저장되지 않습니다:
HISTCONTROL=ignorespace명령어에 비밀번호를 평문으로 전달해야 하는 경우에 유용합니다. (물론 좋은 방법은 아니지만, 여전히 현실에서 일어나는 일이죠.)
자주 실행하는 특정 명령어가 히스토리에 나타나지 않길 원한다면 다음을 사용하세요:
HISTCONTROL=ignorespace:erasedups이렇게 설정하면 명령어를 실행할 때마다 이전에 실행했던 동일한 명령어가 히스토리 파일에서 모두 삭제되고, 가장 마지막 실행 기록만 저장됩니다.
특히 마음에 드는 설정은 HISTTIMEFORMAT입니다. 이 설정을 사용하면 히스토리의 모든 항목 앞에 타임스탬프가 붙습니다. 예를 들어 저는 다음과 같이 설정합니다:
HISTTIMEFORMAT="%F %T "이후 history 5를 입력하면 아래처럼 깔끔하고 완전한 정보를 얻을 수 있습니다:
1009 2018-06-11 22:34:38 cat /etc/hosts
1010 2018-06-11 22:34:40 echo $foo
1011 2018-06-11 22:34:42 echo $bar
1012 2018-06-11 22:34:44 ssh myhost
1013 2018-06-11 22:34:55 vim .bashrc
덕분에 명령어 히스토리를 훨씬 쉽게 훑어볼 수 있고, 이틀 전에 집에 있는 실험용 서버로 SSH 터널을 구성할 때 사용했던 명령어(매번 잊어버리는…)도 금방 찾을 수 있습니다.
Bash 스크립트 작성 모범 사례 11가지
마지막으로 Bash 스크립트를 작성할 때 지키면 좋은 모범 사례 11가지를 정리하며 글을 마무리하겠습니다. (절대적 진리라고 주장하지는 않겠습니다.)
- 주석은 아까운 게 아닙니다. Bash 스크립트는 복잡해질 수 있습니다. 주석을 달지 고민된다면 일단 주석을 추가하세요. 주말이 지나고 돌아왔을 때 지난주 금요일에 무엇을 하려던 건지 파악하는 데 시간을 쓰고 있다면, 주석을 넣는 걸 잊었던 것입니다.
- 변수 이름은 항상 중괄호로 감싸세요.
${myvariable}처럼 말입니다. 이 습관을 들이면${variable}_suffix같은 표현도 가능해지고, 스크립트 전체의 일관성도 향상됩니다.
- 백틱 대신
$()문법을 사용하세요. 표현식을 평가할 때 백틱을 쓰지 말고$()문법을 사용하세요.for file in $(ls); do이렇게 하세요. 아래처럼 하지 마세요:
for file in `ls`; do전자는 중첩이 가능하고 가독성이 좋으며, 시스템 관리자 커뮤니티 전반의 기대에도 부합합니다. 백틱은 사용하지 마세요.
- 일관성을 유지하세요. 하나의 스타일을 정했다면 스크립트 전체에서 끝까지 지키세요. 물론 저는 백틱보다
$()문법을, 변수는 중괄호로 감싸는 방식을 선호합니다. 들여쓰기도 탭이 아닌 공백 2~4칸을 권장하지만, 설령 '잘못된' 방식을 선택하더라도 그 방식을 일관되게 유지하는 것이 중요합니다.
- 적절한 shebang을 사용하세요. Bash로만 실행할 의도로 스크립트를 작성한다면
#!/usr/bin/bash를 shebang으로 쓰는 것이 좋습니다.#!/bin/sh나#!/usr/bin/sh는 사용하지 마세요. 스크립트는 실행되지만 호환성 모드로 동작하면서 예상치 못한 부작용이 생길 수 있습니다. (호환성 모드가 목적이라면 예외입니다.)
- 문자열 비교 시 변수를 따옴표로 감싸세요. if문에서 문자열을 비교할 때는 변수를 따옴표로 묶는 것이 좋습니다. 변수가 비어 있으면 아래 코드는 Bash 오류를 발생시킵니다:
if [ ${myvar} == "foo" ]; then
echo "bar"
fi반면 아래처럼 따옴표로 감싸면 false로 평가됩니다:
if [ "${myvar}" == "foo" ]; then
echo "bar"
fi또한 변수 내용을 확신할 수 없을 때(예: 사용자 입력을 파싱할 때)는 변수를 따옴표로 감싸 특수문자 해석을 방지하고, 공백이 포함되어 있더라도 변수가 하나의 단어로 취급되도록 해야 합니다.
- 문자열 비교에도 이중 등호(
==)를 사용하세요. 취향의 문제이긴 하지만, 저는 Bash에서 문자열을 비교할 때도 이중 등호(==)를 선호합니다. 단일 등호도 문자열 비교에는 동작하지만, 저는 등호 하나를 보면 즉시 '단일 등호는 대입 연산자!'라는 생각이 들기 때문입니다. 역시 일관성의 문제죠.
- 적절한 종료 코드(exit code)를 사용하세요. 스크립트가 작업에 실패했다면 사용자에게 실패 메시지를 출력하고(가능하면 해결 방법도 함께), 0이 아닌 종료 코드를 반환하세요:
# we have failed
echo "Process has failed to complete, you need to manually restart the whatchamacallit"
exit 1이렇게 하면 다른 스크립트에서 이 스크립트를 프로그래밍 방식으로 호출하고 성공 여부를 검증하기가 훨씬 쉬워집니다.
- Bash 내장 기능으로 기본값과 오류 처리를 하세요. 변수에 합리적인 기본값을 설정하거나, 정의되어야 할 변수가 정의되지 않았을 때 오류를 발생시키려면 Bash의 내장 메커니즘을 활용하세요:
# $myvar 값을 redhat으로 설정하고 'redhat'을 출력
echo ${myvar:=redhat}# $myvar가 정의되지 않았다면 'The variable myvar is undefined, dear reader' 오류 발생
${myvar:?The variable myvar is undefined, dear reader}
- 함수 내 변수에는
local키워드를 사용하세요. 특히 규모가 큰 스크립트를 작성하거나 여러 사람과 함께 작업할 때는 함수 안에서 변수를 정의할 때local키워드를 사용하는 것을 고려하세요.local키워드는 해당 함수 안에서만 보이는 지역 변수를 생성하여, 변수 이름 충돌 가능성을 크게 줄여줍니다.
- 스크립트의 한 줄을 너무 길게 작성하지 마세요. 시스템 관리자라면 때때로 콘솔에서 디버깅을 해야 합니다. 데이터 센터의 실제 콘솔이든, 가상화 플랫폼을 통한 가상 콘솔이든 말이죠. 그런 환경에서 스크립트를 디버깅해야 한다면 이 조언에 감사하게 될 겁니다. 많은 시스템에서 콘솔의 기본 너비는 여전히 80자입니다. 아주 긴 줄로 된 스크립트를 콘솔에서 디버깅해야 한다면 고통스러운 경험이 될 겁니다. 게다가 짧은 줄로 작성된 스크립트는 일반 편집기에서 읽고 이해하기에도 훨씬 수월합니다!
저는 Bash를 정말 사랑합니다. 이 주제만으로도 몇 시간이든 글을 쓰거나 동료 애호가들과 유용한 팁을 나눌 수 있습니다. 여러분의 애용하는 Bash 팁도 댓글로 공유해 주세요!