elsa in mac

[Linux 초보 탈출] 쉘(shell)에서 명령어의 입출력을 제어하는 pipe 그리고 redirection 본문

Linux/Linux 팁

[Linux 초보 탈출] 쉘(shell)에서 명령어의 입출력을 제어하는 pipe 그리고 redirection

elsa in mac 2025. 7. 22. 23:51

 

이번 포스트에서는 쉘(shell)에서 명령어(command)의 입출력(input/output)을 제어하는 pipe와 redirection이라는 개념에 대해 알아보도록 하겠습니다. 

리눅스를 사용하다 보면, 자연스럽게 Terminal을 자주 그리고 오래 사용하게 됩니다. 이유는 대다수의 많은 도구들이 GUI 보다는 Text 기반의 CLI/TUI 들이기 때문이고. Linux를 사용하는 컴퓨터의 환경을 관리하거나 꾸미거나 하는 등등의 작업들이 대부분 terminal에서 이뤄집니다.  그래서 Linux를 잘, 그리고 능숙하게 사용하려면 Terminal을 잘 사용해야 하는데, "Terminal을 잘 사용한다"는 의미는 Terminal 프로그램을 잘 쓸 줄 안다는 의미보다는 Terminal에서 하고자 하는 작업을 능숙하고 빠르게 할 줄 알아야 한다는 의미입니다. 

그러다 보니, Terminal 환경에 대한 것들을 이해할 필요가 있습니다. 앞으로 몇 차례 포스팅을 통해 Linux에 입문하고자 하는 분들을 위해 이러한 기초적인 것들을 다루게 될 것입니다. 

 

shell

Linux를 설치하고 Termial 앱을 실행 시키면, 프롬프트가 표시되고 커서만 달랑 나오게 됩니다. 

터미널을 실행 !

여기서부터 이제 뭔가를 해야 하는데.. 이것은 엄밀히 말하면 shell이라는 프로그램이 실행되어 있는 것입니다. 말하자면 Prompt와 커서가 Shell의 Inteface인 셈입니다. 

우리가 컴퓨터를 사용한다는 것은 바꿔말하면 컴퓨터와 대화를 한다는 것이고, 대화를 한다는 것은 컴퓨터에게 명령하고 컴퓨터가 처리한 명령의 결과를 확인하는 과정이라고 볼 수 있습니다. 그런데 인간이 이해하는 언어와 컴퓨터가 이해할 수 있는 언어는 다르기 때문에, 이 둘 간의 중재자가 필요합니다. 사람이 이해할 수 있는 언어와 표현으로 컴퓨터에 요청하면 컴퓨터는 자신의 언어와 표현으로 대답을 하는데, 이것은 다시 인간이 이해할 수 있는 언어로 바뀌어야 합니다.  이러한 대화의 중재자 역할을 담당하는 것이 바로 shell입니다. 

Shell의 사전적 의미는 누구나 알다시피 "조개껍질" 이라는 뜻입니다. 컴퓨터와 인간 사이에 대화의 중재자 역할을 담당하는 녀석의 이름을 왜 shell이라고 했을까?

운영채제의 핵심을 우리는 kernel이라고 부릅니다. Kernel의 사전적 의미가 "핵심" 또는 "씨앗" 이라는 의미이지요. 엄밀히 말해 우리가 부르는 Linux도  바로 이 kernel을 의미합니다. Kernel은 컴퓨터 하드웨어와 직접 소통하며, 메모리 관리, 프로세스에 대한 스켸쥴링, 입출력 처리등을 담당하는 운영체제(OS)의 가장 기본적이고 근본적인 역할을 수행하는 core 입니다. 누가 이 용어를 처음 사용했는지는 확실하지 않습니다.  조개의 핵심.. 즉, 조개의 가장 깊숙한 곳에 진주가 있고, 이것을 덮고 있는 것이 조개 껍질이라고 본다면, 진주는 kernel 이고 kernel을 덮고 있는 것이 바로 shell 이라고 볼 수 있습니다. shell 이라는 개념은 프랑스 국적의 컴퓨터 과학자인 루이 푸쟁이 1960년대 중반 Multics(멀틱스) 라는 운영체제를 개발하면서 shell이라는 용어를 처음으로 사용했습니다.  이후, Unix 운영체제에서 쉘에 대한 개념과 역활 그리고 기능이 더욱 발전하게 됩니다.  Unix의 최초 shell은 1970년 초, Ken Thompson 이 만든 Thompson shell(sh) 이였고, 이 후 Stephen Bourne이 70년대 말 Bourne Shell(sh)을 통해 Script 기능을 대폭 강화 합니다. 이 후 다양한 shell들이 등장했고, 1989년에 Brian Fox가 GNU project를 위해 Bourne shell을 기반으로 Bash(Bourne Again SHell)을 개발하기에 이릅니다. 

 

pipe

서두에 쉘에서 명령어의 입출력을 제어하는 기능에 pipe 가 있다고 언급을 했었지요. 

Unix 운영체제가 개발되던 초창기, 개발자들은 Unix가 갖춰야 할 기능을 구현함에 있어 철학적인 개념 정립에도 매우 진지했습니다. Unix의 이러한 철학과 깊이 있는 개념 정립은 이후, 많은 OS에 영향을 주었고 오늘날 우리가 사용하고 있는 컴퓨터 운영체제의 기반이 되었습니다. 

당시에는 대부분의 데이터들은 Text 기반의 문자열이었고, 만들어야 할 기능들이 꽤나 많았습니다. 당시 CPU의 성능도 Memory의 용량도 매우 작았기 때문에 지금처럼 하나의 프로그램에 다양하고 복잡한 기능을 다 담을 수가 없었습니다. 개발자들의 의욕과 열정이 과도하면 너무 많은 것들을 담으려 하는 경향이 있지요, 

Unix 개발에 참여했던 벨 연구소(Bell Labs)의 Douglas Mcllroy(더글라스 메클로이)는 프로그램들이  오직 한 가지 특화된 기능에 집중하기를 원했습니다. 대신, 이러한 특화된 기능에 집중된 프로그램들을 상호 연결해서 궁극의 복잡한 문제를 해결하는 것이 모든 면에서 좋다고 판단을 했습니다.  대신 이렇게 상호 협업을 하기 위해서 프로그램들은 입력과 출력 모두에서 Text stream을 처리할 수 있는 기능을 필수적으로 갖추고 있어야 한다고 생각했습니다.  당시만 해도 한 프로그램이 출력은 임시 파일로 저장되고, 다른 프로그램이 그 저장된 임시 파일을 읽어와서 처리하는 식이였습니다. 당시의 저장장치는 지금처럼 빠르지도 않았기에 데이터를 처리하는 시간보다 파일에 저장하고 다시 읽어오는 시간이 더 길었습니다. 

메클로이는 이러한 철학을 달성하기 위해 한 프로그램의 결과(출력)가 다른 프로그램의 입력으로 직접 연결될 수 있는 메커니즘을 구상하게 되는데, 이것이 바로 Pipe라는 개념입니다.   pipe는 중간 단계인 결과를 저장하고 또다시 이 결과를 읽어 들이는 과정을 생략하고 메모리 상에서 직접 데이터를 보내고 받는 방법으로 기존의 방식보다 매우 효율적이고 효과적인 결과를 만들 수 있게 되었습니다. 

쉘에서 pipe는 | 키를 사용합니다. 

shift + \ = pipe

pipe를 사용하는 예를 하나 보겠습니다. 

스샷을 보면, ls 명령으로 현재 디렉터리의 파일과 하위 디렉터리 목록을 볼 수 있는데요.  다음 prompt에서 아래와 같은 명령을 내린 것을 알 수 있습니다. 

ls | grep ".txt" | sort

ls 명령을 수행하게 되면 현재 디렉터리의 파일몰록을 출력하게 되는데, 이 출력을 표준 출력장치인 terminal로 보내는 것이 아니고, pipe를 이용하여 grep 명령의 입력으로 보내게 됩니다. grep은 입력 문자열에 대해 사용자가 정의한 패턴에 해당하는 정보만 필터링하여 결과를 출력해 주는 명령어입니다. 그런데 그 출력을 역시 표준 출력장치인 termial로 보내지 않고, 다시 pipe를 통해 sort 명령의 입력으로 보냅니다. sort는 입력받은 문자열을 올림차순으로 출력합니다. sort 다음에는 pipe 가 없으므로 표준 출력인 terminal로 출력하게 됩니다. 

또 다른 예를 하나 보겠습니다.

grim 은 화면을 screen capture 하는 도구입니다. -g 옵션을 주고 " " 안에 capture 할 영열에 대한 geometry 정보, 예를 들어 "100, 200, 800x600"로 명령을 내리면, x=100, y=100 위치에서 width(폭) 800, height(높이) 600 픽셀 영역을 캡처합니다. 그리고 - 는  캡처한 이미지를 파일로 저장하지 말고, 표준 출력으로 보내라는 의미입니다.  swappy는 이미지 편집 GUI 프로그램으로 -f 는 이미지의 파일 경로를 지정하는 옵션이고 - 이므로 표준 입력으로부터 이미지 데이터를 읽어와 표시하라는 의미가 됩니다. 

즉, 위의 명령을 실행하게 되면, slurp에 의해 화면에 + 모양의 마우스 커서가 나타나고 사용자는 capture 하고자 하는 영역을 드래그 앤 드롭하게 됩니다. slurp의 결과는 사용자가 선택한 영역에 대한 geometry 값이 출력되는데, 이 출력을 이용하여 grim 이 실체 해당 영역을 스크린 캡처하게 됩니다. 그 결과는 이미지 데이터이지만 파일로 저장하지 않고 표준 출력으로 보냅니다. pipe로 swappy로 보내라고 되어 있으니 swappy는 표준입력 즉, pipe를 통해 전달된 이미지 데이터를 받아 해당 이미지를 표시하게 됩니다. 

말하자면, 저 한 줄이 screen capture 프로그램인 셈입니다. 

grim -g "$(slurp)" - ❘ swappy -f - 의 결과

다른 예를 하나 더 보겠습니다. 

ps 명령은 실행 중인 process의 목록을 출력해 주는 것인데, ps에 aux 옵션을 주면 현재 컴퓨터에서 실행 중인 모든 프로세스의 목록을 출력합니다. 이 결과는 pipe로 grep "omega"로 입력되고, grep은 이 목록 중에 omega가 포함된 line 만 필터링하게 됩니다. 그리고 그 필터링된 결과는 다시 pipe로 grep -v "grep"으로 보내지게 되는데, 이 명령은 해당 문자열 목록 중에서 "grep"이 포함된 라인을 제외시킵니다. 그리고 그 결과는 또다시 awk로 보내지게 되는데 awk는 한 문자열에서 공백으로 구분된 2번째 필드만 추출합니다. ps aux의 결과에서 두 번째 필드는 해당 프로세스의 ID 정보(PID)이므로 PID 정보로만 구성된 문자열 목록이 pipe로 다시 xargs kill로 보내지게 됩니다. 그리고 결국 해당 PID에 해당하는 프로세스들은 kill 명령에 의해 종료되게 됩니다. 

정리하자면, 위의 명령어 셋은 "현재 실행 중인 프로세스 중에 omega가 포함된 프로세스들만 모두 종료시켜라"라는 의미가 됩니다. 

마지막으로 pipe를 이용하는 예를 하나 더 보겠습니다. 

du 명령은 현재 디렉터리에 포함된 파일들과 디렉터리의 크기를 표시해 주는 명령입니다. 그 출력 결과는 pipe로 sort로 전달되는데, sort는 입력받은 목록을 -r(reverse) 즉, 내림차순으로 정렬하고 그 결과를 pipe에 의해 head 명령으로 보냅니다. head는 입력된 문자열 목록 중 -n 10 즉, 처음부터 10개만 표시하게 합니다. 

따라서, 위의 명령어 셋은 "현재 디렉토리의 파일을 크기를 기준으로 내림차순으로 정리하고 가장 크기가 큰 파일부터 10개의 정보만 표시해 줘"라는 것이 됩니다.  결과는 아래와 같습니다. 

 

 

redirection

redirection의 사전적 의미는 방향전환입니다. 의미 그대로 redirection이라는 개념은 프로그램에 입력, 출력, 에러의 방향을 변경하는 것으로 일반적으로 입력과 출력은 모두 표준입력(stdin), 표준출력(stdout), 표준에러(stderr)로 표준입력은 키보드, 표준출력과 표준에러는 terminal 입니다. 

표준입력과 표준출력은 이해를 하겠는데, 표준에러는 뭘까?
Unix와 Unix-like(Linux) 시스템에서 모든 프로그램은 실행될 때 3개의 데이터 스트림을 갖게 됩니다. 표준입력, 표준출력, 그리고 표준에러. 표준입력은 데이터를 읽어 들이는 통로로 기본은 키보드입니다. 표준출력은 프로그램의 정상적인 결과를 내보내는 통로로 Terminal이며, 파일 디스크립터(File Descriptor) 번호는 1입니다. 표준에러는 프로그램의 에러 메시지나 진단정보를 내보내는 통로로 표준출력과 동일한 Terminal이기는 하지만 파일 디스크립터 번호가 2입니다.  파일 descriptor란 입출력 자원(Input/Output resource)을 추적하고 관리하기 위한 식별표입니다. 

경우에 따라서 이 표준입력과 출력을 바꿈으로써, 작업의 효율성을 높일 수 있게 되는데, redirection은 앞에서 잠시 언급했던 Unix 최초의 shell을 개발한 Bell 연구서의 Ken Thompson에 의해 제안되었습니다. 

pipe가 프로그램들 간의 입력과 출력을 연결하는 것이라면, redirection은 단일 프로그램이 입력 혹은 출력의 방향을 변경하는 것으로 > (출력 redirection)와 < (입력 redirection)를 사용합니다. 

ls는 현재 디렉터리(폴더) 의 파일과 서브 디렉토리 목록을 출력하는 명령인데, > files_list.txt라고 했으니, terminal로 출력하지 않고, files_list.txt라는 파일을 생성하고 해당 파일에 출력합니다. 

만일, 실시간으로 log 데이터를 출력하는 프로그램이 있다고 해 봅시다. 출력이 끊임 없이 출력되기 때문에 이 결과를 실시간으로 저장하지 않는다면 나중에 데이터를 분석할 수 없을 것입니다. 이렬 경우 redirection 기능이 없다면 정말 끔찍하겠죠. 

sort를 실행하면, 키보드로부터 데이터 입력을 대기 합니다. 사용자는 데이터를 키인하고 Ctrl+d 를 누르면 지금까지 입력한 데이터를 올림차순으로 정렬하여 결과를 출력합니다.  위의 스샷을 보면 < saved_data.txt 라고 되어 있으니, 입력을 키보드로 부터 받지 않고, saved_data.txt로부터 받게 됩니다. 

위의 스샷을 보면, > 가 아니고 >> 을 사용하는 것을 알 수 있는데,  >> 를 "표준 출력 추가 리디렉션"이라고 합니다. 즉, 좌측이 출력을 우측의 출력 끝에 추가하는 것으로, 위의 예는 test2.txt의 내용을 test1.txt의 맨 마지막 줄에 추가하라는 명령이 됩니다. 

pingping이라는 명령은 없기 때문에 표준에러로 에러메시지를 출력하게 되는데, 앞서 언급했듯이 에러 메시지는 동일하게 terminal로 출력을 하지만 file descriptor가 2번 입니다. 즉, 2> 라는 의미는 표준에러 스트림만 error.log로 redirection 하게 됩니다.

예를 들어, 어떤 프로그램이 실행되면 정상 log와 오류 발생 시 오류 메시지가 terminal에 출력되게 될 터인데, 이 경우 모두 terminal에 출력을 하게 되므로 정상 메시지와 에러 메시지가 섞여 있게 되고, 나중에 log를 분석할 때 어디서 어떤 오류가 발생했는지를 찾는 게 힘들 수 있습니다. 이 경우 위와 같이 2> 를 사용하면 실행 중 발생하는 에러 메시지(file descriptor 2번)만 파일로 저장되기 때문에 분석이 훨씬 효율적일 수 있습니다. 만일 정상메시지와 오류메시지를 구분하여 모두 redirection 하고 싶다면 아래와 같이 하면 됩니다. 

반면, 정상 메시지와 에러 메시지를 모두 하나의 파일로 redirection 하고 싶다면.. 아래와 같이 합니다. 

ping > message.log로 명령을 내리게 되면, 에러 메시지는 빠진 정상 출력만 redirection 하게 됩니다. 

예를 들어, 어떤 프로그램을 실행하면 엄청나게 많은 메시지들이 실시간으로 출력된다고 가정해 봅시다. 사실은 출력되는 메시지는 관심이 없는데 말이죠? 대부분의 프로그램들은 출력 메시지를 막는 옵션을 갖고 있는 경우가 많습니다. 하지만, 어떤 프로그램은 그런 옵션이 없다면? 

이럴 경우에는 redirection 기능을 이용하여 표준출력과 표준에러를 terminal이 아닌 다른 곳으로 보내면 되겠죠? 그래서 Linux에서는 null 장치 혹은 blackhole 이라는 것을 갖추고 있습니다. 

위의 스샷은 &> /dev/null 이므로, 표준출력과 표준에러 출력을 모두 null 장치로 보내게 됩니다. 따라서, 어떠한 메시지도 terminal에 출력되지 않게 됩니다.  정상 메시지만 보내고 싶다면 > /dev/null, 에러 메시지만 보내고 싶다면 ? 네 그렇쵸 2> /dev/null 해 주면 됩니다. 

 

- - - - - 

이번 포스트에서는 shell에서 사용하는 입출력제어의 대표적인 개념인 pipe와 redirection에 대해 살펴봤습니다. Linux를 사용하면서 여러 가지 문제에 봉착하고 그에 대한 해결책을 찾기 위해 AI의 도움을 받거나 혹은 Community에서 해답을 찾기 마련인데, pipe나 redirection과 관련된 표현이 나오더러도 이젠 이게 뭐지? 하지 않으실 수 있을 겁니다. ^^

공유하기 링크
Comments