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

시스템 관리자를 위한 Bash 실전 가이드

모든 직업에는 그 분야의 전문가들이 가장 자주 사용하는 도구가 있습니다. 많은 시스템 관리자에게 그 도구는 바로 셸(shell)입니다. 대부분의 Linux 및 기타 Unix 계열 시스템에서 기본 셸로 사용되는 것이 바로 Bash입니다.

Bash는 1980년대 후반에 탄생한 꽤 오래된 프로그램이지만, 그보다 10년 이상 앞선 C 셸(csh)과 같은 훨씬 오래된 셸들의 전통 위에 세워졌습니다. 셸이라는 개념 자체가 워낙 오래되었기 때문에, 시스템 관리자의 업무를 한결 수월하게 만들어 줄 방대한 노하우가 곳곳에 숨어 있습니다.

지금부터 기본적인 내용들을 하나씩 살펴보겠습니다.

실수로 root 명령어를 실행하지 않도록 예방하기

한 번쯤은 의도치 않게 root 권한으로 명령어를 실행해 문제를 일으킨 적이 있으신가요? (손들기)

아마 우리 대부분은 한 번쯤은 그런 경험이 있을 겁니다. 정말 아찔한 순간이죠. 같은 실수를 반복하지 않도록 도와주는 아주 간단한 요령 몇 가지를 소개합니다.

별칭(alias) 활용하기

먼저 mvrm 같은 명령어에 mv -irm -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가지를 정리하며 글을 마무리하겠습니다. (절대적 진리라고 주장하지는 않겠습니다.)

  1. 주석은 아까운 게 아닙니다. Bash 스크립트는 복잡해질 수 있습니다. 주석을 달지 고민된다면 일단 주석을 추가하세요. 주말이 지나고 돌아왔을 때 지난주 금요일에 무엇을 하려던 건지 파악하는 데 시간을 쓰고 있다면, 주석을 넣는 걸 잊었던 것입니다.
  1. 변수 이름은 항상 중괄호로 감싸세요. ${myvariable}처럼 말입니다. 이 습관을 들이면 ${variable}_suffix 같은 표현도 가능해지고, 스크립트 전체의 일관성도 향상됩니다.
  1. 백틱 대신 $() 문법을 사용하세요. 표현식을 평가할 때 백틱을 쓰지 말고 $() 문법을 사용하세요.
    for file in $(ls); do

    이렇게 하세요. 아래처럼 하지 마세요:

    for file in `ls`; do

    전자는 중첩이 가능하고 가독성이 좋으며, 시스템 관리자 커뮤니티 전반의 기대에도 부합합니다. 백틱은 사용하지 마세요.

  1. 일관성을 유지하세요. 하나의 스타일을 정했다면 스크립트 전체에서 끝까지 지키세요. 물론 저는 백틱보다 $() 문법을, 변수는 중괄호로 감싸는 방식을 선호합니다. 들여쓰기도 탭이 아닌 공백 2~4칸을 권장하지만, 설령 '잘못된' 방식을 선택하더라도 그 방식을 일관되게 유지하는 것이 중요합니다.
  1. 적절한 shebang을 사용하세요. Bash로만 실행할 의도로 스크립트를 작성한다면 #!/usr/bin/bash를 shebang으로 쓰는 것이 좋습니다. #!/bin/sh#!/usr/bin/sh는 사용하지 마세요. 스크립트는 실행되지만 호환성 모드로 동작하면서 예상치 못한 부작용이 생길 수 있습니다. (호환성 모드가 목적이라면 예외입니다.)
  1. 문자열 비교 시 변수를 따옴표로 감싸세요. if문에서 문자열을 비교할 때는 변수를 따옴표로 묶는 것이 좋습니다. 변수가 비어 있으면 아래 코드는 Bash 오류를 발생시킵니다:
    if [ ${myvar} == "foo" ]; then
    echo "bar"
    fi

    반면 아래처럼 따옴표로 감싸면 false로 평가됩니다:

    if [ "${myvar}" == "foo" ]; then
    echo "bar"
    fi

    또한 변수 내용을 확신할 수 없을 때(예: 사용자 입력을 파싱할 때)는 변수를 따옴표로 감싸 특수문자 해석을 방지하고, 공백이 포함되어 있더라도 변수가 하나의 단어로 취급되도록 해야 합니다.

  1. 문자열 비교에도 이중 등호(==)를 사용하세요. 취향의 문제이긴 하지만, 저는 Bash에서 문자열을 비교할 때도 이중 등호(==)를 선호합니다. 단일 등호도 문자열 비교에는 동작하지만, 저는 등호 하나를 보면 즉시 '단일 등호는 대입 연산자!'라는 생각이 들기 때문입니다. 역시 일관성의 문제죠.
  1. 적절한 종료 코드(exit code)를 사용하세요. 스크립트가 작업에 실패했다면 사용자에게 실패 메시지를 출력하고(가능하면 해결 방법도 함께), 0이 아닌 종료 코드를 반환하세요:
    # we have failed
    echo "Process has failed to complete, you need to manually restart the whatchamacallit"
    exit 1

    이렇게 하면 다른 스크립트에서 이 스크립트를 프로그래밍 방식으로 호출하고 성공 여부를 검증하기가 훨씬 쉬워집니다.

  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}
  1. 함수 내 변수에는 local 키워드를 사용하세요. 특히 규모가 큰 스크립트를 작성하거나 여러 사람과 함께 작업할 때는 함수 안에서 변수를 정의할 때 local 키워드를 사용하는 것을 고려하세요. local 키워드는 해당 함수 안에서만 보이는 지역 변수를 생성하여, 변수 이름 충돌 가능성을 크게 줄여줍니다.
  1. 스크립트의 한 줄을 너무 길게 작성하지 마세요. 시스템 관리자라면 때때로 콘솔에서 디버깅을 해야 합니다. 데이터 센터의 실제 콘솔이든, 가상화 플랫폼을 통한 가상 콘솔이든 말이죠. 그런 환경에서 스크립트를 디버깅해야 한다면 이 조언에 감사하게 될 겁니다. 많은 시스템에서 콘솔의 기본 너비는 여전히 80자입니다. 아주 긴 줄로 된 스크립트를 콘솔에서 디버깅해야 한다면 고통스러운 경험이 될 겁니다. 게다가 짧은 줄로 작성된 스크립트는 일반 편집기에서 읽고 이해하기에도 훨씬 수월합니다!

저는 Bash를 정말 사랑합니다. 이 주제만으로도 몇 시간이든 글을 쓰거나 동료 애호가들과 유용한 팁을 나눌 수 있습니다. 여러분의 애용하는 Bash 팁도 댓글로 공유해 주세요!