[중요] Shell: I/O Redirection
0. 들어가며
쉘 프로그래밍 카테고리의 직전 포스트는 표준 입출력에 관한 포스트였다. 입출력은 프로그램과 프로그램을 사용하는 유저 모두에게 매우 중요하기에 표준 입출력에 대한 개념은 꼭 유닉스 유저가 아니라도 이해할 필요가 있다. 그것이 저 포스트의 의의라고 생각한다.
하지만 동시에 저 포스트는 유닉스 쉘을 활용하는데 필수적인 I/O Redirection을 이해하기 위한 전 단계이기도 하다. I/O Redirection
은 쉘의 가장 중요한 기능들 중 하나로, 난 이 기능이 없다면 쉘 리눅스를 도저히 쓸 자신이 없다. 차라리 윈도우 GUI로 넘어가겠다. 이 개념은 쉘의 활용성을 극대화해서, GUI가 아니기에 러닝커브가 높을 수밖에 없는 쉘의 단점을 덮어버릴 정도로, 그러니까 그 커닝커브를 극복하는 것이 기꺼이 가치 있을 정도로 의미가 있다. 쉘을 제대로 쓰기 위해서는 이 개념을 자세히 훝고, 또 실제로 사용해야 한다.
이번 포스트에서는 쉘의 I/O Redirection에 대해 자세히 살펴본다. 먼저 그 개념을 훑은 뒤, 프로세스의 입출력을 전환하는 다양한 연산자들을 살펴보고 그 찬란한 활용사례를 살펴보겠다. 또한 여러 프로세스를 연쇄적으로 연결할 수 있는 Pipe 연산자를 알아보고 활용사례를 몇 가지 살펴보도록 하자.
1. I/O Redirection이란?
직전 포스트는 표준 입력과 표준 출력을 다뤘고 그 둘은 프로세스에 따로 값을 주지 않을 경우의 기본 입력과 출력이라고 설명했다. 쉘 프로그램들은 대부분 표준 입력은 키보드로 바로 연결되어 있고, 표준 출력은 화면(콘솔)로 연결되어 있다. 우리가 쉘에 키보드로 명령을 입력하고 화면으로 결과를 본 것은 ‘그냥 당연한거지;’ 이상의 의미가 있었던 것이다.
‘I/O Redirection’에서 I/O
는 Input / Output 의 약자로 입력과 출력을 뜻한다. Redirection
은 ‘재지정’의 의미로 ‘방향을 돌린다’는 의미를 가지고 있다. 이 둘을 합치면 프로세스의 표준 입력 또는 표준 출력을 임의의 입력 또는 출력으로 전환하는 것을 I/O Redirection
이라고 한다.
그럼 그 의미는 무엇일까? 처음 쉘을 다룰 경우, 우리가 사용하는 대부분의 명령어는 화면에 결과를 출력한다. 따로 지정하지 않았기에(아니면 못했기에) 표준 출력인 화면에 결과를 내놓은 것이다. 하지만 때로는 그 결과를 쉘 종료 시 사라지는 휘발적인 화면 위가 아닌, 반영구적으로 저장될 수 있는 파일에 남기고 싶을 때가 있다. 가령, 리눅스 환경에서 서버 접근 로그 파일을 남기고 싶을 경우 화면에 결과를 띄우는 것은 실제 배포 환경에서는 가치가 없을 것이다. 많은 경우, 접근 로그와 어떤 식으로든 발생한 에러 로그를 ‘access.log’, ‘error.log’ 등의 파일에 남긴다. 이런 수북히 쌓인 로그는 향후 트래픽 분석 등의 용도로 사용될 수 있을 것이다. 표준 출력이 아닌 파일로 출력이 전환됐다는 것은 어떤 식으로든 사용자의 조작이 있었다는 것이고, 이것이 곧 Redirection
이다.
우리는 이제 그 ‘조작’하는 방법을 배우고 활용해볼 것이다.
2. 주요 연산자 확인
입출력을 전환하는 것은 쉘에 명령어를 입력한 뒤 입출력을 전환해주는 Phrase를 입력해주어야 한다. 이때 Phrase는 어떤 전환인지 나타내는 전환 연산자와 전환 대상 파일로 이루어진다. 이번 장에서는 전환의 종류가 입력, 출력, 에러일 때의 전환 연산자를 각각 살피고, 이 둘의 중요한 쓰임새들을 추가로 살펴보자.
2.1. >
: 표준 출력 전환 연산자
>
는 표준출력 전환 연산자로 표준 출력을 임의의 대상으로 옮긴다. 명령어의 출력을 화면이 아닌 임의의 다른 파일로 옮기고 싶다면 이 연산자를 사용해 방향을 전환할 수 있다.
예제로 파악해보자. ‘test’라는 폴더 안에 있는 파일들의 목록을 확인하되 그 결과를 화면이 아닌 ‘ls-test.txt’라는 이름의 파일에 저장하고 싶다고 하자. 코드는 정말 간단하다.
$ ls test > ls-test.txt
>
연산자를 사용해 출력 방향을 파일로 옮겼다. 화면에는 어떤 결과도 출력되지 않는다. 출력의 방향이 화면이 아니기 때문이다. 대신 파일의 내용을 확인해보면 원하던 결과물이 들어있음을 알 수 있다.
$ cat ls-test.txt
aws-scripts my_module.py open-tmux ...
2장 설명하기 전까지 무슨 말을 하는지 아리송했다면 이 예제로 한 번에 파악했으리라 생각한다. 아무 디렉토리나 잡고 실제로 해보자. 존재하는 디렉토리를 대상으로 했다면 문제없이 작동한다. 없는 디렉토리를 지정한 경우는 표준 오류 전환 연산자에서 살펴보도록 하자.
2.2. <
: 표준 입력 전환 연산자
>
가 표준 출력을 전환했다면 그를 뒤집은 <
는 표준 입력을 전환하는 연산자이다. 쉘과 쉘에서 실행하는 프로그램은 대부분 표준 입력이 키보드로 설정되어 있는데 입력을 키보드가 아닌 임의의 다른 대상으로 지정할 수 있다.
이번에도 예제로 파악하자. 우리는 cat 이라는 프로그램을 이미 배웠다. 필수 쉘 커맨드를 소개한 포스트에서 cat 은 자연수 개수의 파일을 입력 받아 그 내용을 하나로 묶어서 화면에 출력하는 프로그램이라고 소개했다.
하지만 저 설명은 편의를 위해 설명한 것으로 사실 cat 은 파일의 이름을 입력 받지 않아도 된다. 이때는 프로세스의 입력 방향이 표준 입력인 키보드로 설정되어 있으며 이를 이용해 세상에서 가장 초라한 텍스트 에디터를 만들 수 있다.
cat 의 입력 방향이 키보드일 경우, 사용자는 엔터를 포함해 원하는 텍스트를 계속 입력할 수 있다. 입력을 마치고 싶으면 <CTRL+D>를 입력하면 되는데 이 키는 쉘에서는 파일의 끝을 뜻하는 EOF 문자로 연결되어 있다. 입력이 종료되면 화면에 내가 입력한 것이 그대로 출력된다.
$ cat
I jumped into Han River.(엔터)
Water was very cold, gold and old.(엔터)
Where am I now, water? non-water? Who cares(엔터)
(<CTRL+D> 입력)
I jumped into Han River.
Water was very cold, gold and old.
Where am I now, water? non-water? Who cares
cat 을 단독으로 입력했기 때문에 입력 방향은 키보드로 설정되어 있고, 출력 방향은 표준 출력인 화면이므로 키보드로 입력한 내용이 화면에 바로 출력됐다.
이를 이용하면 파일에 간단하게 한 줄 입력하고 싶을 때 vim
등 기타 에디터를 열지 않고도 파일을 쉽게 만들 수 있다.
$ cat > test.txt
Go away!! # 파일의 내용이 아닌 내가 입력하는 내용임
(<CTRL+D> 입력)
$ cat test.txt
Go away!!
> test.txt
는 표준 출력을 전환하는 Phrase이기 때문에 cat 에는 출력 지정은 있지만 입력은 아예 없다. 그래서 표준 입력으로 입력을 받아 파일에 출력했다.
자, 잡설이 길어졌는데 이 특성을 사용해 <
를 활용해보자. 인자가 없을 때 cat 의 입력이 키보드라고 했다. 그렇다면 입력을 전환해 입력을 키보드에서 임의의 다른 파일로 지정할 수 있다.
$ cat < test.txt
Go away!!
입력의 방향이 키보드가 아니기 때문에 내가 입력할 필요 없이 파일의 내용이 그대로 출력되었다.
여기서 ‘어? 저거 cat test.txt
랑 똑같잖아 임마!’라고 생각이 들어야 한다. 이건 표준 입력이 이상한 것이 아니라 cat 이 그렇게 작동하도록 설정되었기 때문에 그렇다. cat 명령어에는 파일의 이름을 인자로 주면 입력을 그쪽으로 전환하도록 cat 내부에 설정되어 있기 때문에 결과적으로 두 명령어의 결과가 같은 것이다. 한 문제를 해결하는 방법은 여러 가지인 것이 좋고, 지금 이 두 가지 경우가 딱 그 사례인 것 같다. 이는 유닉스에서 지향하는 바이기도 하기에 두 가지 다 알아두는 것이 나쁘지 않다.
다른 사례를 하나 더 살펴보자. 매우 간단한 파이썬 프로그램을 만들텐데 이 프로그램은 이름과 이메일, 직업을 입력 받아 정해진 형식으로 문자열을 출력하는 일을 한다.
# test.py에 내용을 저장한다.
name = input("너의 이름은? : ")
email = input("너의 이메일은? : ")
job = input("너의 직업은? : ")
print()
print(f'{name}이의 이메일은 {email}이고 이 자의 직업은 {job}입니다')
이 test.py
를 실행해서 값을 입력해보자.
$ python test.py
너의 이름은? : 박성환
너의 이메일은? : shoark7@naver.com
너의 직업은? : 백수
박성환이의 이메일은 shoark7@naver.com이고 이 자의 직업은 백수입니다
이런 프로그래밍 예제는 프로그래밍을 처음 배울 때 정말 흔하게 해보는 것 같다. 이 예제의 문제점은 무엇일까? 내 이름과 이메일, 직업 등은 잘 바뀌지 않기 때문에 입력값도 대부분 일정하다. 그럼에도 파이썬 모듈을 실행할 때마다 저 귀찮은 입력을 3줄을 다 해줘야 하는 것이 이 기능의 치명적인 약점이다. 그렇다면 해결 방안이 있을까? 당연, 입력을 전환하자.
현재 저 프로그램은 3줄의 입력을 모두 표준 입력, 즉 키보드로 입력 받고 있다. 그렇다면! 입력을 키보드가 아닌 미리 저장해둔 개인정보 파일로 전환한다면 되지 않을까?
먼저 내 데이터를 data.txt에 저장하자.
$ cat > data.txt
박성환
shoark7@naver.com
백수
(<CTRL+D> 입력)
그 다음 모듈을 실행하되 입력을 이 데이터 파일로 돌려주면!
$ python test.py < data.txt
너의 이름은? : 너의 이메일은? : 너의 직업은? :
박성환이의 이메일은 shoark7@naver.com이고 이 자의 직업은 백수입니다
어떤가? 이런 활용은 문제 예시를 미리 제공하는 알고리즘 문제 해결 사이트들에서도 유용하게 쓸 수 있다.
2.3. 2>
: 표준 에러 전환 연산자
2>
는 표준 에러를 다른 출력으로 전환하는 연산자이다. 이전 포스트에서 표준 출력은 다시 표준 출력과 표준 에러로 구분된다고 했다. 표준 출력은 프로그램이 정상적으로 종료되었을 때 출력되는 방향이고, 표준 에러는 프로그램이 비정상적으로 종료되었을 때 출력되는 방향이다. 가령 cat 명령어로 존재하지 않는 파일을 입력으로 주면 다음과 같은 결과가 나온다.
$ cat no-file.txt
cat: no-file: 그런 파일이나 디렉터리가 없습니다
이 메시지는 현재 디렉토리에 해당 파일이 없기에 프로그램을 실행할 수 없었고, 그래서 화면에 출력된 오류 메시지이다. 유닉스 쉘에서 표준 출력과 표준 에러는 모두 화면으로 설정되어 있다. 그래서 프로그램이 정상적으로 종료하든, 비정상적으로 종료하든 모두 쉘에 결과가 출력된 것이다. 우리는 그동안 그저 ‘어 실행 안 되네?’ 하고 넘어갔지만 말이다.
유닉스에서 표준 출력과 표준 에러를 구분한다는 것은 둘의 출력 방향을 다르게 할 수 있다는 것이 된다. 가령 정상적인 출력은 화면에 출력하되, 오류는 따로 파일에 저장한다는지 하는. 바로 예시를 보자.
$ cat no-file.txt 2> error.log
$ cat error.log
cat: no-file: 그런 파일이나 디렉터리가 없습니다
2>
연산자를 통해 표준 에러를 ‘error.log’라는 파일로 돌렸다. 그래서 없는 파일을 cat 의 인자로 주었는데 오류 메시지가 화면으로 출력되지 않았고 대신 로그 파일에 저장된 것을 확인할 수 있었다.
그러나 만약 같은 명령어에 존재하는 파일을 주면, 2>
는 표준 출력은 건드리지 않고 표준 에러만 건드리기 때문에, 정상적인 출력은 그냥 화면에 나오게 된다.
$ cat yes-file 2> error.log
I exist!!
여기서 전환 연산자에 왜 ‘2’가 붙는지 설명해야 할 것 같다. 유닉스 시스템에는 File Descriptor라는 개념이 존재하는데, 이는 파일이나 소켓과 같은 I/O를 할 수 있는 객체를 유일하게 접근할 수 있는 추상적인 자원번호를 가리킨다. 학교에서 각 반 학생들에게 1번부터 번호를 부여하면 그 번호가 학생을 고유하게 구별하는 것과 같은 이치다.
그런데 표준 입출력, 표준 에러는 고정적인 File Descriptor를 부여받는데, 표준 입력은 0번, 표준 출력은 1번, 표준 에러는 2번이다. 입출력은 어떤 시스템이든 간에 꼭 필요한 기능이기에 고정적인 번호를 부여 받았다.
그래서 표준 에러 전환 연산자에 ‘2’가 붙은 것이다. 표준 출력 연산자가 숫자 없이 >
만 있는 것은 표준 출력 전환은 워낙 빈번히 사용되기 때문에 숫자를 생략한 것이고 1>
과 같이 입력해도 >
과 똑같이 작동한다.
이 개념은 표준 에러와 표준 출력을 동시에 전환하는 2.5 절에서 다시 한 번 살펴보도록 한다.
2.4. >>
의 사용: 덮어쓰기가 아닌 이어붙이기
2.1, 2.2, 2.3 절에서 살펴본 표준 입출력, 표준 에러 전환하는 모두 한 개의 >
또는 <
로 구성되었는데 이들은 파일을 덮어쓴다는 특성이 있었다. 다음 예제를 살펴보자.
$ echo 'hi!' > ok.txt
$ echo 'hi!' > ok.txt
$ echo 'hi!' > ok.txt
$ cat ok.txt
hi!
이전에 echo 명령어는 인자로 받은 텍스트를 표준 출력에 출력하는 명령어로 설명했다. 이 예제에서는 >
를 통해 출력 방향을 파일로 돌렸는데 이때, 같은 명령어를 세 번 실행했음에도, 파일에는 최종적으로 한 줄만 들어있음을 확인할 수 있다. 이것은 >
가 하나만 쓰였을 때는 명령어가 이어붙이기(Append)가 아니라, 덮어쓰기(Write)가 되기 때문이다.
하지만 많은 경우, 가령 로그를 남길 때는 한 줄 입력할 때마다 덮어쓰기가 되는 것보다는 기존 파일의 끝에 이어붙이는 것이 유용할 수 있다. 이때는 덮어쓰는 명령어들에 >
나 <
를 하나씩 더 붙여주면 된다. 표준 출력 전환을 파일에 이어 붙이려면 >>
, 표준 입력 전환을 파일에 이어 붙이려면 <<
, 표준 에러 전환을 파일에 이어붙이려면 2>>
를 입력하면 된다.
# 빈 파일 nofile을 만든다.
$ echo '' > nofile
$ echo 'hi!' >> nofile
$ echo 'hi!' >> nofile
$ echo 'hi!' >> nofile
$ cat nofile
hi!
hi!
hi!
>>
전환자를 통해 파일에 덮어쓰는 것이 아닌 파일의 끝에 ‘hi!’라는 문자열을 3번 이어붙였다. 그 결과 3번의 ‘hi!’가 출력되었다.
2.5. 표준 에러 & 표준 출력을 동시에 전환하기
앞서 2.1절과 2.3절을 통해서 표준 에러와 표준 출력을 전환하는 방법에 대해 살펴봤다. 표준 출력은 >
전환자, 표준 에러는 2>
연산자를 사용했다. 그런데 이 연산자 자체로는 서로 상대방의 출력 전환에는 영향을 주지 못한다는 문제가 있다. 가령 >
는 표준 출력만 전환하기 때문에 표준 에러 전환에 영향을 미칠 수 없고, 2>
는 반대로 표준 출력 전환에 영향을 줄 수 없다.
하지만 이 둘을 동시에 어떤 파일로 전환하거나, 한 번에 서로 다른 파일에 전환해야 하는 경우가 충분히 발생할 수 있다. 그래서 이번 절에서는 둘을 동시에 같은 파일로 전환할 때와, 서로 다른 방향으로 전환할 때를 구분해서 살펴보도록 하겠다.
2.5.1. 동시에 한 파일로 전환하기
표준 출력과 표준 에러를 동시에 한 파일로 전환하고 싶을 수 있다. 이때는 &>
연산자를 사용하면 된다.
# no-file <- 존재하지 않는 파일
# yes-file <- 존재하는 파일
$ cat no-file &>> results.txt
$ cat yes-file &>> results.txt
cat: no-file: 그런 파일이나 디렉터리가 없습니다
I exist!!
&>
또한 이어붙이기로 기능을 전환하고 싶다면 &>>
를 사용하면 된다. ‘no-file’은 존재하지 않는 파일이기 때문에 cat 은 표준 에러로 결과를 출력하는데 &>
는 표준 에러를 전환하기 때문에 결과 파일로 출력이 전환됐다. 동시에, 표준 출력도 같이 전환하기 때문에 존재하는 파일 ‘yes-file’을 cat 처리했을 때도 결과 파일로 출력이 전환됐다.
연산자 앞이 왜 ‘&’일까 조금만 생각하면 충분히 납득할 수 있다. ‘&’는 프로그래밍과 논리식에서 AND(그리고)를 의미한다. 파이썬에서 ‘&’는 Bitwise AND 연산을 처리한다. 그러니까 표준 출력(‘>’) 그리고 표준 에러(‘2>’)를 동시에 전환하는 연산자에 ‘&’ symbol을 사용한 것은 꽤나 재치 있는 것 같다.
그리고 유닉스 배포판에 따라 ‘&>’ 대신 ‘>&’가 맞는 전환 연산자일 수도 있고 내 우분투처럼 둘 다 지원하기도 한다.
2.5.2. 동시에 서로 다른 파일에 전환하기
어떤 명령어를 실행하되 정상적인 결과는 결과를 담는 파일에, 비정상적인 결과는 에러를 담는 파일에 따로 저장하고 싶을 수 있다. 대표적인 경우가 서버에서 로그를 접속 로그(access.log)와 에러 로그(error.log)로 나누어 담는 경우를 생각해볼 수 있다. 접속 로그는 접속 트래픽을 분석하는 데 쓰일 수 있고, 에러 로그는 어떤 에러가 어떤 빈도로 발생하는지 분석할 때 사용될 수 있다.
이때는 표준 에러와 표준 출력 전환을 한 명령에 동시에 진행하면 된다.
cat exists_or_not.txt > access.log 2> error.log
이 명령은 명령어가 정상적인 결과를 출력한다면 ‘access.log’ 파일로, 비정상적인 결과를 출력하면 ‘error.log’ 파일로 출력을 전환한다. 둘은 순서가 바뀌어도 상관은 없다.
근데 주의할 점은 만약 저 파일이 존재한다면 ‘access.log’ 파일로 출력을 전환하는데 그러는 동시에 ‘error.log’는 빈 파일로 덮여쓰여진다는 점이다. 표준 출력에는 출력이 있어서 전환이 됐지만 동시에 표준 에러는 출력할 값이 없어서, 그러니까 빈 값이어서 빈 값이 ‘error.log’에 뒤집어 씌어지는 것이다.
그렇기에 저 명령식에서는 이어붙이기로 출력 전환 방식을 지정하는 것이 일반적으로 좋겠다.
cat exists_or_not.txt >> access.log 2>> error.log
3. 명령어 연결하기: Pipe 연산자
2장에서는 명령어(사실 이것들은 프로그램이고, 실행 중인 프로그램은 프로세스이기 때문에 프로세스라고 해도 맞다.)의 표준 입출력, 표준 에러를 다른 ‘파일’로 전환하는 방법을 알아보았다.
이것도 아름답지만 화룡점정으로 명령어들을 연쇄적으로 이어붙이는 것이 가능하다. 프로그램들은 입력 받고 그 입력에 연산을 가해 출력을 반환하는데 지금까지 우리는 그 입력을 키보드나 파일에서만 받고, 출력은 화면이나 파일로만 했다. 이런 방식은 한 명령어 입력으로 한 가지 작업만 할 수 있는 ‘작은 작업’이다. 하지만 데이터 전처리같은 일을 해본 사람들은 이런 큰 작업들이 단일 명령어로 되는 일이 아니라는 것을 잘 알고 있을 것이다. 수많은 작은 기능들을 계속 On-going으로 작업하면서 결과를 얻을 수 있다.
유닉스 쉘에서는 한 줄의 명령으로 한 가지 작업만 하는 것이 아니라 이 들을 연쇄적으로 이어서 한 번에 큰 작업을 할 수 있는 기능을 지원한다. 이는 Pipe 연산자 ‘|‘의 힘으로 가능하다.
파이프 연산자는 명령의 출력을 다른 명령의 입력으로 전환하고 이를 원하는 만큼 연쇄적으로 가능하게 해서 한 번의 입력으로 많은 작업이 가능하게 한다.
가령 현실의 문제를 가정해보자. ‘수많은 고객의 이름 정보를 담은 파일이 있을 때 이 중에서 이름이 ‘F’로 시작하는 사람의 수를 세고 싶다.’ 이 문제를 맞닥뜨리고 당신은 어떻게 할 것인가? 언제나 정답은 최소 단위의 작은 부분문제로 문제들을 쪼개나가라는 것이다. 난 저 문제를 세 과정으로 나눠서 보겠다.
- 파일의 내용을 디스크에서 읽는다.
- 그 내용을 가지고 이름이 ‘F’로 시작하는 사람만 추린다.(filter)
- 그 추린 사람들의 이름을 센다.
이렇게 문제를 해결하다보면 최종적으로 우리의 문제가 능히 해결될 것 같다. 여기서 내가 강조하고 싶은 것은 각 중간과정에서 이전 단계의 출력을 자신의 입력으로 사용한다는 것이다. 2번 과정은 1번이 읽은 내용을 사용하고, 3번은 2번이 출력한 추린 이름을 자신의 입력으로 사용한다. 외담으로 경영에서는 이런 중간제품을 재공품(work in process)라고 한다.
Pipe 연산자가 하는 일이 딱 이거다. Pipe 연산자는 명령어들을 이어, 이전 명령의 중간 출력물을 다음 명령의 입력으로 전환해준다. 이 포스트의 로고 사진이 파이프를 배관하는 게임인 것은 다 이유가 있는 것이다. 파이프를 다양한 방향으로 잇고 이어서 우리가 원하는 최종적인 방향으로 결과물이 나오게 할 수 있다.
위의 예제를 리눅스 쉘에서 실행해보자. 먼저 사람들의 이름을 담은 ‘names.txt’라는 파일을 준비했다. 예제를 위해 나는 ‘A’부터 ‘Z’까지의 철자로 시작하는 임의의 단어를 3개씩 골라 파일에 담겠다.
그리고 각 과정에 맞는 명령어를 선택했다.
- cat 명령어로 파일을 읽는다.
- 입력 받은 파일의 내용 중에서 정규표현식의 패턴에 일치하는 줄만을 출력하는 grep 프로그램으로 ‘F’로 시작하는 이름만 추린다.
- 입력 받은 파일의 글자 수, 단어 수, 줄 수를 세는 기능인 wc 프로그램으로 이름의 수를 센다.
이 과정을 실제 코드로 확인하자.
$ cat names.txt | grep '^F' | wc -l
3
grep 이나 wc 를 처음 보는 사람들이 있을 수 있기에 해설을 하자면,
- cat 명령어로 파일을 읽는다. 출력이 ‘|’ 연산자로 다음 명령어인 grep 명령어의 입력으로 전환됐다.
- grep 명령어로 파일의 이름 목록 중에서 ‘F’로 시작하는 단어만 고른다.(filter) ‘^F’는 정규표현식 패턴으로 단어의 어떤 패턴(여기서는 ‘F로 시작한다’)을 나타내는데 이 패턴에 매칭되는 이름들만 출력된다. 이 명령어는 너무 중요해서 향후 독립적인 포스트에서 단독으로 다룰 예정이다. 일단 넘어가자. 그리고 grep 커맨드의 출력은 또다시 ‘|‘를 통해 wc 의 입력으로 전환된다.
- 이전 명령에서 ‘F’로 시작하는 이름만을 넘겨받았다. 이제는 그 이름의 수를 세야 한다. wc 명령어는 파일의 글자 수, 단어 수, 줄 수를 모두 출력하는데 ‘-l’(line을 의미함) 옵션을 주면 줄 수만을 출력한다. wc 도 중요한 명령어에 속하기 때문에 향후 독립적인 포스트에서 다른 중요한 기능들과 같이 다룰 예정이다. 파일에 ‘F’로 시작하는 이름은 3개가 있었기 때문에 숫자 3이 출력된다.
Pipe 연산자(‘|‘)을 통하면 출력이 유효한 이상 작업을 연쇄적으로 계속 이어나갈 수 있고, 이번 예제보다 훨씬 더 복잡하고 기상천외한 작업을 해볼 수 있다. 사실 이번에는 우리가 다뤄 본 커맨드가 워낙 적어서 더 멋있는 예제를 시도해볼 수 없었다. 이것도 나름 고민한 예제이다. 나중에 쉘의 활용성을 높일 수 있는 주요 커맨드들을 더 공부하면서 ‘|‘는 계속 써볼 것이다.
유닉스 철학에는 ‘Rule of modularity’ 또는 ‘Rule of composition’이라는 법칙이 있는데 각 기능은 최대한 한 기능만을 수행하도록 작게 만들고 이들을 구성해서 더 큰 일을 하도록 하자는 것이다. Pipe 연산자는 이 철학을 실현해주는 쉘의 정말 중요한 기능이다.
4. 마치며
이번 포스트는 Redirection, 주요 Redirection 연산자, Pipe 연산자에 대해 알아보았다. 계속 강조하지만 만약 유닉스 쉘을 잘 쓰고 싶다면 이 기능은 필히 마스터해야 한다. 그래서 이번 포스트는 의미 있고 중요하다. 이후 ‘확장’이라는 개념과 합쳐지면 쉘의 뽕맛은 극대화되고 윈도우 같은 GUI로 넘어갈 생각을 하지 못하게 된다.
사실 시간이 오래 걸릴 것 같은 Redirection 포스트보다는 중요하고 재밌는 쉘 커맨드나 ‘alias’를 만드는 등의 짧고 현실적인 수요가 있는 포스트들부터 먼저 작성하고 싶었다. 하지만 저런 커맨드들은 Pipe 연산자를 써야 의미 있고 더 재미 있다. 그래서 굳이 Redirection편을 먼저 작성했다. 이제 본격적으로 쉘의 다른 커맨드들을 배워보고 이들을 연결해서 신기한 작업을 해보도록 하자.
이상 Redirection 포스트를 마친다.