리눅스 파이프는 명령 줄 유틸리티들이 상호 작용하는 방식을 혁신적으로 변화시킵니다. 각각 독립적으로 작동하는 명령들을 연결하여 하나의 강력한 팀으로 만들어 복잡한 작업을 단순화하고 생산성을 극대화할 수 있습니다. 이 글에서는 리눅스 파이프의 작동 원리와 실제 활용법을 자세히 살펴보겠습니다.
파이프, 어디에나 존재하는 핵심 기능
파이프는 리눅스와 유닉스 계열 운영체제에서 가장 핵심적인 명령 줄 기능 중 하나입니다. 리눅스 관련 기사를 접할 때마다 파이프가 얼마나 자주 사용되는지 확인할 수 있습니다. How-To Geek의 리눅스 관련 글들을 살펴보면 거의 모든 기사에서 다양한 방식으로 파이프가 활용되고 있습니다.
리눅스 파이프를 사용하면 기본적으로는 지원하지 않는 작업들을 수행할 수 있게 됩니다. 리눅스의 설계 철학은 각자의 기능을 수행하는 작은 유틸리티들을 조합하여 사용하는 것입니다. 각각의 유틸리티는 고유한 기능을 가지고 있으며 파이프를 통해 이들을 연결하면 마치 팀처럼 협업하여 복잡한 작업을 처리할 수 있습니다. 파이프를 통해 한 명령의 출력을 다른 명령의 입력으로 연결하여 여러 명령을 조합해 사용하면 원하는 결과를 얻을 수 있습니다.
간단한 파이프 사용 예시
다양한 파일이 혼합된 디렉토리가 있다고 가정해 봅시다. 이 디렉토리에서 특정 파일 형식의 개수를 알고 싶을 때, 파이프를 사용하면 쉽게 해결할 수 있습니다. 물론 다른 방법도 있지만 파이프를 소개하는 것이 목적이므로 파이프를 활용하여 해결해 보겠습니다.
먼저, `ls` 명령어를 사용하여 파일 목록을 얻습니다.
ls
이제 `grep` 명령어를 사용하여 관심 있는 파일 형식(예: 확장자에 “page”가 포함된 파일)을 필터링합니다.
특수 문자 `|`를 사용하여 `ls` 명령어의 출력을 `grep` 명령어의 입력으로 전달합니다.
ls | grep "page"
`grep` 명령어는 검색 패턴과 일치하는 행을 출력하므로, 확장자에 “.page”가 포함된 파일 목록만 표시됩니다.
이 간단한 예시를 통해 파이프의 강력한 기능을 확인할 수 있습니다. `ls` 명령어의 출력은 터미널 창으로 직접 출력되는 대신, 파이프를 통해 `grep` 명령어로 전달되었습니다. 최종 출력은 파이프 연결의 마지막 명령어인 `grep`의 결과입니다.
파이프 체인 확장하기
파이프로 연결된 명령어 체인을 확장해 보겠습니다. 이제 `.page` 파일의 개수를 세기 위해 `wc` 명령어를 추가해 보겠습니다. `wc` 명령어에 `-l` 옵션을 추가하여 줄 수를 세고, `ls` 명령어에도 `-l` 옵션을 추가합니다. 이 옵션은 곧 사용하게 될 것입니다.
ls -l | grep "page" | wc -l
`grep`은 더 이상 마지막 명령어가 아니므로 출력이 화면에 표시되지 않고, `grep`의 출력은 `wc` 명령어의 입력으로 전달됩니다. 터미널 창에 표시되는 최종 출력은 `wc` 명령어의 결과입니다. `wc` 명령어는 해당 디렉토리 안에 69개의 “.page” 파일이 있다고 알려줍니다.
이번에는 명령어 체인을 더 확장해 보겠습니다. `wc` 명령어를 제거하고 `awk` 명령어로 대체합니다. `-l` 옵션을 포함한 `ls` 명령어의 출력은 9개의 열로 이루어져 있습니다. `awk`를 사용하여 5번째, 3번째, 9번째 열을 출력하여 파일 크기, 소유자, 이름을 나타내도록 하겠습니다.
ls -l | grep "page" | awk '{print $5 " " $3 " " $9}'
이제 각 파일에 대한 해당 열들의 목록을 얻을 수 있습니다.
이 출력을 `sort` 명령어를 통해 전달하여 크기 순으로 정렬해 보겠습니다. `-n` 옵션을 사용하여 첫 번째 열을 숫자로 취급하도록 합니다.
ls -l | grep "page" | awk '{print $5 " " $3 " " $9}' | sort -n
이제 출력 결과가 파일 크기 순으로 정렬되고, 사용자가 선택한 3개의 열만 표시됩니다.
더 많은 명령어 추가
마지막으로 `tail` 명령어를 추가하여 출력 결과의 마지막 5줄만 표시하도록 하겠습니다.
ls -l | grep "page" | awk '{print $5 " " $3 " " $9}' | sort -n | tail -5
이 명령어는 “현재 디렉토리에서 가장 큰 5개의 `.page` 파일을 크기순으로 표시하라”는 의미로 해석할 수 있습니다. 이러한 기능을 수행하는 단일 명령어는 없지만, 파이프를 사용하여 직접 만들어냈습니다. 자주 사용하는 명령어는 별칭이나 쉘 함수로 등록하여 타이핑 시간을 절약할 수 있습니다.
최종 출력은 다음과 같습니다.
`sort` 명령어에 `-r` 옵션(역순 정렬)을 추가하고, `tail` 대신 `head`를 사용하여 출력 결과의 첫 번째 줄부터 선택하여 크기 순서를 반전시킬 수도 있습니다.
이제 가장 큰 5개의 `.page` 파일이 크기가 큰 것부터 작은 것 순으로 나열됩니다.
최근 사용 예시
최근 How-To Geek 기사에 소개된 흥미로운 두 가지 예시를 살펴보겠습니다.
일부 명령어(예: `xargs`)는 파이프를 통해 입력을 받을 수 있도록 설계되었습니다. 다음은 여러 파일의 단어, 문자, 줄 수를 계산하는 방법입니다. `ls`의 출력을 `xargs`로 파이핑하면 파일 이름 목록이 `wc` 명령어의 매개변수로 전달됩니다.
ls *.page | xargs wc
단어, 문자, 줄의 총 개수가 터미널 창 하단에 표시됩니다.
다음은 현재 디렉토리에 있는 고유한 파일 확장자의 정렬된 목록과 각 유형의 개수를 표시하는 방법입니다.
ls | rev | cut -d'.' -f1 | rev | sort | uniq -c
많은 작업이 한 번에 이루어집니다.
`ls`: 디렉토리에 있는 파일들을 나열합니다.
`rev`: 파일 이름을 반전시킵니다.
`cut`: 지정된 구분자(“.” )가 처음 나타나는 위치에서 문자열을 자릅니다. 이후의 텍스트는 삭제됩니다.
`rev`: 파일 확장자인 나머지 텍스트를 반전시킵니다.
`sort`: 목록을 알파벳순으로 정렬합니다.
`uniq`: 목록에서 고유한 항목의 수를 세어 표시합니다.
출력 결과는 파일 확장자 목록과 각 고유한 파일 형식의 개수를 알파벳순으로 정렬하여 보여줍니다.
명명된 파이프
명명된 파이프라는 또 다른 유형의 파이프를 사용할 수 있습니다. 이전 예시에서 사용한 파이프는 명령 줄을 처리할 때 쉘에 의해 즉석에서 생성됩니다. 이러한 파이프는 생성되어 사용된 후 폐기됩니다. 일시적이며 흔적을 남기지 않습니다. 파이프를 사용하는 명령어가 실행되는 동안에만 존재합니다.
명명된 파이프는 파일 시스템에서 영구 객체로 나타나며 `ls` 명령어를 사용하여 확인할 수 있습니다. 명명된 파이프에 저장된 데이터는 읽혀지기 전까지는 삭제되지 않고, 컴퓨터를 재부팅해도 유지되기 때문에 영구적입니다.
명명된 파이프는 여러 프로세스 간에 데이터를 교환하는 데 주로 사용되었지만, 최근에는 많이 사용되지 않습니다. 여전히 효율적으로 활용하는 사람들도 있겠지만, 흔하게 볼 수 있는 기능은 아닙니다. 하지만 이해를 돕기 위해 또는 단순한 호기심을 위해 다음과 같이 사용할 수 있습니다.
명명된 파이프는 `mkfifo` 명령어를 사용하여 생성됩니다. 다음 명령어는 현재 디렉토리에 “geek-pipe”라는 명명된 파이프를 만듭니다.
mkfifo geek-pipe
`ls` 명령어를 `-l` 옵션과 함께 사용하면 명명된 파이프의 세부 정보를 확인할 수 있습니다.
ls -l geek-pipe
출력 결과의 첫 번째 문자는 파이프를 의미하는 “p”입니다. “d”는 파일 시스템 객체가 디렉토리임을 나타내고, “-“는 일반 파일을 의미합니다.
명명된 파이프 사용법
명명된 파이프를 사용해 보겠습니다. 이전 예시에서 사용한 익명 파이프는 보내는 명령어에서 받는 명령어로 즉시 데이터를 전달합니다. 반면에 명명된 파이프를 통해 전송된 데이터는 데이터를 읽을 때까지 파이프에 보관됩니다. 데이터는 실제로 메모리에 저장되므로, 명명된 파이프의 크기는 데이터의 유무와 상관없이 `ls` 목록에서 변경되지 않습니다.
다음 예시에서는 두 개의 터미널 창을 사용합니다. 각 터미널 창에 라벨을 붙여 구분해 보겠습니다.
# Terminal-1
한 터미널 창에서는 이렇게 입력합니다.
# Terminal-2
다른 터미널 창에서는 이렇게 입력하여 두 터미널 창을 구분합니다. “#” 기호는 쉘에게 뒤에 오는 것이 주석임을 알리고 무시하도록 지시합니다.
이제 이전 예시에서 사용했던 명령어를 명명된 파이프로 리디렉션해 보겠습니다. 즉, 한 명령어에서 익명의 파이프와 명명된 파이프를 동시에 사용합니다.
ls | rev | cut -d'.' -f1 | rev | sort | uniq -c > geek-pipe
명명된 파이프의 내용을 `cat` 명령어로 리디렉션하여 두 번째 터미널 창에 해당 내용이 출력되도록 합니다. 출력 결과는 다음과 같습니다.
그리고 첫 번째 터미널 창에서 명령 프롬프트로 돌아온 것을 볼 수 있습니다.
방금 일어난 일은 다음과 같습니다.
- 일부 출력을 명명된 파이프로 리디렉션했습니다.
- 첫 번째 터미널 창이 명령 프롬프트로 바로 돌아가지 않았습니다.
- 데이터는 두 번째 터미널에서 파이프에서 읽을 때까지 파이프에 남아 있었습니다.
- 데이터가 읽혀진 후 첫 번째 터미널 창에서 명령 프롬프트로 돌아왔습니다.
명령어 끝에 `&` 기호를 추가하면 첫 번째 터미널 창에서 백그라운드 작업으로 명령어를 실행할 수 있다고 생각할 수 있습니다. 물론 그렇게 하면 명령 프롬프트로 바로 돌아갈 수 있습니다.
백그라운드 처리를 사용하지 않은 이유는 명명된 파이프가 차단 프로세스임을 강조하기 위해서였습니다. 명명된 파이프에 데이터를 넣으면 파이프의 한쪽 끝만 열립니다. 데이터를 읽는 프로그램이 파이프에서 데이터를 가져가기 전까지는 다른 쪽 끝은 열리지 않습니다. 커널은 파이프의 다른 쪽 끝에서 데이터를 읽을 때까지 첫 번째 터미널 창에서 프로세스를 일시 중단합니다.
파이프의 강력한 힘
명명된 파이프는 현재는 독특한 기능처럼 보일 수 있습니다.
반면에 일반적인 리눅스 파이프는 터미널 창에서 사용할 수 있는 가장 유용한 도구 중 하나입니다. 리눅스 명령 줄 기능이 활성화되기 시작하고 명령 모음을 조합하여 강력한 성능을 낼 수 있다면, 생산성을 극대화할 수 있습니다.
마지막으로 팁을 드리자면, 파이프 연결을 사용하는 명령어를 작성할 때는 한 번에 하나의 명령어를 추가하고 각 부분을 확인한 후 다음 명령어를 파이핑하는 것이 좋습니다.