본문 바로가기
리눅스/Part 1 - Learning The Shell

리눅스 기초 | 7. 쉘처럼 세상 바라보기

by 객잔주인 2024. 5. 24.

해당 포스팅은 William E. Shotts, Jr.의 오픈소스 저서 The Linux Command Line(링크)를 번역한 내용입니다


이번 챕터에서는 커맨드 라인에서 엔터를 눌렀을 때 일어나는 "마술" 몇 가지를 알아볼 것입니다. 새로 알아야 할 명령어는 한 가지만 더 배우면 쉘의 여러가지 놀라운 기능을 사용할 수 있습니다.

  • $\texttt{echo}$ - 텍스트 한 줄을 출력합니다

확장

명령어를 입력하고 엔터를 칠 때마다 $\texttt{bash}$는 명령을 실행하기 전에 입력된 텍스트에 여러가지 변환을 합니다. 이전에 배웠듯이 $\texttt{*}$와 같은 단일 문자가 쉘에게는 그 이상의 의미를 가질 수 있다는 것을 알고 있습니다. 이를 확장(expansion)이라고 합니다. 확장이 일어나면 쉘은 입력된 것을 확장해서 다른 것으로 만듭니다. 이 과정을 잘 설명하기 위해 명령어 $\texttt{echo}$를 배워보도록 하겠습니다. $\texttt{echo}$는 쉘 빌트인으로 굉장히 간단한 작업을 수행합니다. 텍스트 인자를 받아서 표준 출력으로 보내죠.

$\texttt{echo}$로 전달된 인자가 그대로 출력되는 간단한 작업입니다. 다른 예시도 한 번 보겠습니다.

 

무슨 일이 일어난거죠? 왜 $\texttt{echo}$가 $\texttt{*}$을 출력하지 않았을까요? 와일드카드를 기억하고 있다면 $\texttt{*}$ 문자가 모든 파일명과 매치되는 문자라는 것을 알 수 있을 것입니다. 그러나 쉘이 그것을 어떻게 하는지는 아직 모르죠. 간단하게는 쉘이 $\texttt{echo}$를 실행하기 전에 $\texttt{*}$을 현재 작업 디렉토리의 파일명들로 확장시킨다고 말 할 수 있습니다. 엔터를 치면 쉘은 명령어를 실행하기 전에 자동으로 커맨드 라인에 있는 문자들을 확장시킵니다. 그렇기 때문에 $\texttt{echo}$ 명령어는 $\texttt{*}$ 문자를 보지 못하게 되고 그 확장된 결과만 보게되는거죠. 이제 왜 $\texttt{echo}$가 $\texttt{*}$ 대신 파일명들을 출력했는지 이해가 되시겠죠?

 

경로명 확장(Pathname Expansion)

와일드카드의 작동 메커니즘을 경로명 확장(pathname expansion)이라고 합니다. 이전 챕터에서 익혔던 다른 테크닉들을 다시 보게되면 그것들이 사실은 확장이었다는 것을 확인할 수 있습니다. 홈 디렉토리가 주어진 경우는 다음과 같습니다:

혹은 와일드 카드를 이용하면 다음과 같이도 할 수 있죠:

홈 디렉토리에서 더 들어가볼 수도 있습니다:

숨김 파일의 경로명 확장

이미 알고 있듯이 마침표로 시작하는 파일명은 숨겨집니다. 경로명 확장도 이것을 고려하기 때문에 $\texttt{*}$ 와일드카드로는 숨김 파일이 나타나지 않습니다
$\texttt{echo *}$
직관적으로 생각하면 마침표로 시작하는 패턴을 사용해서 숨김 파일도 표시할 수 있을 것 같습니다. 이렇게요:
$\texttt{echo .*}$
잘 작동하는 것 처럼 보이지만 자세히 보면 $\texttt{.}$이나 $\texttt{..}$와 같은 이름들도 등장하는 것을 볼 수 있습니다. (쉘 환경이나 설정에 따라 나타나지 않을 수 있습니다) 이 디렉토리들은 현재 작업 디렉토리와 부모 디렉토리를 의미하기 때문에 이 결과가 원하던 결과가 아닐 수 있습니다. 다음과 같이 입력해도 같은 결과를 볼 수 있습니다:
$\texttt{ls -d .* | less}$
경로명 확장을 더 잘 이용하기 위해서는 더 자세한 패턴을 입력해야합니다.
$\texttt{echo .[!.]*}$
이 패턴은 하나의 마침표 뒤에 마침표가 아닌 다른 문자가 오는 모든 파일명으로 확장됩니다. 이는 거의 대부분의 숨김 파일에 적용될 수 있는 패턴입니다(다만 여러개의 마침표로 시작하는 파일명은 놓칠 것입니다). $\texttt{ls}$ 명령어에 $\texttt{-A}$ 옵션을 사용하면 모든 숨김 파일을 볼 수 있습니다.
$\texttt{ls -A}$

틸데 확장(Tilde Expansion)

$\texttt{cd}$ 커맨드를 배웠을 때를 상기해보면 틸데(~)도 특별한 의미를 가지고 있습니다. 틸데를 단어의 앞에 사용했을 때에 틸데는 명시된 사용자의 홈 디렉토리로 확장되거나 명시된 사용자가 없을 경우 현재 사용자의 홈 디렉토리를 가리킵니다. 현재 사용자의 홈 디렉토리를 가리키는 경우:

사용자 "foo"의 홈 디렉토리를 가리키는 경우:

산술 확장(Arithmetic Expansion)

쉘은 확장을 통해 산술 계산이 이루어지도록 합니다. 이를 통해 쉘 프롬프트를 계산기처럼 사용할 수 있습니다.

산술 확장은 다음과 같은 형식으로 작성되어야 합니다:

$\texttt{\$((expression))}$

여기서 $\texttt{expression}$은 값과 산술 연산자가 있는 산술 표현을 의미합니다.

 

산술 확장은 오로지 정수만 지원하지만 여러가지 연산을 할 수 있습니다. Table 7-1은 지원되는 연산들을 보여줍니다.

 

Table 7-1: 산술 연산자

연산자 설명
+ 덧셈
- 뺄셈
* 곱셈
/ 나눗셈 (확장이 정수만 지원하므로 결과도 정수로 나온다는 점에 유의하세요)
% 모듈로(Modulo), 간단히 말해 '나머지'
** 제곱

산술 표현에서 공백은 아무런 의미를 가지지 않으며 하나의 산술 표현은 다른 산술 표현의 내부에 작성될 수 있습니다. 예를 들어, 5의 제곱 곱하기 3을 계산하려면 다음과 같이 입력합니다:

한 쌍의 괄호를 이용하면 여러 하위 표현을 하나로 묶을 수 있습니다. 이 테크닉을 사용하면 바로 위의 예시를 하나의 확장만 사용하여 다시 작성할 수 있습니다:

나눗셈과 나머지 연산자를 사용한 예시를 보여드리겠습니다. 정수 나눗셈의 결과가 어떻게 나오는지 확인해보세요.

산술 확장에 대한 더 깊은 내용은 챕터 34에서 다루도록 하겠습니다.

중괄호 확장(Brace Expansion)

아마 확장들 중에 가장 괴상한 확장은 중괄호 확장(brace expansion)일 것입니다. 중괄호 확장은 중괄호 안의 패턴으로부터 여러 텍스트를 생성하도록 해줍니다. 예시를 확인해보세요:

중괄호 확장이 될 패턴의 앞쪽에 위치한 텍스트를 전치부(preamble)라고 하고 뒤쪽에 위치한 텍스트를 후치부(postscript)라고 합니다. 중괄호 내부에는 특정 범위의 정수나 문자 혹은 콤마(,)로 구분된 문자열이 올 수 있습니다. 패턴에는 따옴표로 감싸지지 않은 공백이 올 수 없습니다. 정수 범위를 사용하는 예시를 보겠습니다:

버전 4.0이상의 $\texttt{bash}$에서는 다음과 같이 정수에 제로패딩(zero-padded)(형식상의 이유로 의미 없는 0을 추가하는 것)도 할 수 있습니다:

알파벳을 역순으로 출력할 수도 있습니다:

중괄호 확장은 중첩해서 작성하는 것도 가능합니다:

그래서 이걸 어디에 쓸까요? 가장 흔히 여러개의 파일이나 디렉토리를 만들 때 사용합니다. 예를 들어, 여러분이 포토그래퍼고 많은 양의 사진을 년-월로 구분하여 정리하고 싶다고 가정할 때, 아마 가장 먼저 할 일은 "년-월" 형식으로 여러개의 디렉토리를 만드는 것일겁니다. 이렇게 하면 디렉토리명이 날짜순으로 정렬될 것입니다. 모든 디렉토리명을 하나하나 직접 작성할 수도 있겠지만 일이 많아지기도 하고 오타가 날 가능성도 큽니다. 대신 이렇게 할 수 있습니다:

파라미터 확장(Parameter Expansion)

이번 장에서는 파라미터 확장에 대해서 맛보기만 해보겠지만 이후에 더 깊이 다룰 예정입니다. 왜냐하면 파라미터 확장은 커맨드라인에서보다 쉘 스크립트에서 더 유용한 기능이기 때문입니다. 이 기능은 시스템이 작은 데이터 조각을 저장하고 각 조각에 이름을 부여할 수 있는 능력과 관련이 있습니다. 그런 조각들을 변수(variables)라고 부릅니다. 예를 들어, $\texttt{USER}$라고 하는 변수는 사용자 이름을 담고있습니다. 파라미터 확장을통해 $\texttt{USER}$의 내용을 확인하고 싶을 때에는 이렇게 할 수 있습니다:

모든 변수들을 보고 싶다면:

다른 확장의 경우 패턴을 잘못 입력한 경우 확장이 일어나지 않고 잘못 입력된 패턴을 그대로 출력합니다. 그러나 파라미터 확장에서 변수명에 오타를 낼 경우에는 공백 문자열을 반환합니다:

명령어 치환(Command Substitution)

명령어 치환은 명령어의 출력을 다른 명령어의 입력으로 사용할 수 있게 해주는 기능입니다.

(저자)가 가장 좋아하는 명령어는:

$\texttt{which cp}$의 결과를 $\texttt{ls}$ 명령어에 인자로 전달합니다. 따라서 $\texttt{cp}$ 프로그램의 전체 경로를 알지 못하더라도 파일 정보를 얻을 수 있습니다. 명령어 치환은 간단한 명령어 뿐 아니라 전체 파이프라인을 인자로 넣어줄 수도 있습니다.

이 예시에서는 파이프라인의 출력이 $\texttt{file}$의 인자로 입력됩니다.

 

명령어 치환에는 또 다른 문법이 있는데, 더 오래된 쉘 프로그램에서 사용되던 문법이지만 $\texttt{bash}$에서도 지원하고 있습니다. 달러 사인과 괄호 대신에 백틱(backquotes 혹은 backtick)을 사용할 수 있습니다.

따옴표(quoting)

지금까지 쉘이 확장을하는 경우의 수를 살펴보았으니 이제 그것을 제어하는 방법을 배워볼 차례입니다. 다음 예시를 보겠습니다:

첫 번째 예시에서는 쉘의 단어 분리기(word-splitting) 기능이 $\texttt{echo}$의 인자 리스트에서 추가적인 공백을 제거했습니다. 두 번째 예시에서는 파라미터 확장이 정의되지 않은 $\texttt{\$1}$을 공백으로 치환했습니다. 쉘은 따옴표를 사용해 불필요한 확장이 일어나지 않게 해줍니다.

큰따옴표(Double Quotes)

첫 번째로 큰따옴표의 용법을 알아보겠습니다. 텍스트를 큰따옴표 안에 넣으면 쉘이 사용하는 모든 특수 문자들이 그 기능을 잃고 일반적인 문자로 인식됩니다. 그 중 $\texttt{\$}$, $\texttt{\\}$(백슬래시), 그리고 $\texttt{`}$(백틱)은 예외입니다. 즉, 단어 분리기, 경로명 확장, 틸데 확장, 중괄호 확장이 모두 억제되지만, 파라미터 확장, 산술 확장, 명령어 치환은 여전히 유효합니다. 큰따옴표를 사용하면 공백이 포함된 파일명도 처리할 수 있습니다. 예를 들어, 우리가 $\texttt{two words.txt}$라는 파일명을 마주한 불쌍한 희생자라고 가정해보겠습니다(파일명에 공백이 있으면 여러 작업에서 파일명을 처리하기 위한 2차적인 공정이 필요하므로 보통은 공백 대신 하이픈(-)이나 언더바(_)를 사용합니다)이 파일명을 커맨드라인에서 사용하기를 시도하면 단어 분리기가 이 파일명을 하나의 인자가 아닌, 두 개의 구분된 인자로 인식할 것입니다.

큰따옴표를 사용하면 단어 분리기가 작동을 멈추고 원하는 결과를 얻을 수 있습니다. 게다가 파일명을 제대로 고칠수도 있죠.

짜잔! 이제 귀찮게 큰따옴표를 사용할 필요가 없어졌습니다.

 

파라미터 확장, 산술 확장, 명령어 치환은 큰따옴표 안에서도 일어난다는 점을 기억하세요.

큰따옴표가 명령어 치환에 어떤 영향을 미치는지 짚고 넘어가겠습니다. 먼저 단어 분리가 어떻게 일어나는지 더 깊게 파보죠. 이전 예시에서 단어 분리기가 텍스트에서 추가적인 공백을 제거하는 것을 보았습니다.

기본적으로 단어 분리기는 공백, 탭, 개행 문자(linefeed characters)를 찾고 이를 단어 사이의 구분 문자(delimeter)로 인식합니다. 이는 따옴표 처리되지 않은 공백, 탭, 개행 문자는 텍스트의 일부로 인식되지 않는다는 것을 의미합니다. 오로지 분리하는 역할만 합니다. 단어들을 분리해서 서로다른 인자로 만들기 때문에, 위 예시에서 커맨드라인은 하나의 명령어와 네 개의 인자를 가진것으로 인식합니다. 큰 따옴표를 사용한다면:

단어 분리기가 억제되고 공백들은 구분 문자로서 인식되지 않습니다. 따라서 공백들도 텍스트의 일부로 여겨집니다. 큰따옴표가 추가되면 커맨드라인은 하나의 명령어와 하나의 인자를 가진것으로 인식합니다.

 

단어 분리기가 개행 문자를 구분 문자로 인식함으로 인해서 명령어 치환에 흥미로운 영향을 미치게 됩니다. 다음 예시를 보겠습니다:

첫 번째 예시에서는 명령어 치환에 따옴표가 사용되지 않아 커맨드라인이 38개의 인자를 받은 것으로 인식합니다. 두 번째 예시에서는 따옴표를 사용했기 때문에 커맨드라인이 한 개의 인자를 입력받은 것으로 인식합니다.

작은따옴표(Single Quotes)

모든 확장을 억제하고 싶은 경우에는 작은따옴표(single quotes)를 사용합니다. 따옴표가 사용되지 않은 경우, 큰따옴표가 사용된 경우, 작은따옴표가 사용된경우를 비교해보겠습니다:

아래로 갈수록 확장이 일어나는 경우가 줄어드는 것을 볼 수 있습니다.

이스케이프 문자(Escaping Characters)

하나의 문자에 대해서만 "따옴표"를 적용하고 싶다면 어떻게 해야할까요? 이런 경우에는 백슬래시($\texttt{\\}$)를 사용할 수 있습니다. 종종 큰따옴표 안에서 확장을 막기 위해 사용됩니다.

파일명에 있는 특수한 의미를 가진 문자의 의미를 죽이기 위해서도 사용합니다. 예를 들어 $\texttt{\$}$, $\texttt{!}$, $\texttt{&}$, 공백 등의 문자가 파일명에 들어있는 경우에는 다음과 같이 백슬래시를 사용할 수 있습니다:

백슬래시를 출력하기 위해서는 백슬래시 두 번($\texttt{\\\\}$)을 입력하면 됩니다. 작은따옴표 안에서는 백슬래시도 그 특수한 의미를 잃어버리고 일반적인 문자로 인식된다는 것에 유의하세요.

백슬래시 이스케이프 시퀀스

이스케이프 문자로서의 역할에 더해서 백슬래시는 제어 코드(control codes)라는 특수 문자를 표현하기 위한 일부로 사용되기도 합니다. ASCII 코드 체계의 첫 32개 문자는 텔레타이프와 같은 장치에 명령을 전달하기 위해 사용됩니다. 일부 코드(탭, 백스페이스, 개행 문자, 캐리지 리턴)는 익숙하고 나머지(null, end-of-transmission, acknowledge)는 낯설 것입니다.

- \a: 벨 소리 (컴퓨터가 비프음을 내도록 함)
- \b: 백스페이스
- \n: 줄 바꿈 (유닉스 계열 시스템에서는 linefeed를 의미)
- \r: 캐리지 리턴
- \t: 탭

위는 자주 사용되는 백슬래시 이스케이프 시퀀스 목록입니다. 이런 표현은 C 언어에서 유래되어 쉘을 포함한 많은 곳에서 채용되었습니다.

$\texttt{echo}$에 $\texttt{-e}$ 옵션을 추가하면 이스케이프 시퀀스를 해석할 수 있습니다. $\texttt{\$' '}$ 구문에 이스케이프 시퀀스를 넣어 사용하는 것도 가능합니다. $\texttt{sleep}$ 명령어(주어진 시간만큼 대기하다가 종료되는 간단한 프로그램)를 사용해서 아주 원시적인 카운트다운 타이머를 만들 수 있습니다:
$\texttt{sleep 10; echo -e "Time's up\\a"}$
혹은 이렇게 할수도 있습니다:
$\texttt{sleep 10; echo -e "Time's up" \$`\\a`}$

요약

쉘을 사용하다보면 확장과 따옴표 사용 빈도가 높아지기 때문에 이들이 어떻게 작동하는지 알아두는 것이 중요합니다. 사실 이들이 쉘에서 가장 중요한 주제라고 주장하는 경우도 있습니다. 확장에 대한 이해 없이는 쉘은 항상 혼란과 수수께끼의 원천이 되고 잠재적인 능력을 낭비하게 될 것입니다.

추가 자료

  • $\texttt{bash}$의 매뉴얼 페이지에는 확장과 따옴표에 관한 주요 섹션이 있으며, 이러한 주제들을 보다 공식적으로 다루고 있습니다.
  • Bash Reference Manual의 확장과 따옴표에 관한 챕터를 살펴보세요:
    https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html
 

Bash Reference Manual

This chapter describes how to use the GNU History Library interactively, from a user’s standpoint. It should be considered a user’s guide. For information on using the GNU History Library in other programs, see the GNU Readline Library Manual. 9.1 Bash

www.gnu.org