트랙
/
Bash
Bash
/
학습 과정
/
따옴표의 중요성
따옴

따옴표의 중요성 에서 Bash

{other: "%{count}개의 연습 문제"}

따옴표의 중요성 소개

bash를 처음 배우는 사람이라면 거듭 듣게 되는 조언이 하나 있어요:

변수를 따옴표로 묶어요!

왜 그럴까요?

$name처럼 변수를 확장할 때는 거의 항상 변수의 _값_을 하나의 문자열(또는 숫자)로 다루고 싶어 해요. 그 값이 예상치 않게 두 개 이상의 별도 문자열로 처리되는 것은 원하지 않아요.

따옴표로 묶기

따옴표로 묶기는 어떤 텍스트가 하나의 단어로 처리되도록 보장하는 가장 기본적인 방법이에요. 따옴표에는 크게 두 가지 종류가 있어요.

큰따옴표

큰따옴표 안에서는 매개변수 확장, 산술 확장, 명령어 치환이 허용돼요.

echo "This is bash version $BASH_VERSION"

큰따옴표로 묶은 문자열 안에 큰따옴표 자체를 넣고 싶다면 앞에 백슬래시를 붙이면 돼요.

echo "She said \"Let me teach you about bash.\""

작은따옴표

작은따옴표는 안에서 어떤 확장도 허용하지 않아요.

echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION

작은따옴표로 묶은 문자열 안에 작은따옴표를 넣는 것은 불가능해요.

따옴표로 묶기가 중요한 이유

echo $name은 정확히 무엇이 문제일까요?

셸이 한 줄의 코드를 해석할 때는 여러 종류의 확장을 순서대로 수행해요. 마지막에 수행되는 확장 중 두 가지가 바로 _단어 분할_과 _파일 이름 확장_이에요.

단어 분할

매개변수 확장, 명령어 치환, 산술 확장의 결과 중 _큰따옴표 안에서 일어나지 않은 것_은 모두 "단어 분할"의 대상이 돼요. 셸은 확장된 결과를 가져와서 $IFS 값에 들어 있는 문자 시퀀스를 기준으로 나눠요. 기본값은 공백, 탭, 줄 바꿈이에요.

셸 루프를 사용하면 이 동작을 직접 확인할 수 있어요:

sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^

출력:

1 The quick brown fox jumps.

그런데 따옴표로 묶지 않으면:

i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^

출력:

1 The
2 quick
3 brown
4 fox
5 jumps.

$IFS 값을 다르게 하면 어떻게 되는지 볼까요:

csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done

출력:

first
second
third
단어 분할 비활성화하기

단어 분할을 비활성화하는 한 가지 방법은 $IFS를 빈 문자열로 설정하는 것이에요.

sentence="The quick brown fox jumps."
IFS=    # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^

출력:

1 The quick brown fox jumps.

하지만 파일 이름 확장도 함께 비활성화하지 않는 한, 변수를 따옴표로 묶지 않고 그대로 두는 것은 권장하지 않아요. 방법은 아래에서 살펴볼게요.

파일 이름 확장

"Glob" 패턴은 파일 집합을 일치시키는 간결한 패턴을 표현할 때 사용해요. 예를 들어 디렉터리에 있는 모든 Markdown 파일을 *.md로 일치시킬 수 있어요. Glob 패턴은 Unix가 만들어진 이래로 존재해 왔어요.

Glob 와일드카드 문자는 다음과 같아요:

  • *는 임의의 문자 0개 이상과 일치해요.

  • ?는 임의의 문자 정확히 1개와 일치해요.

  • [...]는 괄호 안에 나열된 문자 집합에서 정확히 1개의 문자와 일치해요.

    Advanced
    문자 집합에 대해서는 세부 사항이 아주 많아요. 여기를 클릭하면 펼쳐서 볼 수 있어요.
    • [abc]는 a 또는 b 또는 c 중 하나인 문자 정확히 1개와 일치해요.
      • 표현식에 닫는 대괄호 자체를 포함하려면, 여는 대괄호 바로 다음 문자(선택적인 ^ 다음)로 두면 돼요.
        • [][]는 닫는 대괄호 또는 여는 대괄호와 일치하는 문자 집합이에요.
        • 또는 백슬래시로 이스케이프할 수 있어요: [[\]]
    • [x-y]는 x부터 y까지의 범위 안에 있는 문자 정확히 1개와 일치해요.
      • 문자 집합에 하이픈 문자 자체를 포함하려면, 여는 대괄호 바로 다음 문자(선택적인 ^ 다음)이거나 닫는 대괄호 바로 앞 문자여야 해요.
        • [-xy]와 [xy-]는 각각 - 또는 x 또는 y 중 하나인 문자 정확히 1개와 일치해요.
        • 또는 백슬래시로 이스케이프할 수 있어요: [x\-y]
      • 범위는 여러 개 지정할 수 있어요. 예를 들어 [0-9A-Fa-f]는 16진수 숫자와 일치해요.
    • [^abc]는 a 또는 b 또는 c가 아닌 문자 정확히 1개와 일치해요.
    • [^x-y]는 x부터 y까지의 범위에 속하지 않는 문자 정확히 1개와 일치해요.
    • [[:character_class:]]는 "character_class"라는 이름의 문자 클래스에 속하는 문자 1개와 일치해요.
      • 사용할 수 있는 문자 클래스는 다음과 같아요:
        • lower -- 소문자
        • upper -- 대문자
        • alpha -- 문자
        • digit -- 숫자
        • alnum -- 문자와 숫자
        • space -- 공백
        • blank -- 가로 공백 (공백과 탭)
        • xdigit -- 16진수 숫자
        • word -- 식별자에 허용되는 문자 (문자, 숫자, 밑줄)
        • punct -- 구두점
        • cntrl -- 제어 문자
        • graph -- "표시되는" 문자
        • print -- 표시되는 문자와 공백
      • 문자 클래스는 조합할 수 있어요:
        • [[:alpha:]] 클래스는 [[:lower:][:upper:]]로 이루어져 있어요.
        • [[:alnum:]] 클래스는 [[:alpha:][:digit:]]로 이루어져 있어요.
        • [[:word:]] 클래스는 [[:alnum:]_]로 이루어져 있어요.
        • 밑줄(8진수 137)은 [[:word:]]이면서 동시에 [[:punct:]]예요.
        • [[:graph:]] 클래스는 [[:alnum:][:punct:]]로 이루어져 있어요.
        • [[:print:]] 클래스는 [[:graph:] ]로 이루어져 있어요. 다른 공백이 아니라 공백(8진수 040)만 포함해요.
파일 목록 반복하기

for문으로 파일 목록을 반복할 수 있어요.

for file in *.csv; do
    do_something_with "$file"
done

변수 "$file"이 따옴표로 묶여 있는 것에 주목해요. Glob 패턴은 이름에 공백이 들어 있는 파일을 반환할 수 있어요. do_something_with 명령이 파일 이름이라는 인자 하나만 정확히 받도록 하려면 변수를 따옴표로 묶어야 해요.

Glob 패턴은 정규 표현식이 아니다

Glob 패턴은 정규 표현식이 아니에요.

  • 문자 정확히 1개와 일치
    • 정규식 .
    • glob ?
  • 문자 0개 이상과 일치
    • 정규식 .*
    • glob *
      • glob 패턴 .*는 "첫 번째 문자가 마침표 자체이고 그 뒤에 임의의 문자가 0개 이상 오는 파일 이름과 일치"를 뜻해요.
  • 문자 1개 이상과 일치
    • 정규식 .+
    • glob ?*
  • 정규식의 문자 집합과 glob의 문자 집합은 같은 방식으로 동작해요.
Advanced

Bash는 정규 표현식에 더 가까운 확장 패턴을 제공해요.

따옴표로 묶기와 파일 이름 확장

파일 이름 확장을 억제하고 싶을 때가 있어요. 사용자 입력을 처리할 때가 그중 하나예요. 다음 코드를 봐요.

read -p "Enter something: " user_input
echo $user_input

사용자가 별표(*)를 입력하면 무엇이 출력될까요? 현재 디렉터리에 있는 파일 목록이에요. 아마 그건 원하는 동작이 아닐 거예요.

또 다른 예로, 패턴을 기대하는 프로그램에 패턴을 인자로 전달하는 경우가 있어요. tr 프로그램은 텍스트를 조작할 때 자주 사용해요. 변수 값을 소문자로 바꾸려면 이렇게 하면 돼요.

var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'

출력:

my puppet

흔한 실수는 tr의 인자를 따옴표로 묶는 것을 잊는 거예요: tr [:upper:] [:lower:]

Caution

왜 그게 문제일까요? 따옴표로 묶지 않은 glob 패턴은 일치하는 파일 목록으로 확장돼요. 스크립트를 사용하는 사람의 현재 디렉터리에 p, u, w라는 파일이 있다고 해봐요:

touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]

이러면 오류가 발생해요!

따옴표로 묶지 않은 [:upper:] 패턴은 :, u, p, e, r 중 아무 문자와 일치해요. 따옴표로 묶지 않은 [:lower:] 패턴은 :, l, o, w, e, r 중 아무 문자와 일치해요. Bash는 첫 번째 패턴에 대해 p와 u 파일을, 두 번째 패턴에 대해 w 파일을 찾아내고, tr은 이렇게 호출돼요.

echo "$var" | tr p u w

그런데 이건 tr에 맞지 않는 인자 개수예요.

파일 이름 확장 비활성화하기

set 명령으로 파일 이름 확장을 비활성화할 수 있어요.

echo *      # prints a list of files
set -f      # disable filename expansion
echo *      # prints a literal asterisk
set +f      # enable filename expansion
파일 이름 확장 제어하기

내장 shopt 명령에는 파일 이름 확장이 동작하는 방식을 제어하는 여러 설정이 있어요. 그중 흥미로운 몇 가지는 다음과 같아요:

  • shopt -s nocaseglob -- 대소문자를 구분하지 않고 일치시켜요.
  • shopt -s extglob -- 확장 패턴을 사용할 수 있게 해요.
  • shopt -s nullglob -- 일치하는 파일이 없으면 패턴을 아무것도 아닌 것으로 바꿔요. 기본 동작은 패턴을 리터럴 문자열로 그대로 두는 거예요.
GitHub에서 편집 링크가 새 창이나 탭에서 열려요