유닉스 쉘(Unix Shell)

파일, 문자, 디렉토리 등 찾기

학습 목표

  • grep 명령어를 사용해서 간단한 패턴과 매칭되는 행을 텍스트 파일에서 골라낸다.
  • find 명령어를 사용해서 간단한 패턴과 매칭하는 파일을 찾는다.
  • 명령-라인 매개변수로 한 명령어의 출력결과를 다른 명령어에 사용한다.
  • “텍스트(text)”와 “바이너리(binary)” 의미와 많은 도구가 바이너리 파일을 잘 다루지 못하는 이유를 설명한다.

검색에 대해서 어떻게 말하는지에 따라 사람의 나이를 유추할 수 있다: 젊은 사람은 동사로 “구글(Google)”을 사용하고, 무뚝뚝한 나이든 유닉스 프로그래머는 “grep”을 사용한다. grep은 “global/regular expression/print(전역/정규표현식/출력)”의 축약어로 초기 유닉스 편집기에서 일반적인 일련의 연산작업이다. 매우 유용한 명령-라인 프로그램 이름이기도 하다.

grep은 패턴과 매칭되는 파일의 행을 찾아 화면에 뿌려준다. 예제 파일로, Salon 잡지 1988년 경쟁부문에서 하이쿠(haiku, 일본의 전통 단시) 3개를 담고 있는 파일을 사용한다. 이 예제 파일을 갖는 “writing” 하위 디렉토리에서 작업을 할 것이다:

$ cd
$ cd writing
$ cat haiku.txt
The Tao that is seen
Is not the true Tao, until
You bring fresh toner.

With searching comes loss
and the presence of absence:
"My Thesis" not found.

Yesterday it worked
Today it is not working
Software is like that.

단어 “not”을 포함하는 행을 찾아 봅시다:

$ grep not haiku.txt
Is not the true Tao, until
"My Thesis" not found
Today it is not working

여기서 not이 찾고자 하는 패턴이다. 무척이나 간단하다: 모든 글자와 숫자를 쓴 문자에 대해서 매칭을 한다. 패턴이 나온 뒤에, 찾고자 하는 이름이나 혹은 파일 이름이 뒤에 온다. 출력값은 “not”을 포함하는 파일에 행이 3개 있다.

다른 패턴을 시도해 보자. 이번에는 “day”이다.

$ grep day haiku.txt
Yesterday it worked
Today it is not working

이번에는 출력값은 “day”를 포함한 “Yesterday”, “Today” 단어를 포함하는 행이 된다. 하지만, 더 큰 단어 안에 포함된 단어도 함께 출력된다. grep명령어에 -w 옵션을 주면, 단어 경계로 매칭을 제한해서, “day” 단어만을 가진 행만이 화면에 출력된다.

$ grep -w day haiku.txt

이 경우에, 해당되는 어떤 것도 없어서, grep 출력값은 비게된다. 때때로, 단어 하나가 아닌, 문구를 찾고자 한다. 인용부호 내부에 문구를 넣어 grep으로 작업하는 것이 편하다.

$ grep -w "is not" haiku.txt
Today it is not working

지금까지 단일 단어 주위를 인용부호로 감쌀 필요가 없다는 것을 알고 있다. 하지만, 단어 다수를 검색할 때 인용부호를 사용하는 것이 유용하다. 이렇게 하면, 검색용어 혹은 검색 문구와 검색 대상이 되는 파일 사이를 더 쉽게 구별하는데 도움이 된다. 나머지 예제에서는 인용부호를 사용한다.

또다른 유용한 옵션은 -n으로, 매칭되는 행에 번호를 붙여 출력한다:

$ grep -n "it" haiku.txt
5:With searching comes loss
9:Yesterday it worked
10:Today it is not working

상기에서 5, 9, 10번째 행이 문자 “it”를 포함함을 확인한다.

다른 유닉스 명령어와 마찬자기로 옵션(즉, 플래그)을 조합할 수 있다. 단어 “the”를 포함하는 행을 찾아보자. “the”를 포함하는 행을 찾는 -w 옵션과 매칭되는 행에 번호를 붙이는 -n을 조합할 수 있다:

$ grep -n -w "the" haiku.txt
2:Is not the true Tao, until
6:and the presence of absence:

이제 -i 옵션을 사용해서 대소분자 구분없이 매칭한다:

$ grep -n -w -i "the" haiku.txt
1:The Tao that is seen
2:Is not the true Tao, until
6:and the presence of absence:

이제, -v 옵션을 사용해서 뒤집어서 역으로 매칭을 한다. 즉, 단어 “the”를 포함하지 않는 행을 출력결과로 한다.

$ grep -n -w -v "the" haiku.txt
1:The Tao that is seen
3:You bring fresh toner.
4:
5:With searching comes loss
7:"My Thesis" not found.
8:
9:Yesterday it worked
10:Today it is not working
11:Software is like that.

grep 명령어는 옵션이 많다. 옵션에 대해서 알기 위해서 man grep을 타이핑한다. man은 유닉스 “manual(매뉴얼)” 명령어다. 명령어에 대한 기술과 옵션을 출력하고, (만약 운이 좋다면) 사용방법에 대한 예제도 몇개 제공한다.

man 페이지를 돌아다니려면, 한줄씩 이동하려면 위 화살표, 아래 화살표를 사용하거나, 페이지 단위로 위아래 이동하려면 “b”나 스페이스키를 사용한다. man 페이를 끝내려면, “q”를 타이핑한다.

$ man grep
GREP(1)                                                                                              GREP(1)

NAME
grep, egrep, fgrep - print lines matching a pattern

SYNOPSIS
grep [OPTIONS] PATTERN [FILE...]
grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]

DESCRIPTION
grep  searches the named input FILEs (or standard input if no files are named, or if a single hyphen-
minus (-) is given as file name) for lines containing a match to the given PATTERN.  By default, grep
prints the matching lines.
...        ...        ...

OPTIONS
Generic Program Information
--help Print  a  usage  message  briefly summarizing these command-line options and the bug-reporting
address, then exit.

-V, --version
Print the version number of grep to the standard output stream.  This version number should be
included in all bug reports (see below).

Matcher Selection
-E, --extended-regexp
Interpret  PATTERN  as  an  extended regular expression (ERE, see below).  (-E is specified by
POSIX.)

-F, --fixed-strings
Interpret PATTERN as a list of fixed strings, separated by newlines, any of  which  is  to  be
matched.  (-F is specified by POSIX.)
...        ...        ...

grep이 파일에 행을 찾는 반면에, find 명령어는 파일 자체를 검색한다. 다시, find 명령어는 정말 옵션이 많다; 가장 간단한 것이 어떻게 동작하는지 시연하기 위해, 다음에 보여지는 디렉토리 구조를 사용한다.

Find 예제를 위한 파일 나무구조

Find 예제를 위한 파일 나무구조

Nelle의 writing 디렉토리는 haiku.txt로 불리는 파일 하나와, 하위 디렉토리 4개를 포함한다. thesis 디렉토리는 슬프게고 비어있고, data 디렉토리는 one.txttwo.txt을 포함하고, tools 디렉토리는 formatstats 프로그램을 포함하고 빈 디렉토리 old가 있다.

첫 명령어로, find . -type d을 실행하자. 항상 그렇듯이, . 자체가 의미하는 바는 현재 작업 디렉토리로, 검색을 시작하는 디렉토리가 된다. -type d은 “디렉토리인 것들”을 의미한다. 아니나 다를까, find의 출력은 .을 포함하는 상기 작은 디렉토리 나무 구조에서 디렉토리 다섯개 이름이 출력된다:

$ find . -type d
./
./data
./thesis
./tools
./tools/old

-type d에서 -type f로 옵션을 변경하면, 대신에 모든 파일 목록이 나온다:

$ find . -type f
./haiku.txt
./tools/stats
./tools/old/oldtool
./tools/format
./thesis/empty-draft.md
./data/one.txt
./data/two.txt

find 명령어는 자동적으로 하위 디렉토리, 또 하위 디렉토리의 하위 디렉토리로 가서 주어진 패턴과 매칭되는 모든 것을 찾는다. 이것을 원치 않는다면, -maxdepth를 사용해서 검색의 깊이를 제한할 수 있다:

$ find . -maxdepth 1 -type f
./haiku.txt

-maxdepth의 반대는 -mindepth로, find에게 단지 일정 깊이 혹은 아래 깊이만 검색해서 출력하게 한다. 그러므로, -mindepth 2는 2 혹은 그 이상의 수준 이하에서 모든 파일을 찾게 된다:

$ find . -mindepth 2 -type f
./data/one.txt
./data/two.txt
./tools/format
./tools/stats

이제 이름으로 매칭을 하자:

$ find . -name *.txt
./haiku.txt

모든 텍스트 파일을 찾기를 기대하지만, 단지 ./haiku.txt만을 화면에 출력한다. 문제는 명령문을 실행하기 전에, *같은 와일드카드 문자를 쉘이 전개하는 것이다. 현재 디렉토리에서 *.txt을 전개하면 haiku.txt이 되기 때문에, 실제 실행하는 명령어는 다음과 같다:

$ find . -name haiku.txt

find 명령어는 사용자가 요청한 것만 수행한다; 사용자는 방금전에 잘못된 것을 요청했다.

사용자가 원하는 것을 얻기 위해서, grep을 가지고 했던 것을 수행하자. 단일 인용부호에 *.txt을 넣어서 쉘이 와일드카드 *을 전개하지 못하게 한다. 이런 방식으로, find 명령어는 확장된 파일명 haiku.txt이 아닌, 실제로 *.txt 패턴을 얻는다:

$ find . -name '*.txt'
./data/one.txt
./data/two.txt
./haiku.txt

앞에서 언급했듯이, 명령-라인(command-line)의 힘은 도구를 조합하는데 있다. 파이프로 어떻게 조합하는지를 살펴봤고; 또 다른 기술을 살펴보자. 방금 보았듯이, find . -name '*.txt' 명령어는 현재 디렉토리 및 하위 디렉토리에 있는 모든 텍스트 파일 목록을 보여준다. 어떻게 하면 wc -l 명령어와 조합해서 모든 파일의 행을 개수할 수 있을까?

가장 간단한 방법은 $() 내부에 find 명령어를 위치시키는 것이다:

$ wc -l $(find . -name '*.txt')
11 ./haiku.txt
300 ./data/two.txt
70 ./data/one.txt
381 total

쉘이 상기 명령어를 실행할 때, 처음 수행하는 것은 $() 내부를 무엇이든 실행하는 것이다. 그리고 나서 $() 표현식을 명령어의 출력 결과로 대체한다. find의 출력 결과가 파일 이름 3개, 즉, ./data/one.txt, ./data/two.txt, ./haiku.txt이여서, 쉘은 다음과 같이 명령문을 구성하게 된다:

$ wc -l ./data/one.txt ./data/two.txt ./haiku.txt

상기 명령문이 사용자가 원하는 것이다. 이러한 확장이 *? 같은 와일드카드로 확장할 때, 정확하게 쉘이 수행하는 것이다. 하지만 자신의 “와일드카드”로 사용자가 원하는 임의 명령어를 사용해보자.

findgrep을 함께 사용하는 것은 일반적이다. find가 패턴을 매칭하는 파일을 찾고 grep이 또 다른 패턴과 매칭되는 파일 내부 행을 찾는다. 예제로 다음에 현재 부모 디렉토리에서 모든 .pdb 파일에 “FE” 문자열을 검색해서, 철 원자를 포함하는 PDB파일을 찾을 찾을 수 있다:

$ grep "FE" $(find .. -name '*.pdb')
../data/pdb/heme.pdb:ATOM     25 FE           1      -0.924   0.535  -0.518

유닉스 쉘은 지금 사용하는 대부분의 사람보다 나이가 많다. 그토록 오랫동안 생존한 이유는 지금까지 만들어진 가장 생산성이 높은 프로그래밍 환경 중 하나 혹은 아마도 가장 생산성 높은 환경이기 때문이다. 구문이 암호스러울 수도 있지만, 숙달한 사람은 다양한 명령어를 대화하듯이 실험하고 나서, 본인 작업을 자동화하는데 학습한 것을 사용한다. 그래픽 사용자 인터페이스(GUI)가 처음에는 더 좋을 수 있지만, 여전히 두번째는 쉘이 최강이다. 화이트헤드(Alfred North Whitehead) 박사가 1911년 썼듯이 “문명은 생각없이 수행할 수 있는 중요한 작업의 수를 확장함으써 발전한다.(Civilization advances by extending the number of important operations which we can perform without thinking about them.)”

grep 사용

The Tao that is seen
Is not the true Tao, until
You bring fresh toner.

With searching comes loss
and the presence of absence:
"My Thesis" not found.

Yesterday it worked
Today it is not working
Software is like that.

haiku.txt 파일이 포함된, 상기 텍스트에서 어떤 명령어가 다음 출력결과를 산출해 낼까요?

and the presence of absence:
  1. grep "of" haiku.txt
  2. grep -E "of" haiku.txt
  3. grep -w "of" haiku.txt
  4. grep -i "of" haiku.txt

find 파이프라인 독해 능력

다음 쉘 스크립트에 대해서 무슨 것을 수행하는지 짧은 설명문을 작성하세요.

find . -name '*.dat' | wc -l | sort -n

ose.dat는 매칭하지만, temp 매칭하지 않기

grep 명령어의 -v 옵션은 패턴 매칭을 반전해서 패턴과 매칭하지 않는 행만 출력된다. 다음 명령어 중에서 어느 것이 ose.dat로 끝나는 (예로, sucrose.dat 혹은 maltose.dat), 하지만 temp 단어는 포함하지 않게 /data 디렉토리에 있는 모든 파일을 찾아낼까요?

  1. find /data -name '*.dat' | grep ose | grep -v temp

  2. find /data -name ose.dat | grep -v temp

  3. grep -v "temp" $(find /data -name '*ose.dat')

  4. 위 어떤 것도 아님.

작은 아낙네

Louisa May Alcott가 지은 작은 아낙네(Little Women)를 친구과 함께 읽고 논쟁중이다. 책에는 Jo, Meg, Beth, Amy 네자매가 나온다. 친구가 Jo가 가장 많이 언급되었다고 생각한다. 하지만, 나는 Amy라고 확신한다. 운좋게도, 소설의 전체 텍스트를 담고 있는 LittleWomen.txt 파일이 있다. for 루프를 사용해서, 네자매 각각이 얼마나 언급된 횟수를 개수할 수 있을까? 힌트: 한가지 해결책은 grep, wc, | 명령어를 동원하는 것이지만, 다른 해결책으로 grep 옵션을 활용하는 것도 있다.