2014년 4월 16일 수요일

처음부터 다시 배우는 리눅스

① 설치와 기본 명령어


이제 리눅스는 더 이상 소수 마니아들만을 위한 운영체제가 아니다. 신문 방송을 통해 리눅스의 존재가 많이 알려지면서 이제 리눅스는 일반인들도 들어본 적이 있는 단어가 되었고 그 범용성 또한 계속 확장되는 추세이다. 처음 소규모 서버 분야에서 조금씩 자리를 차지하기 시작한 리눅스는 이제 엔터프라이즈, 데스크탑 그리고 임베디드 분야까지 진출하고 있는 가장 잠재력이 높은 운영체제의 하나로 성장했다.

이에 따라 리눅스를 사용하는 사람들의 모습도 다양해지고 있다. 하지만 유닉스 클론인 리눅스의 근본적인 부분은 그 모습을 그대로 유지하고 있으며 새로운 변화 속에서도 든든한 기초의 역할을 꾸준히 계속해 오고 있다.

최근 들어 리눅스 데스크탑에 대한 관심이 높아지면서 윈도우에서 리눅스로 옮겨보고 싶어 하는 개발자들이 늘고 있는 듯하다. 하지만 이들 중의 상당수가 리눅스 설치 이후 실제 개발자에게 필요한 리눅스 셸 활용법이나 개발툴 활용 그리고 윈도우와는 다른 문화를 제대로 접해보지 못하고 있는 듯하다. 이번 연재에서는 이런 리눅스의 조금은 전통적인 사용 환경과 개발 환경을 하나씩 소개해 나가면서 개발자들에게 좀 더 도움이 되는 부분들을 다뤄보기로 한다.

리눅스를 써 보고 싶다면 리눅스를 설치해 보는 것이 가장 좋은 길이다. 그렇다면 리눅스는 어떻게 설치할 것인가? 이 질문은 간단해 보이면서도 많은 리눅스 사용자들을 혼란스럽게 하는 질문이다. 특히 리눅스와 같은 오픈소스 환경은 다양성이 주요한 장점인 까닭에 자신의 필요에 맞는 배포판을 자유롭게 선택해 마음껏 설정해 쓰는 것이 가장 바람직하겠지만 오픈소스에는 자유가 많은 만큼 선택의 괴로움 또한 증가하는 것이 사실이다.

게다가 최근 들어 네트워크의 지속적인 발전과 하드웨어 가격의 하락은 굳이 리눅스를 '깔아서 쓰지' 않고서도 잘 쓸 수 있는 환경이 나타나는 추세도 보인다. 일례로 Knoppix 배포판의 경우 부팅 가능한 CD-ROM 디스크 한 장에 리눅스 배포판을 담고 있는데 Knoppix 디스크를 만들어 사용하면 Knoppix CD-ROM을 단순히 부팅하는 것만으로도 리눅스 시스템을 쓸 수 있게 해준다. 비록 파일 시스템이 읽기 전용이라는 단점이 따라오기는 하지만 리눅스를 돌리기가 예전에 비해서는 훨씬 간단해진 셈이다.

또한 최근 웹 호스팅 서비스의 가격 하락으로 PHP와 같은 웹 프로그래밍 언어에 관심이 많은 사람이라면 공들여 PHP 프로그래밍 전용 서버를 하나 설치하는 것보다는 오히려 웹 호스팅 업체에서 제공하는 리눅스 계정을 약간의 비용을 들여서 하나 장만하는 것이 직접 자신의 컴퓨터에 리눅스를 설치하는 것보다 편리할 수도 있다.

하지만 전체적인 시스템에 대한 감을 잡기 위해서는 아무래도 스스로 직접 리눅스 서버를 설치해서 사용하는 편이 가장 지름길이다. 우선, 여기서는 개발자를 위한 리눅스 서버를 하나 장만하는 것을 위주로 해서 이야기를 엮어 나가 보도록 하자. 

리눅스 시스템의 선택
개발자를 위한 리눅스 하드웨어는 그다지 높은 사양을 요구하지는 않는다. 요즘 일반화되어 있는 GHz대 CPU에 256MB 정도의 메모리만 있어도 리눅스를 쓰는 데는 무리가 없다. 다만 CPU의 속도가 빠르면 아무래도 컴파일할 때 시간이 덜 걸리고, 메모리가 많으면 하드디스크 스왑이 줄어든다는 장점이 있겠다.

리눅스 하드웨어의 호환성은 지금도 꾸준히 개선되고 있으며, 최근에는 리눅스를 처음부터 지원하는 하드웨어도 많이 출시되고 있다. 하지만 리눅스 환경은 하드웨어 호환성의 측면에서 여전히 윈도우 환경에 비해 모자란 면이 없지 않다. 특히, 그래픽카드의 지원은 아직도 상대적으로 많이 부족한데 새로 리눅스용 시스템을 만드는 경우에는 그래픽카드의 지원 상황은 반드시 체크해 보도록 하자.

필자의 경우는 보통 리눅스 문서 프로젝트(The Linux Documentation Project, 
www.tldp.org) 페이지의 Hardware-HOWTO 문서를 우선 참조한다. 또한, 최신 하드웨어의 호환성 여부 정보는 유즈넷 뉴스그룹을 참고하거나 유즈넷에 직접 질문을 포스팅해 보는 것도 좋다. 아무래도 영문권 사용자들은 지금도 뉴스그룹을 많이 이용하는 까닭에 사용자 수가 많고 원하는 답변을 얻게 될 확률 또한 그만큼 높은 편이다.

comp.os.linux 아래쪽 뉴스그룹을 주로 참고하는 것이 괜찮으며 하드웨어 호환성은 comp.os.linux.hardware에서 많이 다루는 편이다. 혹시, 뉴스그룹이 무엇인지 잘 모르는 사람은 구글에서 제공하는 뉴스 서비스인 구글 그룹 서비스(groups.google.com)를 이용해 보자.

구입하고자 하는 하드웨어의 정확한 호환성 여부에 관한 정보를 얻지 못하는 경우는 직접 리눅스 커널 소스코드를 다운받고(ftp://ftp.kernel.org), 커널 소스코드의 압축을 푼 다음 소스코드를 뒤져 보는 것도 괜찮은 선택이다. 보통 해당 디바이스의 드라이버 소스코드에는 코멘트로 현재 드라이버의 지원 상황이 적혀 있기 마련이며, C 언어를 잘 못하더라도 코멘트 문장을 읽기란 어렵지 않다. 예를 들어 네트워크 카드 드라이버들은 리눅스 커널 소스의 drivers/net 디렉토리에 들어가 있다.

배포판의 선택
이제 시스템을 장만했다면 설치할 배포판을 고를 차례이다. 리눅스 배포판은 소스코드가 공개되어 있는 리눅스의 특성상 수많은 종류가 있다. 그 중 많이 쓰이고 유명한 배포판들이 레드햇, 수세, 데비안, 젠투, 맨드레이크, 한컴 리눅스 등이다. 

리눅스 배포판의 응용 프로그램 패키징
바이너리 패키징 
이들 배포판의 특성을 전체적으로 분류해 보면 우선 응용 프로그램의 패키징 방식에 따라 배포판들을 나누어 볼 수 있다. 대표적인 패키징 방식은 리눅스 배포판의 양대 산맥 격인 레드햇의 rpm 형식과 데비안의 deb 형식이다. 레드햇의 배포판, 그리고 최근의 레드햇 페도라, 맨드레이크, 수세, 한컴 리눅스와 같은 배포판이 rpm 패키지를 사용하고 있다. 이에 반해 deb 형식은 데비안 배포판에 사용되고 있으며 데비안 이외에도 여러 배포판들에서 deb 형식을 도입하고 있다.

주지하다시피 윈도우 설치 패키지와는 달리 리눅스 배포판들의 설치 패키지는 의존성(dependency) 개념이 처음부터 고려되어 있다. 즉 어떤 응용 프로그램을 설치하려고 할 때 이 패키지가 다른 패키지가 기본으로 설치되어 있어야 실행될 수 있다면 패키지 시스템은 자동적으로 이럴 때 어떤 패키지들이 추가로 필요한 지를 사용자에게 알려준다.

예를 들어 리눅스 시스템에서 사용자가 어떤 자바 기반의 응용 프로그램 패키지 설치 명령을 내리면 그 리눅스 배포판의 패키징 시스템은 현재 시스템에 자바 런타임 패키지가 설치되어 있는지를 우선 체크하고, 자바 런타임이 있지 않은 경우에는 설치 작업을 더 이상 진행하지 않는다. 이 덕분에 리눅스 시스템은 윈도우보다 응용 프로그램의 유지보수 측면에서 시스템 관리가 한결 수월하다.

여기서 조금 더 나아가면 하나의 응용 프로그램 패키지를 설치할 때 자동적으로 필요한 패키지 리스트를 생성해 낼 수 있으면 좋을 것이고 가능하다면 이 리스트를 보고 직접 다운로드 작업까지 패키지 관리 프로그램이 알아서 처리해 주면 더욱 좋을 것이다.

이렇게 패키지 사이의 의존성을 자동으로 처리해 주는 패키지 관리자의 대표적인 것이 데비안의 APT(Advanced Package Manager, www.debian.org/doc/manuals/apt-howto)이다. APT를 사용하면 사용자는 패키지 다운로드나 설치를 수동으로 할 필요가 없이 단순히 설치할 패키지의 이름만 지정해 주면 시스템이 알아서 패키지 다운로드 및 설치, 업그레이드를 해주며, 심지어는 배포판 자체의 업그레이드까지도 가능하게 해준다. 많은 데비안 사용자들이 게시판에서 APT의 편리함을 패키지 설치 명령어 한 줄 짜리 답변으로 대신하는 경우를 여러분들도 간혹 보았을 것이다.

# apt-get install <패키지 이름>

APT의 경우에서 볼 수 있듯이 리눅스 배포판에서 이제 응용 프로그램 설치는 사용자가 원하는 패키지를 선택하면 시스템이 알아서 필요한 다른 패키지까지 알아서 다운받아 설치하는 형태로 변화되어 있다. deb뿐만 아니라 이제는 rpm에서도 마찬가지 경향이 나타나며 rpm에서는 이것이 yum이나 지미안(Ximian)의 Red Carpet과 같은 좀 더 쓰기 쉬운 패키지 관리 매니저로 나타나게 된다. 즉, 과거에는 rpm 방식과 데비안 배포판의 deb 패키징에 대한 비교가 많았지만 이제는 rpm이냐 deb이냐 혹은 소스 패키지이냐의 구분이 커다란 의미가 없는 방향으로 나아가고 있다.

리눅스 사용자의 입장에서는 어느 배포판이든 응용 프로그램을 설치하는 방법이 원하는 프로그램을 마우스로 선택하고 설치 버튼을 누르는 쪽으로 이미 통일됐기 때문이다. 다만, 설정 파일의 유지보수 측면이나 시스템을 내 마음대로 꾸미는(customization) 측면에서는 파일 단위의 의존성을 체크하는 rpm보다는 패키지 단위로 의존성을 체크하는 deb이 좀 더 우위에 있는 편이지만 실제 사용자의 입장에서 큰 차이는 존재하지 않는다.

하지만 전통적으로 rpm 기반의 배포판들은 레드햇, 수세, 맨드레이크와 같이 상용 업체들에서 만들어지는 경우가 많고 따라서 설치 작업이 쉬운 경우가 많으며 기본 데스크탑 설정도 깔끔하게 잘 되어 있다는 장점이 있다. 따라서 리눅스에 익숙하지 않은 사용자들은 아무래도 이런 상용 업체가 만든 배포판이 좀 더 쉽게 다가갈 수밖에 없다.

일단 설치가 쉽고 설정이 많이 필요하지 않는 레드햇과 같은 상용 배포판을 쓰면서 리눅스에 어느 정도 익숙해졌다고 생각하면 좀 더 다양한 가능성을 제공하는 데비안이나 여타 소스코드 패키지 기반의 시스템으로 넘어가는 것도 좋은 방법이다. 컴퓨터의 세계에서는 일반적으로 좀 더 최적화(customization)가 쉽고 동적(dynamic)인 시스템 구성이 가능할수록 사용자 입장에서 배워야 할 것이 많은 경향이 있을 수밖에 없다. 데비안의 경우 배포판 설치 작업을 좀 더 쉽게 하려는 여러 가지 노력이 계속되고 있으나 아무래도 다른 배포판에 비해 배울 것이 많은 편이다. 

소스 패키지 기반의 배포판
리눅스와 같은 오픈소스 환경에서는 소스코드가 공개되어 있기에 프로그램을 설치할 때 직접 소스코드를 컴파일해서 설치하는 방식이 예전부터 자연스럽게 정착되었다. 게다가 소스코드 컴파일은 기본적으로 배포용 패키지가 플랫폼에 중립적인 경향이 강하며, 컴파일 된 바이너리가 시스템에 최적화될 수 있다는 장점을 갖고 있다.

하지만 사용자가 소스코드를 직접 손으로 컴파일하면서 수많은 응용 프로그램들을 모두 관리하는 것은 많은 시간과 노력을 들여야 할 뿐만 아니라 설치하는 패키지 숫자가 많아지면 패키지 관리가 무척 힘든 일이 될 것이다. 그러나 리눅스의 소스 패키지 기반 배포판들은 역시 바이너리 패키징과 마찬가지로 의존성 처리 기능을 갖추고 있으며, 그 대표 주자가 젠투 리눅스(gentoo.org, 이하 젠투)이다. 원래 소스코드를 가져와 컴파일을 해서 응용 프로그램을 설치하는 방식은 FreeBSD의 ports 시스템이 원조라고 할 수 있다. 

ports는 소스코드를 직접 가져와 컴파일하면서도 패키지간의 의존성 정보를 알아서 처리해 주는데 리눅스에서는 젠투가 널리 쓰이고 있다. 젠투에서는 portage라는 소스 패키지 관리 프로그램을 사용하고 있는데 portage 역시 앞에서 본 데비안의 apt-get 만큼이나 패키지 관리가 편리하다. 다만 portage의 경우 소스코드 패키지를 다루기 때문에 패키지 설치 명령을 내리면 자동적으로 필요한 소스코드 패키지들을 다운받은 다음 소스코드 컴파일이 시작되는 차이점이 있다. 젠투에서 응용 프로그램 패키지를 설치하려면 다음 명령을 사용한다.

# emerge <패키지 이름>

젠투의 portage 시스템은 여기서 한술 더 떠 아예 다음과 같은 명령어도 가능하다. 이 명령은 실제 젠투 설치 과정의 일부인데 최소한도의 리눅스 사용이 가능한 젠투 base system을 구성하는 과정에서 쓰는 명령이다. 이 명령 하나로 가장 최근의 젠투 base system을 구성할 수 있으며, 이미 base system이 설치된 시스템에서는 업데이트까지 해결해 준다. 

# emerge system

여담이지만, 최근 들어 얼핏 보기에 마니아층에 좀 더 적합할 것 같은 젠투가 많은 인기를 끌고 있는 주된 이유는 무엇일까? 필자 개인적인 경험에 비추어 볼 때 젠투의 매력은 다른 무엇보다도 실제 리눅스 시스템이 어떻게 구성되는지 그 과정을 하나하나 보여준다는 데 있는 것 같다. 소스코드 컴파일은 사실 오픈소스 운영체제를 쓰지 않으면 거의 접하기 힘든 경험이다. 대부분의 소스코드는 그냥 가져다가 ./configure; make; make install 명령어만 내리면 설치가 되기는 하지만 컴파일이 안 되는 경우도 많으며, 초보 사용자의 입장에서는 컴파일 에러를 만났을 때 에러 해결이 힘든 경우가 허다하다.

하지만 젠투 리눅스는 소스코드 패키지 설치를 거의 make && make install 수준으로 쉽게 만들면서도 실제 컴파일 과정을 모두 볼 수 있다. 또한 젠투의 설치 과정은 다른 배포판과는 달리 하나하나 손으로 명령을 입력해야 한다.

예를 들어, 네트워크 설정을 할 때 다른 배포판은 일반적으로 IP 주소와 게이트웨이, 네임서버 주소 등의 정보를 설치 프로그램의 대화상자에 입력하는 방식을 많이 쓰는데 젠투에서는 다음처럼 route add, ifconfig 명령을 써서 사용자에게 실제로 네트워크 설정을 직접 명령어를 타이핑해서 설정하도록 한다. 설치 메뉴얼을 프린트해 놓고 하나하나 따라하면서 설치를 진행하다 보면 덩달아 배우는 지식도 많아지는 재미가 있는 배포판이 젠투라고 할 수 있겠다.

# ifconfig eth0 broadcast <브로드캐스트 주소> netmask <넷마스크> up
# route add default gw <게이트웨이 주소>

다만 젠투는 설치 과정이 직접 리눅스 명령어를 입력하면서 진행되기 때문에 유닉스 셸 프롬프트가 익숙하지 않은 초보자들에게는 적합하지 않으며, 패키지를 설치할 때 소스코드를 컴파일하기 때문에 시간이 많이 걸린다는 단점이 있다(참고로 젠투 최근 버전부터는 소스코드 컴파일 외에 바이너리 패키지 설치 방식도 같이 지원한다). 

전체적으로(필자 개인적인 생각으로 볼 때) 젠투는 리눅스 시스템 설치 과정에서 리눅스 시스템 설정에 대해 많은 사항을 자연스럽게 배울 수 있게 해 주기 때문에 개발자들에게 가장 적합한 배포판이 아닐까 싶다. 특히 젠투에서 하드웨어 자동 인식을 갖추고 리눅스 커널 컴파일을 쉽게 해주는 genkernel(www.gentoo.org/doc/en/genkernel.xml)은 아직까지 리눅스 커널 컴파일을 해 보지 않은 사용자들에게 괜찮은 가이드가 될 수 있을 것이다. 시스템 최적화의 측면에서는 데비안도 아주 유연하지만 짧은 시간에 설치 작업만으로 시스템 관리와 설정에 관한 많은 것을 쉽게 배울 수 있게 해주는 젠투의 장점은 젠투만의 독보적인 특징이 아닐까 싶다.

리눅스 라이브 CD, Knoppix
최근 들어 CD 한 장으로 부팅해서 쓰는 리눅스 배포판이 많이 나오고 있다. 그 대표적인 것이 앞에서도 잠깐 언급했던 Knoppix(www.knoppix.net)인데 이러한 CD-ROM 부팅 방식의 배포판은 하드디스크를 비우고 채워 넣는 설치작업이 아예 필요 없다는 간편함이 큰 장점이다. 특히 하드디스크에 리눅스를 설치해 쓰는 경우라도 우선 배포판을 설치하기 전에 이런 리눅스 라이브 CD를 이용해서 쉽게 시스템 테스트를 해볼 수 있다는 점은 많은 도움이 된다.

Knoppix는 CD-ROM 방식이기 때문에 파일 시스템이 읽기 전용이고 기본적으로 시스템 설정을 고칠 수 없다는 단점이 있지만 여느 오픈소스 프로젝트들이 그렇듯이 그저 CD-ROM으로 부팅하는 리눅스 배포판이라는 기본적인 기능 외에 여기서 파생된 여러 가지 재밌는 아이디어들 또한 많이 구현되어 있다. 하드디스크의 조그만 FAT32 파티션에 Knoppix CD 이미지를 넣고 CD-ROM 대신 부팅을 해서 쓴다든가, Knoppix의 데스크탑 환경인 KDE를 빼고 GNOME을 집어넣은 Gnoppix 프로젝트라든가, 내 입맛에 맞는 설정을 플로피나 USB 메모리 드라이브에 저장해 놓고 매번 불러 쓰는 것과 같은 기능들이 이미 구현되어 있으니 관심 있는 독자들은 프로젝트 홈페이지를 한번 들러 보자. 

리눅스의 설치
리눅스를 설치하기 전에, 준비한 시스템에 리눅스가 잘 작동하는지 테스트를 해보고 싶다면 방금 이야기한 Knoppix를 하나 준비하자. Knoppix CD 이미지를 다운받아 공 CD 하나에 구운 다음 Knoppix를 CD-ROM 부팅해 보면 대략 내 시스템에서 어떤 부품이 말썽을 피울 것인지 미리 짐작해 볼 수 있다.

구체적인 배포판의 설치 과정은 여기서는 생략하기로 한다. 이미 리눅스 설치의 편의성은 레드햇 구 버전부터 이미 윈도우보다 쉬워진 지 오래고 데비안이나 젠투와 같이 설치가 상대적으로 까다로운 배포판은 직접 프로젝트 홈페이지에서 문서 자료를 구하는 것이 좋기 때문에 굳이 설치까지 다룰 필요는 없을 것 같다. 다만 여기서는 배포판에 상관없이 설치 과정에서 걸리게 되는 파티션에 대한 개념과 리눅스 설치 이후 리눅스용 GUI 응용 프로그램을 원격으로 실행할 때 사용하는 X서버/클라이언트 개념에 대해 잠깐 짚어보고 넘어가도록 하자. 

하드디스크 파티션 나누기
하드디스크를 왜 나누어 쓸까? 그 이유야 다양하겠지만 가장 쉽게 생각해 볼 수 있는 파티션 나누기의 이점은 분할해서 쓰기 때문에 필요한 부분만 다른 파티션에 영향을 주지 않고 내 맘대로 바꾸어 쓸 수 있다는 점이다.

예를 들어 윈도우에서 파티션을 하지 않고 하나의 하드디스크를 그냥 사용했다고 가정해보자. 윈도우 새 버전이 출시되었을 때 이 사용자가 새 버전의 윈도우를 깔끔하게 새로 설치하려면 그동안 저장된 사용자 문서나 여러 데이터 파일들을 잠시 백업받은 뒤 나중에 복구하거나, 혹은 임시 디렉토리라도 만들어서 거기에 데이터 파일들을 저장해 놓은 다음, 기존의 윈도우 디렉토리와 응용 프로그램 디렉토리들을 재주껏 지우는 과정을 거쳐야 한다.

하지만 이 사용자가 하드디스크를 이미 파티션해 놓았고, 파티션이 분리된 D: 드라이브에다가 항상 사용자 데이터를 저장하고 있었다면 얘기는 달라진다. 이 경우 새 윈도우를 설치하려면 그냥 C: 드라이브를 속 시원히 밀어버려도 D:의 사용자 데이터에는 전혀 이상이 없기 때문이다.

파티션을 나누는 또 하나의 주된 이유는 하나의 하드디스크에 2개 이상의 운영체제를 설치할 때이다. 이 경우는 운영체제마다 독립된 저장 공간이 필요할 테니 파티션을 이용하는 것이 가장 바람직한 방법이 될 것이다. 그렇다면, 2개의 운영체제가 하나의 하드디스크에 설치되어 있다고 할 때 이 하드디스크로는 어떻게 부팅을 해야 할까? 여기서 기본 파티션(primary partition)과 확장 파티션(extended partition)의 개념이 나오게 된다.

우선, 기본 파티션은 부팅이 가능한 파티션이라고 정의해 볼 수 있다. 그리고 여기서 하나 외워 둘 것은 기본 파티션은 하나의 하드디스크에 4개만 만들 수 있다는 제한 조건이다. 그렇다면 4개 이상의 기본 파티션이 필요한 경우는 어떻게 처리할까? 이 경우는 4개의 기본 파티션 중의 하나를 ‘더 쪼갤 수 있도록’ 설정한다. 이렇게 더 쪼개질 수 있도록 지정된 파티션이 확장 파티션이다.

즉, 기본 파티션은 원래 4개 밖에 만들 수 없도록 되어 있지만 그 중 하나를 맘대로 쪼갤 수 있도록 해서 실제로 4개 이상의 기본 파티션을 만들 수 있는 효과를 내는 것이다. 이 경우 우리가 쪼갤 수 있도록 지정한 파티션을 확장 파티션이라고 하며, 확장 파티션은 그 안의 조그만 파티션들을 담게 되는 큰 그릇 역할을 하게 되는 셈이다.

그런데 요즘은 윈도우 fdisk의 영향으로 기본 파티션을 보통 하나만 만들고 하드 디스크의 남은 부분은 자동으로 확장 파티션으로 지정해 버리는 방법이 많이 사용된다. 즉, 기본 파티션을 4개까지 만들다가 4개 이상의 파티션을 생성해야 할 일이 생기면 그 중 하나를 더 쪼개 쓸 수 있도록 지정하지 않고, 처음부터 만들어진 파티션 중 두 번째 파티션을 바로 확장 파티션으로 지정해 놓고 이 안에서 숫자의 제한 없이 파티션의 개수를 계속 늘려 나가겠다는 얘기이다. 하지만 이 방식이 리눅스를 설치하는 사용자의 입장에서는 약간의 혼동을 일으키게 된다는 문제가 있다.

우선, 기본 파티션만을 이용해서 리눅스를 설치하는 과정을 생각해 보자. 리눅스는 윈도우와는 달리 가상 메모리를 쓰기 위해 스왑 파티션이 필요하니 리눅스 전용 파티션 하나, 스왑 파티션 하나, 이렇게 2개의 파티션이 최소한으로 필요하다. 윈도우 듀얼 부팅을 위해 윈도우에도 파티션을 하나 줘 도합 3개의 파티션을 만들어 본다고 가정하자. 리눅스는 파일 방식으로 모든 하드웨어 장치를 가리키는데 일반적인 IDE 방식 하드디스크가 하나 설치되어 있다고 가정하면 이 하드디스크는 /dev/hda에 해당하며 여기서 한번 /dev/hda에 리눅스 fdisk로 파티션을 나누어 보자.

# fdisk /dev/hda

Command (m for help): p

Disk /dev/hda: 120.0 GB, 120034123776 bytes
255 heads, 63 sectors/track, 14593 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1   *           1        7649    61440561    7  HPFS/NTFS
/dev/hda2            7650       14000    51014407+  83  Linux
/dev/hda3           14001       14063      506047+  82  Linux swap

Command (m for help):

앞에서 볼 수 있듯이 생성되는 파티션은 /dev/hda2, /dev/hda3로 끝남을 알 수 있다. 윈도우용 파티션은 /dev/hda1으로 설정해 주었다. 그런데 여기서 하드디스크를 /dev/hda1, /dev/hda2 둘로 쪼갠 다음, 두 번째 기본 파티션인 /dev/hda2를 확장 파티션으로 지정해 보자. 

# fdisk /dev/hda
Command (m for help): p

Disk /dev/hda: 120.0 GB, 120034123776 bytes
255 heads, 63 sectors/track, 14593 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1   *           1        7649    61440561    7  HPFS/NTFS

Command (m for help): n
Command action
   e   extended             # 여기서 extended 선택
   p   primary partition (1-4)

.
.
(중간 생략)
.
.

Command (m for help): p

Disk /dev/hda: 120.0 GB, 120034123776 bytes
255 heads, 63 sectors/track, 14593 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1   *           1        7649    61440561    7  HPFS/NTFS
/dev/hda2            7650       14593    55777680    5  Extended

Command (m for help):

그 다음, 이 그릇 역할을 하는 /dev/hda2 파티션을 잘게 쪼개어 보자. 쪼개진 파티션 각각을 논리 파티션(logical partition)이라고 한다.

# fdisk /dev/hda

Command (m for help): p

Disk /dev/hda: 120.0 GB, 120034123776 bytes
255 heads, 63 sectors/track, 14593 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1   *           1        7649    61440561    7  HPFS/NTFS
/dev/hda2            7650       14593    55777680    5  Extended

Command (m for help): n
Command action
   l   logical (5 or over)          # logical을 선택한다.
   p   primary partition (1-4)
l
First cylinder (7650-14593, default 7650):
Using default value 7650
Last cylinder or +size or +sizeM or +sizeK (7650-14593, default 14593): 14500

Command (m for help): p

Disk /dev/hda: 120.0 GB, 120034123776 bytes
255 heads, 63 sectors/track, 14593 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1   *           1        7649    61440561    7  HPFS/NTFS
/dev/hda2            7650       14593    55777680    5  Extended
/dev/hda5            7650       14500    55030626   83  Linux

.

(중간 생략)

.

Command (m for help): p

Disk /dev/hda: 120.0 GB, 120034123776 bytes
255 heads, 63 sectors/track, 14593 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/hda1   *           1        7649    61440561    7  HPFS/NTFS
/dev/hda2            7650       14593    55777680    5  Extended
/dev/hda5            7650       14500    55030626   83  Linux
/dev/hda6           14501       14563      506016   82  Linux swap

Command (m for help):

즉, 확장 파티션 그릇인 /dev/hda2 안에 생성된 자식 파티션들을 가리키는 파일 이름은 /dev/hda5, /dev/hda6, /dev/hda7 등이 됨을 볼 수 있다. 중간의 hda3, hda4가 생략된 조금 got갈리는 숫자 배열 방식이기는 하지만 한번 알아두면 편하니 머릿속에 꼭 넣어 놓도록 하자. <그림 1>에서는 하드디스크 파티션을 만들면서 기본 파티션만 쓴 경우와 확장 파티션을 쓴 경우를 다이어그램으로 간략화시켜 표현해 보았다.

VMWare, 가상 시스템  

몇 개의 파티션을 만들 것인가?
앞에서 잠깐 얘기했듯이 리눅스를 설치하기 위해서는 리눅스 전용 파티션 하나, 그리고 스왑 파티션 하나, 이렇게 최소 2개의 파티션이 필요하다. 하지만 파티션은 적절히 나누어 놓으면 나중에 업그레이드나 시스템을 변경할 일이 생길 때 무척 쉽게 대응할 수 있다.

유닉스 시스템의 재미있는 특징 중의 하나는 하드디스크를 마운트할 때 윈도우와 같이 C: D: E: 드라이브 이름을 주지 않고 디렉토리에 바로 마운트시켜 버린다는 점이다. 아래 df 명령 출력을 잠깐 보자. df는 남아있는 디스크 용량을 보여주는 명령어이고 -h는 MB, GB 단위로 용량을 출력하라는 옵션이다.

$ df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/hda1             3.9G  2.5G  1.3G  66% /
/dev/hdb1              39G   33G  4.3G  89% /home
/dev/hda2              15G   13G  1.4G  91% /home/foobar/backup
/dev/hdb2              18G   16G  1.2G  94% /usr

앞의 리눅스 시스템의 경우 물리적으로 2대의 하드디스크를 리눅스에서 가져다 쓰고 있으며, 첫 번째 하드디스크의 첫 번째 파티션은 리눅스의 루트(root) 파티션, 두 번째 파티션은 백업용인 /home/foobar/backup으로 쓰이고 있음을 알 수 있다. 두 번째 하드디스크의 첫 번째 파티션은 사용자 계정이 들어가는 /home 디렉토리에 그리고 두 번째 파티션은 응용 프로그램들이 주로 들어가는 /usr 디렉토리에 사용되고 있음을 볼 수 있다. 

우선, 리눅스 시스템을 만들 때 사용자 계정이 들어가는 /home 디렉토리는 별도의 파티션으로 나누어 주는 것이 차후의 시스템 확장을 대비해 무척 편리하다. 리눅스를 완전히 새로 설치하는 경우가 생기더라도 /home 디렉토리가 별도의 파티션으로 빠져 있으면 리눅스 루트 파티션을 새로 포맷하고 리눅스를 설치한 다음 /home 디렉토리만 그냥 마운팅해 버리는 것으로 사용자 파일은 전혀 손상 없이 유지되기 때문이다. 

그 다음으로 생각해 볼 것이 /boot 파티션이다. /boot 파티션은 굳이 안 만들어도 상관은 없지만, 리눅스 커널을 넣어두는 곳이라 하나쯤 분리해 놓는 편이 좋다. /boot 파티션이 분리되어 있으면 루트 파일 시스템에 오류가 생기더라도 커널 차원의 부팅에는 문제가 없기 마련이다. 

/boot 파티션의 크기는 커널 이미지가 1MB가 안되는 편이니 수십MB 정도만 잡아도 충분하다. /var 디렉토리를 파티션으로 분리하면 어떤 점이 좋을까? /var 디렉토리 안에는 여러 종류의 로그(log) 파일과 메일 데이터가 저장된다. /var/log와 /var/spool/mail이 대표적인 예이다. 

만약 /var 디렉토리가 파티션 분리되어 있지 않다면 특정 서버 프로세스의 로그 파일이 갑자기 넘치거나 덩치 큰 메일이 하드디스크를 채워버리는 경우 시스템의 루트 파티션에 더 이상 쓰기가 불가능하게 될 것이고 이것은 서버 동작의 정지로 이어진다. 하지만 /var 디렉토리가 분리되어 있으면 이 경우 /var 파티션이 가득 차기는 하지만 루트 파티션은 영향을 받지 않기 때문에 서버는 여전히 제 동작할 수 있다.

마찬가지로 응용 프로그램이 많은 경우 /usr 쪽을 분리해 내는 것도 생각해 볼 수 있을 것이다. 하지만 대규모 서버를 운영하기 전에는 지나치게 파티션을 많이 만들 필요 없다. 일반적인 개인 사용자의 입장에서 리눅스 파티션은 스왑 파티션, /, /home, /boot 정도만 해주면 충분하다고 보면 되겠다.

참고로 파일 시스템은 자동 에러 체킹 기능이 있는 저널링 파일 시스템을 사용하도록 하자. 일반적으로 많이 쓰이는 ext3, xfs, Reiserfs 정도를 사용하면 된다. 단, /boot 파티션은 심플한 것이 좋으니 어디서나 지원하는 ext2 파일 시스템으로 포맷해서 쓰는 것이 좋다. 

세컨드 PC에서 리눅스
요즘은 세컨드 PC라는 말이 의외로 자주 등장하고 있는 것 같다. 새 PC를 장만하는 데 드는 비용이 지속적으로 하락하고 있고, VGA가 내장된 microATX 크기의 메인보드가 10만원이 채 안되는 가격을 형성하고 있으니 이제는 세컨드 PC도 사치가 아닌 시대가 찾아온 셈이다. 

게다가 구형 취급을 당하는 1GHz CPU들의 성능은 사실 리눅스를 돌리기에는 남아도는 사양이다. 이런 이유로 여유가 닿는 대로 간단히 세컨드 PC로 리눅스 서버를 구성해서 개발 전용으로 쓰는 것도 괜찮은 아이디어이다. 그리고 리눅스 데스크탑이 비약적인 발전을 이루고 있긴 하지만 인터넷 익스플로러에 지나치게 최적화되어 버린 국내 인터넷 환경은 이미 윈도우 PC를 어느 정도는 쓰지 않을 수 없을 정도까지 상황이 악화되어 버리고 말았다. 아는 사람의 미니 홈페이지에 접속하기 위해 신용카드로 물건을 하나 주문하는 데도 윈도우와 인터넷 익스플로러가 있어야 하는 상황은 결코 정상은 아니지만, 이것이 짧은 시간 내에 해결될 것 같지는 않다.

리눅스에서는 X윈도우가 클라이언트/서버 구조이기 때문에 윈도우의 터미널 서버보다 쓰기 편한 터미널 서버/클라이언트 환경을 이용할 수 있다. 바로 옆의 리눅스 서버의 GUI 응용 프로그램의 디스플레이를 윈도우 PC로 당겨  쓰는 것은 무척 쉬운 일이며, 리눅스는 윈도우 XP 프로페셔널처럼 윈도우 시스템을 현재 누군가가 쓰고 있는 경우 터미널 클라이언트에서의 로그인 접속을 막아버리는 제한도 없다.

사용자 입장에서는 윈도우와 리눅스 두 환경을 한 데스크탑에서 쓸 수 있으니 상당히 편리하다. <화면 1>은 윈도우 XP에서 X윈도우 서버인 Xmanager를 설치하고 리눅스용 응용 프로그램들의 디스플레이를 윈도우 데스크탑에서 쓰고 있는 모습이다. 인터넷 익스플로러와 나란히 리눅스용 한텀, gnuplot, xlogo가 실행되어 있는 모습을 볼 수 있다.


많은 독자들이 이미 이 방법을 잘 사용하고 있겠지만 이제 어떻게 하면 리눅스용 GUI 애플리케이션들을 윈도우 데스크탑으로 끌어와서 디스플레이할 수 있는지 살펴보도록 하자. 게다가 X윈도우의 클라이언트/서버 구조는 일반적인 클라이언트/서버 구조를 이해하는 데 많은 도움을 준다.

클라이언트/서버 구조는 중간에 서비스라는 매개체를 넣고 생각하면 쉽다. 어떠한 서비스를 제공하는 쪽은 서버가 되는 것이고 그 서비스를 받는 쪽은 클라이언트가 되는 것이다. 어떤 사람들은 그냥 쉽게 메인프레임과 터미널의 확장, 혹은 웹 서버와 클라이언트 정도로 생각을 하는데 여기에 한번 X윈도우 서버와 클라이언트의 작동을 살펴보도록 하자.

우선 리눅스의 모든 X윈도우 응용 프로그램들은(KDE나 Gnome 응용 프로그램들을 모두 포함하여) X클라이언트이다. 그리고 윈도우 PC에서 작동하는 Xmanager나 XFree86과 같은 프로그램은 X서버이다. X서버는 X클라이언트의 디스플레이 서비스를 제공한다. 

즉, X서버는 X클라이언트의 요청에 따라 X클라이언트의 모습을 화면에 그려내는 서비스를 제공하는 것이다. 그리고 X클라이언트는 X서버의 이런 디스플레이 서비스를 이용해서 자신의 모습을 화면에 표시할 수 있게 되는 것이다. 이 모든 구조가 클라이언트/서버 방식으로 구현되어 있으니 클라이언트와 서버는 완전히 분리될 수 있는 것이다.

그렇다면 사용자가 리눅스용 GUI 응용 프로그램들을 지금 쓰고 있는 컴퓨터에 디스플레이하려면 어떻게 해야 할까? 답은 X윈도우 서버를 현재 쓰고 있는 컴퓨터에 설치해서 사용한다는 것이다. 클라이언트/서버 환경에서 보통은 웹 브라우저와 같이 사용자는 클라이언트 프로그램을 쓰게 되는 경우가 많기에 이런 경우 왜 X클라이언트가 아니고 X서버를 내 데스크탑에 깔아야 하는지 잘 이해하지 못하는 경우가 많은 것 같다.

하지만 X서버는 X클라이언트에게 디스플레이 서비스를 제공하는 서버가 맞으며, 사용자에게는 데스크탑에 X클라이언트의 모습을 출력시켜 주는 기능을 제공하는 응용 프로그램인 것이다. 따라서 사용자는 X서버만 설치하면 OS에 상관없이 어디서나 리눅스 GUI 응용 프로그램들을 불러 쓸 수 있다는 얘기가 된다. 보통, 윈도우에서 많이 쓰이는 X서버로는 Exceed, Xmanager 그리고 공개인 XFree86이 있다. 

Xmanager(www.netsarang.co.kr)의 경우 국산 프로그램으로 속도도 빠르고 평가판 다운로드가 가능하며 한글 폰트까지 내장되어 있어 편리하다. 윈도우용 XFree86은 공개라는 장점이 있지만 속도가 상대적으로 느리다는 단점이 있다. 이제 직접 리눅스용 X클라이언트를 윈도우 데스크탑에 띄워 보자. X클라이언트는 DISPLAY 환경 변수를 참조해서 X서버와의 연결을 시도한다. 즉, DISPLAY 환경 변수는 우리가 알아서 설정해 줘야 한다. 서버와 클라이언트의 구분에 유의하면서 다음 명령어들을 하나하나 실행시켜 보자.
  1. 윈도우즈 데스크탑에서 엑스 서버 실행
  2. telnet이나 ssh로 리눅스 컴퓨터 접속
  3. DISPLAY 환경변수에 윈도우즈 데스크탑의 아이피 주소와 디스플레이
    번호를 설정. 보통 디스플레이 번호는 0번을 쓰면 된다.
    $ export DISPLAY=<윈도우즈 데스크탑 아이피 주소>:0.0
  4. 원하는 엑스 클라이언트 실행
    $ xterm &
이렇게 하면 윈도우 데스크탑에 떠 있는 리눅스용 X터미널을 볼 수 있을 것이다. 경우에 따라서는 X서버에서 네트워크를 통한 클라이언트의 접속을 수동으로 열어줘야 하는 경우도 있다(이럴 때 X서버 쪽에서 xhost <클라이언트의 IP 주소>와 같은 명령어를 쓰게 된다).

여기서 ssh를 이용한 X11 forwarding 기능에 대해 잠깐 알아보자. 앞의 과정에서 DISPLAY 환경 변수를 세팅하기가 의외로 번거로울 수 있는데 X11 forwarding 기능을 이용하면 ssh로 접속을 시도할 때마다 이 DISPLAY 정보가 알아서 자동으로 세팅된다. 

윈도우에서 cygwin에 포함된 ssh와 XFree86을 X서버로 쓰는 경우 /etc/ssh_config나 홈 디렉토리의 .ssh_config에 다음 2줄을 추가한다. Xmanager와 같은 X서버를 쓸 때는 연결 방식을 ssh로 설정하는 것으로 보통 충분하다.

Host <리눅스 서버의 IP 혹은 *>
ForwardX11 yes

이렇게 하면 DISPLAY 환경 변수 설정이 필요 없게 되며 특히 윈도우 데스크탑이 사설 IP를 쓰는 IP 공유기 뒤에 있고, 리눅스 서버가 IP 공유기 바깥에 있는 경우와 같이 DISPLAY 환경 변수 설정이 곤란한 상황에서 아주 유용하게 쓰인다.

리눅스의 사용, 유닉스 셸에서의 파일 관리
KDE와 GNOME과 같은 리눅스 데스크탑 환경의 발전의 영향으로 요즘은 리눅스를 쓰면서 처음부터 유닉스 셸을 접하는 사람들의 비율이 예전처럼 많지는 않은 것 같다. 아마도 터미널 방식의 커맨드 라인 인터페이스 (command line interface)가 불편할 것이라는 선입관이 많이 작용하는 이유도 상당할 것 같은데 개발자 입장에서 기본적인 유닉스 명령어는 필수이며 유닉스 셸은 조금만 익숙해지면 무척이나 쓰기 편한 인터페이스 환경이다. 파일 관리를 중심으로 가장 기본적인 유닉스 명령어들을 한번 더 체크해 보도록 하자. 

파일 리스트 보기 - ls
가장 먼저 배우게 되는 유닉스 명령어의 대표격이다. 긴 파일 리스트를 보여주는 ls -l, 숨겨진 파일(리눅스에서 .로 시작하는 파일은 모두 숨겨진 파일이다)까지 보여주는 ls -al에 추가해서 디렉토리 뒤에는 /, 실행 파일 뒤에는 *, 심볼릭 링크(윈도우의 ‘바로가기’에 해당한다)에는 @를 붙여주는 -F 옵션까지를 기억해 두자. GNU ls에서는 한글 파일명이 잘 보이지 않는 경우 간혹 --show-control-chars 옵션을 쓰기도 한다. pwd는 현재 디렉토리를 알려주는 명령어이다. 

$ pwd
/home/jwsohn
$ ls
Mail  dragon.txt       public_html  storage                  www
bin   hangul_fonts     share        swarm_for_fedora_core_1
doc   man              src          temp
$ ls -F
Mail/  dragon.txt       public_html@  storage/                  www/
bin/   hangul_fonts/    share/        swarm_for_fedora_core_1/
doc/   man/             src/          temp/
$ ls -F bin
ex@  rview@  rvim@  vi@  view@  vim*  vimdiff@  vimtutor*  xxd*

파일 이름 자동 완성 기능, 디렉토리 옮겨 다니기 - cd
리눅스에서 많이 쓰는 bash와 tcsh에는 파일 이름 자동 완성(filename autocompletion) 기능이 있다. 파일 이름이 상당히 긴 경우 그냥 탭(tab) 키만 눌러 주면 쉘이 알아서 파일 이름을 사람 대신 타이핑해 주는 기능인데 여기에 일단 맛들이고 나면 심지어는 윈도우에도 bash를 깔게 될 정도로 그 마수에서 벗어나기가 쉽지 않다. 혹 아직까지 이 기능을 써 보지 않은 독자들을 위해서 간단한 예를 들어 보자. 앞의 파일 리스트에서 swarm_for_fedora_core_1 디렉토리에 들어가기 위해서는 다음과 같은 명령을 입력해야 한다. 

$ cd swarm_for_fedora_core_1

모두 다 타이핑하려면 귀찮으니 이 경우 자동완성 기능을 이용하면, 

$ cd sw
$ cd sw [TAB]                   # 탭 키를 누른다.
$ cd swarm_for_fedore_core_1     # bash가 파일 이름을 대신 타이핑해 준다.

s로 시작하는 디렉토리가 3개 있는데 만약 s만 타이핑하고 탭 키를 누르면 어떻게 될까? 

$ cd s
$ cd s [TAB]        # 탭 키를 누른다. bash가 비프음으로 파일명 중복을 알려준다.
$ cd s [TAB]        # 탭을 한번 더 누른다. 중복 파일명이 보인다.
share                    storage                  
src                      swarm_for_fedora_core_1  
$ cd sw             # w를 하나 더 타이핑해 준다.
$ cd sw [TAB]      # 이후로는 앞의 경우와 같다.

참고로 윈도우와는 달리 리눅스에서는 디렉토리 이름에도 와일드카드를 쓸 수 있다. 

$ cd ~              # ~를 생략하고 cd만 입력해도 된다.
$ pwd
/home/jwsohn
$ cd sw*
$ pwd
/home/jwsohn/swarm_for_fedora_core_1

cd 명령과 관련해서 점 1개(.)는 현재 디렉토리, 점 2개(..)는 한 단계 위의 디렉토리(parent directory), 그리고 ~ 는 사용자의 홈 디렉토리(보통은 /home/<사용자 계정>이다)임을 꼭 외워두자. 

디렉토리 생성과 삭제 - mkdir, rmdir
말 그대로이다. mkdir, rmdir 뒤에는 복수 개의 디렉토리가 올 수 있다.

파일 복사 - cp
리눅스 명령어 중 복수 파라미터가 필요한 명령어는 반드시 두 번째 파라미터를 생략해서는 안 된다는 원칙이 있다. 파일 복사 cp는 원본 파일 하나, 복사된 파일 하나 해서 두 개의 파라미터를 가지는 대표적인 명령인데 우선 다음 예를 보면서 그 의미를 살펴보자. 다운받은 한글 폰트의 백업본을 만드는 과정이다.

$ cd hangul_fonts/
$ ls
iyagi.pcf.gz     johabg16.pcf.gz  johabp16.pcf.gz   johabsm16.pcf.gz
iyagis16.pcf.gz  johabm16.pcf.gz  johabsg16.pcf.gz  johabsp16.pcf.gz
$ cp iyagi.pcf.gz iyagi.pcf.gz.backup   # cp 명령의 일반적인 형식
$ ls iyagi.pcf.gz*
iyagi.pcf.gz  iyagi.pcf.gz.backup
$ cp iyagi.pcf.gz iyagis16.pcf.gz ..    # 복수 개의 파일 복사. ..에 유의하자
$ cd ..
$ ls
Mail  dragon.txt       iyagis16.pcf.gz  share    swarm_for_fedora_core_1
bin   hangul_fonts     man              src      temp
doc   iyagi.pcf.gz     public_html      storage  www

mv

mv는 move의 약자라고는 하나 여러 가지 기능을 갖고 있다. 유닉스에서 파일 이름을 바꾸는 명령이 무엇이냐는 질문에 mv라고 대답하면 novice에서 user로 승급하게 된다는 농담도 있는데 실로 간단하면서도 여러 응용을 보여주는 참으로 재미있는 명령어이다. 역시 사용 예를 보자. 

◆ 파일이나 디렉토리 이름 변경 
$ mkdir backup
$ mv backup storage     # backup을 storage로 변경

◆ 디렉토리를 통째 이동 
$ ls
Mail    bin  dragon.txt       man          share  storage                  temp
backup  doc  hangul_fonts     public_html  src    swarm_for_fedora_core_1  www
$ cd storage
$ mv ../hangul_fonts ../doc .   # 상위 디렉토리 두개를 현재 디렉토리로 옮김
$ ls -F
doc/  hangul_fonts/

별로 쓰일 일이 없을 것 같던 현재 디렉토리 기호 (.)가 유용하게 쓰이고 있음을 볼 수 있다. 윈도우 데스크탑에서 마우스 드래그앤드롭으로 폴더를 옮기는 것과 아주 흡사하다. 

파일 지우기 - rm
파일을 삭제할 때는 rm 명령을 쓴다. -r 옵션을 쓰면 디렉토리도 지울 수 있다. 리눅스에서 rm 명령으로 한번 삭제한 파일은 복구가 되지 않으니 rm 명령을 쓰기 전에는 항상 여러 번 확인하는 습관을 들이는 것이 좋다. 

심볼릭 링크 만들기 - ln -s
ln 명령에는 아예 심볼릭 링크를 의미하는 -s 옵션을 붙여 외워버리자. 심볼릭 링크는 윈도우 파일 매니저의 바로가기에 해당한다. ln -s 명령을 쓸 때는 항상 원본 파일/디렉토리 이름이 앞에, 만들어지는 심볼릭 링크가 뒤에 간다는 점을 잘 외워두자. 

$ ln -s storage my_stuff
$ ls -ld storage my_stuff 
lrwxrwxrwx    1 jwsohn   jwsohn          8  5월 20 08:21 my_stuff -> storage
drwxr-xr-x    2 jwsohn   jwsohn       4096  5월 20 08:08 storage
참고로 심볼릭 링크를 지워도 원본에는 영향이 없다. 
$ rm my_stuff
$ ls -ld storage
drwxr-xr-x    2 jwsohn   jwsohn       4096  5월 20 08:08 storage

개발자 입장에서 계속 살펴볼 리눅스
지금까지 간략히 개발자를 위한 리눅스 시스템과 배포판 선택시 유의할 사항, 기본 유닉스 명령어에 관해 알아보았다. 다음 글부터는 이번 글에 다루지 못한 고급 수준의 중요 유닉스 명령어와 시스템 관리 기초를 다지고 리눅스의 다양한 개발 환경을 어떻게 활용하는 것이 좋은지 개발자의 입장에서 계속 살펴보기로 한다. @

VMWare(http://www.vmware.com)는 소프트웨어적으로 PC 한 대를 에뮬레이션해 주는 상용 프로그램이다. VMWare는 윈도우용과 리눅스용 두 가지가 있으며 어느 경우에든 VMWare를 실행하면 조그만 윈도우 하나에서 부팅시 비프음부터 시작해서 바이오스가 뜬 다음 완전한 시스템이 하나 에뮬레이션으로 실행된다. VMWare는 처음 출시되었을 때, 특히 리눅스와 윈도우를 동시에 사용하기를 원하는 사용자들에게 인기가 높았던 솔루션이었다.

원래 VMware는 소프트웨어 방식으로 하드웨어를 에뮬레이션하는 까닭에 속도가 느리고 높은 하드웨어 사양을 요구한다는 단점이 있었다. 하지만 오랜 개발 과정을 거치면서 최적화가 상당히 많이 진행된 까닭에 최근의 VMWare는 속도도 빠르고 안정성도 높다고 한다. 실제, 윈도우용 VMWare의 가상 하드웨어에 리눅스를 설치한 다음 리눅스 상의 아파치 웹 서버로 인터넷 서비스를 운영하는 엽기적인(?) 경우도 제법 있다.

윈도우와 리눅스의 듀얼 부팅이나 새로운 시스템을 하나 만들지 않고 굳이 VMWare를 선택해서 시스템을 구성할 때의 장점은 무엇일까? 우선, VMWare는 다양한 시스템 구성을 사용자 마음대로 만들어 볼 수 있다. 각 가상 머신마다 메모리 크기, 하드디스크 크기와 같은 설정을 내 마음대로 구성할 수 있으며, 여러 종류의 OS를 마음대로 깔아볼 수 있다. 이렇게 다르게 구성한 시스템은 만들어진 VMWare 하드디스크 이미지 파일만 갖고 있으면 언제든지 다른 구성의 시스템을 부팅해 볼 수 있는 것이다. 즉, 물리적인 하드웨어로 친다면 배포판 A를 깔아놓은 컴퓨터, 배포판 B를 깔아놓은 컴퓨터, 배포판 B를 깔고 여기에 오라클과 같은 DB 셋팅을 한 컴퓨터, FreeBSD를 설치한 컴퓨터 등 이런 식으로 VMWare 사용자는 VMWare를 통해 컴퓨터 여러 대를 갖고 있는 효과를 볼 수 있는 것이다.

아직 VMWare의 이런 동적(dynamic)인 시스템 구성을 굳이 활용할 정도는 아니지만 당장 새로운 배포판이 출시되었다든가 새로운 OS를 테스트해 보기에는 테스트 머신을 새로 구성하기보다는 그냥 VMWare 가상 머신을 하나 더 만드는 것도 괜찮은 방법임을 알 수 있다. 실제, 여러 종류의 리눅스 배포판을 테스트하는 전문 리뷰어들의 경우, 새 배포판을 깔면서 하드디스크를 매번 파티션하기도 귀찮고 하니 VMware를 많이 사용한고 한다. 그리고 소규모의 클러스터링을 할 경우 VMWare를 쓴다면 한 기계에 VMware 가상 머신을 노드 숫자대로 서너 개 띄워놓고 각각의 노드가 제 역할을 잘 해 내는지 테스트해 볼 수도 있을 것이다. 예를 들어 웹 서버와 데이터베이스 서버를 물리적으로 분리했을 때 원래 의도했던 대로 전체 시스템이 잘 동작하는지 체크하는 경우 VMware를 쓰면 물리적인 시스템을 2개 구성하고 직접 케이블링을 하는 것보다 좀 더 간편하게 테스트가 가능할 수도 있는 것이다.

아직은 일반 개발자나 사용자의 입장에서 이런 가상 머신의 활용도는 그다지 중요하지는 않지만 최근 들어 서버 쪽에서는 이런 식으로 가상 머신을 구성하고자 하는 경향이 강해지는 것 같다. 관심 있는 독자는 재미삼아 VMWare를 설치해 보자. 리눅스에서 VMWare를 쓰면 굳이 윈도우로 부팅을 하지 않고서도 윈도우 머신을 리눅스 상에서 그냥 쓸 수 있다는 장점도 있으니 말이다. VMware 이외에 bochs와 같은 오픈소스 하드웨어 에뮬레이터도 있지만 아직은 최적화에서 VMWare에 많이 밀리는 듯하다.

 ② 고급 명령과 시스템 관리


지난 호에 이어 이번 호에서는 리눅스를 설치 한 다음 숙지해야 할 리눅스 시스템의 특성을 살펴보기로 한다. 필자 개인적인 경험으로는 리눅스 시스템의 여러 토픽들 중에서 리눅스를 많이 써 보지 않은 사람들이 조금 당혹할 수 있는 부분이 리눅스의 보안 시스템이라고 생각한다. 실제, 윈도우는 최근 윈도우 2000, XP에 와서야 멀티유저의 개념이 제대로 정착되어 나가고 있는 셈이지만 리눅스는 애초부터 멀티유저의 개념에서 설계가 되었고 또 그렇게 운영되는 시스템이다.

멀티유저 운영체제에서는 자연스럽게 사용자간의 파일 접근 권한이라든가 보안 문제가 부상하게 된다. 대부분의 리눅스 사용자들이 처음에는 윈도우를 쓰는 경우가 많은 까닭에 멀티유저 시스템은 낯설게 느껴질 수밖에 없으며 웹 서버 프로그래밍이 많은 요즈음의 특성상 보안의 기초가 되는 리눅스의 파일 접근 권한(permission)과 소유권(ownership)에 대한 지식은 단단히 다져놓고 넘어가도록 하자. 

파일 접근 권한과 소유권
유닉스의 심플한 설계 철학은 파일 소유권에도 반영되어 있는데 우선 유닉스의 파일은 읽기(read), 쓰기(write), 실행하기(execute) 세 가지의 기능을 수행할 수 있다. 여기서 잠깐 주의할 것은 cd 명령으로 디렉토리를 바꾸어 들어갈 수 있는 권한은 실행하기에 해당한다는 것이다.

유닉스 쪽의 파일 소유권은 우선 사용자(user), 그룹(group), 그리고 그 외의 사람들(others)의 관점으로 나누어진다. 즉, 파일 하나에 대해서 사용자, 그룹, 그리고 그 외의 사람들은 각기 다른 접근 권한을 가질 수 있도록 설계되어 있다. 다음을 보자. 

r w x   r w x   r w x
4 2 1   4 2 1   4 2 1   (2^2, 2^1, 2^0)
O O O | O O O | O O O
user    group   others

유닉스의 파일 소유권은 이와 같이 간단히 도식화 해 볼 수 있다. 사용자, 그룹, 그 외의 사람들마다 3비트로 각각 read, write, execute의 스위치를 on/off 할 수 있다. 3비트는 숫자로 표현해보면 0에서 7까지의 숫자로 나타낼 수 있다. 예를 들어, 어떤 파일의 소유권자에게 읽기와 쓰기 권한을 주고 싶으면 r 스위치와 w 스위치를 올리면 되니 이것을 숫자로 표현해보면 4+2+0=6이 된다. 마찬가지로 읽기와 실행 권한만 주고 싶다면 r 스위치와 x 스위치를 올려서 4+0+1=5가 된다. 이런 방식으로 유닉스의 chmod 명령에서는 숫자를 이용, 파일의 퍼미션을 설정할 수 있다. 다음 예를 보면서 좀 더 자세히 살펴보자.

$ ls -l foobar.txt
-rw-rw----    1 mewmew   mewmew        500  6월  8 23:00 foobar.txt
$ chmod 640 foobar.txt
$ ls -l foobar.txt
-rw-r-----    1 mewmew   mewmew        500  6월  8 23:00 foobar.txt

좀 더 퍼미션의 숫자 표현에 친숙해 지기 위해 한번 8가지 퍼미션의 경우를 다 적어보면 다음과 같다. 조금 귀찮아 보여도 한번 손가락으로 직접 꼽아가며 확인해 보자. 

---  0 --x  1 
-w- 2 -wx 3 
r--   4 r-x  5 
rw-  6 rwx 7 

파일 소유권이 설정되는 과정
이제 유닉스에서 파일 소유권이 어떻게 결정되는지 알아보자. 유닉스에서 파일 소유권은 원칙적으로 그 파일을 ‘생성한’ 유저가 갖게 된다. 즉, mewmew라는 유저가 foobar.txt라는 파일을 하나 만들었다고 가정하자. 유닉스 시스템은 디폴트로 새로이 생성되는 foobar.txt의 소유자를 파일을 생성한 mewmew로 설정하게 된다.

그 다음으로 유닉스 시스템은 foobar.txt의 그룹 소유권을 결정하게 되는데 이를 위해 mewmew라는 유저가 어떤 그룹에 속해 있는지를 /etc/passwd의 패스워드 엔트리를 보고 결정하게 된다. /etc/passwd에 mewmew 유저는 다음과 같이 등록되어 있다. 

mewmew:x:1012:1012:,,,:/home/mewmew:/bin/bash

혹 /etc/passwd의 문법을 잘 모르는 사람은 박스 기사를 참고해서 passwd의 매뉴얼 페이지를 잠깐 읽어보자. 여기서 mewmew은 디폴트로 1012의 uid를 가지고 1012의 gid를 가짐을 알 수 있다. 그렇다면 이제 /etc/group를 잠깐 보자. 

mewmew:x:1012:

따라서 mewmew이 생성한 foobar.txt의 그룹 소유권자의 디폴트는 1012의 gid를 갖는 mewmew 그룹이 된다. 그렇다면 이 foobar.txt의 기본 읽기/쓰기/실행 퍼미션은 어떻게 정해질까? 유닉스에서는 umask를 이용해서 생성되는 파일의 기본 퍼미션이 정해진다. umask는 masking, 즉 빼기 방식으로 파일 퍼미션을 정하는데 보통 022를 umask 값으로 많이 쓴다. /etc/profile의 쉘 설정을 보면 보통 다음과 같은 명령어를 볼 수 있다. 

umask 022

이 명령이 실행되면 일반 파일은 666(rw-rw-rw)의 퍼미션에서 umask의 022를 뺀 644(rw-r--r--)의 디폴트 퍼미션을 갖게 되고 디렉토리의 경우는 777(rwxrwxrwx)의 퍼미션에서 umask의 022를 뺀 755(rwxr-xr-x)를 갖게 된다.

setuid, setgid, sticky bit
setuid는 유닉스에서 볼 수 있는 특이한 파일 보안 개념이다. 일반적으로 실행 가능한 유닉스 파일을 실행할 때 유닉스에서는 그 명령을 내린 유저의 권한에 따라 실행하게 된다. 예를 들어 mewmew 유저가 다음과 같은 퍼미션의 /bin/ls 명령을 실행시키면 mewmew 유저는 root 유저도 아니고, wheel 그룹에 속해 있지도 않지만 others 퍼미션에 명시된 r-x 권한 중에서 x 권한이 허가되어 있는 까닭에 유닉스 시스템은 mewmew 유저의 /bin/ls 명령 실행을 허가해 주게 된다.

-r-xr-xr-x  1 root  wheel  32464 28 May 01:43 /bin/ls

그런데 setuid 옵션이 켜진 파일은 실행될 때, 예를 들어 앞의 경우 /bin/ls 명령에 setuid가 켜져 있다면 mewmew 유저가 명령을 실행했더라도 유닉스 시스템은 /bin/ls 파일의 소유자인 root 유저가 명령을 실행한 것으로 간주해서 /bin/ls 명령을 실행한다. 즉, /bin/ls 명령이 mewmew가 아닌 root 소유로 실행되는 것이다. setuid를 켜는 방법은 다음과 같다. 두 명령 모두 동일한 명령이다. 아래 명령의 경우는 rwxr-xr-x 퍼미션이 755이고 앞의 4가 setuid 옵션이다. 

$ chmod u+s foobar
$ chmod 4755 foobar

두 번째 명령의 경우 숫자 표현이 조금 독특한데 한번 섹션 2의 chmod의 매뉴얼 페이지의 일부분을 인용해 보겠다. 다음의 매뉴얼 페이지 인용을 보면 유닉스의 파일에는 user, group, others이외에 디폴트로 생략되어 있는 3 비트짜리 setuid, setgid, sticky bit 옵션이 있으며, 차례대로 4, 2, 1의 숫자로 나타낼 수 있음을 알 수 있다. 

CHMOD(2)            Linux Programmer's Manual            CHMOD(2)

NAME
       chmod, fchmod - change permissions of a file

SYNOPSIS
       #include 
       #include 

       int chmod(const char *path, mode_t mode);
       int fchmod(int fildes, mode_t mode);

DESCRIPTION
       The mode of the file given by path or referenced by fildes
       is changed.

       Modes are specified by or'ing the following:

              S_ISUID   04000 set user ID on execution

              S_ISGID   02000 set group ID on execution

              S_ISVTX   01000 sticky bit

              S_IRUSR (S_IREAD)
                        00400 read by owner
(이하 생략)

이런 방식은 어디에서 응용하면 좋을까? 간단한 예가 게임에서 하이 스코어 리스트를 관리하는 경우를 들어볼 수 있다. 하이 스코어 리스트는 일반 유저들은 마음대로 고칠 수 없도록 해야 하지만 막상 서로 다른 유저가 하나의 하이 스코어 리스트 파일을 공유하는 것은 쉽지 않은 일이다. foobar, doobar 유저가 fungame을 하는 상황을 생각해보자. fungame의 파일 퍼미션은 다음과 같다. fungame 유저는 fungame 실행파일을 위해 새로 만들어진 유저이다. 그리고 fungame_high_score_list를 다음과 같이 구성했다. 

-r-sr-xr-x  1 fungame  wheel    65535 28 May 08:13 /usr/local/bin/fungame
-rw-r--r--  1 fungame  fungame  38    28 May 09:00 /usr/local/etc/fungame_high_score_list

setuid가 설정되어 있지 않는 경우라면 foobar 유저가 fungame을 실행하면 fungame 실행파일은 foobar 유저의 소유로 실행되기 때문에 fungame에서 fungame_high_score_list 파일을 액세스하면 others에 해당하는 읽기 권한밖에 얻을 수 없다. doobar 유저 역시 마찬가지로 fungame이 실행될 때 doobar 유저가 others로 분류되는 바람에 fungame에서 하이 스코어 리스트 파일을 읽을 수밖에 없는 상황이 벌어진다.

하지만 fungame 실행파일에 setuid를 켜 주면 상황이 달라진다. setuid가 설정된 fungame 실행파일은 어떤 사용자가 이 명령을 실행시키든지 간에 fungame 사용자가 명령을 실행한 것으로 간주하기 때문이다. 따라서 fungame 실행파일은 사용자에 상관없이 fungame_high_score_list 파일을 읽고 쓸 수 있게 된다.

이런 setuid 보안 모델은 보통 일반 유저가 시스템 관련 명령을 실행했을 때 그 명령이 잠깐 동안 관리자인 root 권한을 얻을 필요가 있을 때 많이 사용된다. 이것을 보통 setuid root라고 부른다. setuid root는 보안상의 문제를 초래하기도 하지만 유저에 따라서 케이스를 모두 구분해 주어야 하는 복잡한 경우를 피할 수 있다. 앞의 게임 하이 스코어 리스트 예를 passwd 명령과 /etc/passwd로 바꾸어 생각해 보면 setuid root의 유용함을 쉽게 알 수 있다. 참고로 /etc/passwd와 /usr/bin/passwd의 파일 퍼미션을 살펴보면 다음과 같다. 예상대로 /usr/bin/passwd는 setuid가 root 유저로 설정되어 있고 /etc/passwd 파일은 root 유저가 읽기와 쓰기 권한을 갖고 있음을 볼 수 있다. 

-r-s--x--x    1 root     root        13476  6월 18  2002 /usr/bin/passwd
-rw-r--r--    1 root     root        43188  6월 16 23:10 /etc/passwd

하지만 setuid root는 보안상의 문제가 생길 수 있다는 점을 항상 염두에 두고 있어야 한다. 간단한 예로, 누군가가 root 권한을 재주껏 얻은 다음 bash 하나를 복사하고 거기에 setuid를 설정해 놓은 경우를 생각해보자. 

-rwsr-xr-x    1 root     root       519964  7월  9  2001 bash

이 셸을 실행시키는 사용자는 당연히 root 권한을 갖게 된다. 실제, 간혹 리눅스 서버가 해킹에 뚫리고 난 뒤에는 이렇게 setuid root된 셸이 만들어져 있는 경우가 많다.

setgid는 setuid 만큼 자주 발생하지는 않지만 그 원리는 setuid와 마찬가지로 setgid가 설정된 파일은 실행될 때 그룹 소유자가 그 파일에 지정된 그룹으로 설정된다. setgid는 write 명령과 같이 특정 그룹을 매개체로 액세스 권한을 공유할 때 사용된다. write 명령을 통해 다른 유저에게 메시지를 보내는 경우 setgid가 없는 경우는 다른 사람의 터미널 디바이스(/dev/tty??와 같은)에 디폴트로 쓰기를 할 수 없고 따라서 메시지를 보낼 수 없게 된다. 하지만, 유저들이 쓰는 모든 터미널 디바이스 파일을 tty와 같은 그룹으로 묶고, write 파일에 tty 그룹으로 setgid를 해 놓으면 그룹 퍼미션을 통해서 다른 유저의 터미널 화면에 쓰기를 하는 것이 가능해진다.

sticky bit은 /tmp와 /var/tmp와 같이 임시 파일이 생겨났다가 없어지는 디렉토리에 자주 사용된다. /tmp 디렉토리의 경우 모든 유저들이 마음대로 파일을 읽고 쓰는 것이 가능해야 하는데 이렇게 되면 /tmp 디렉토리는 rwxrwxrwx 퍼미션을 가져야 하고 이것은 유저들이 /tmp 디렉토리에서는 다른 유저의 파일을 지울 수도 있다는 문제를 초래할 수 있다. 하지만 sticky bit이 설정된 디렉토리에서 유저 A는 유저 B가 생성한 파일을 지울 수 없게 된다. 좀 더 정확히 말하면 /tmp와 같은 sticky bit이 설정된 디렉토리에서는 디음과 같은 경우가 아니고서는 파일을 지울 수 없다.

◆ 파일의 소유자
◆ 파일이 놓인 디렉토리의 소유자 
◆ 수퍼유저 

sticky bit은 다음과 같이 t가 추가된 형태로 표기된다. 숫자로 표현하면 1777이 될 것이다. 이것 역시 앞에 잠깐 언급한 chmod(2) 매뉴얼 페이지를 참고해 보자. 

drwxrwxrwt  105 root     root       102400  6월 13 13:09 /tmp

su
일반적으로 윈도우를 많이 쓰던 사용자가 리눅스를 쓰기 시작하면서 접하기 쉬운 실수가 수퍼유저인 root 계정으로 리눅스 시스템을 쓰는 것이다. 하지만 윈도우와는 달리 리눅스는 일반 유저 계정으로도 별다른 불편 없이 모든 작업을 할 수 있고, 필요할 때만 root 계정을 쓰면 되며, 특히 일반 유저 계정을 쓸 경우 바이러스와 같은 악성 코드가 실행 권한을 얻게 되는 것을 구조적으로 방지할 수 있기 때문에 처음부터 일반 사용자로 시스템을 사용하는 습관을 들이는 것이 중요하다. 그런 이유로 리눅스에서는 필요할 때만 su 명령으로 root shell을 획득하고 그렇지 않은 경우는 항상 일반 유저의 권한으로 컴퓨터를 사용하는 것이 좋다. 최근에 윈도우 XP에서도 도입된 Switch User 기능이 윈도우판 su라고 볼 수 있겠다. 


리눅스 시스템의 환경 변수와 디렉토리
필자의 개인적인 생각이지만 리눅스 데스크탑이 아직까지 널리 쓰이지 않고 있는 이유 중의 하나는 리눅스 배포판이 전통적으로 보안에 신경을 많이 쓴 형태로 설정되어 나오기 때문이라고 본다. 따라서 윈도우를 쓰던 사용자들이 리눅스를 쓰면 보안상 이유로 디폴트로 숨겨놓은 기능들을 잘 모르고 지나치게 되고, 이것이 나중에 불편함으로 느껴지는 경우가 많은 듯 하다. 사용자 홈 디렉토리와 환경 변수에서 이런 사소한 불편함을 간단히 몇 가지 다루어 보자.

PATH 환경 변수
우선, 리눅스에서는 명령을 실행할 때 기본적으로 수퍼유저의 권한이 필요한 실행파일들의 디렉토리 위치를 PATH 환경변수에서 빼 놓고 있다. 실제, su 명령을 실행한 다음 fdisk와 같은 수퍼유저용 명령을 실행해보면 대부분의 리눅스 배포판에서 command not found라는 에러 메시지를 만나게 된다. 도대체 PATH가 어떻게 설정되어 있을까?

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games

그렇다면 fdisk는 어디에 들어가 있을까? 다음의 결과를 보면 /sbin/fdisk에 들어가 있음을 알 수 있다. 그러나 PATH 환경 변수에는 /sbin 디렉토리가 들어가 있지 않다. 따라서 일반 유저는 su 명령으로 수퍼유저가 되더라도 PATH 환경 변수에 /sbin 디렉토리가 들어가 있지 않은 까닭에 fdisk 명령어가 실행이 되지 않는 것이다. 참고로 리눅스의 명령어 중 일반 사용자용 명령어들은 전통적으로 bin 디렉토리 아래 많이 들어가고 수퍼유저용 명령어들은 sbin 디렉토리에 많이 들어간다. 

$ find / -name fdisk
/usr/share/webmin/mscstyle3/fdisk
/usr/share/webmin/fdisk
/sbin/fdisk
/etc/webmin/fdisk

그러면 PATH 환경 변수에 /sbin 디렉토리를 추가해 보자. sbin 디렉토리는 /sbin 이외에도 /usr/sbin, /usr/local/sbin이 있을 수 있고, 보통 /usr/sbin에는 배포판이 설치될 때 /sbin 만큼 필수적이지는 않지만 중요한 시스템 관리 명령어들이 많이 들어가 있으니 /sbin과 함께 /usr/sbin도 같이 넣어보자. 참고로 bash 외의 csh이나 tcsh은 여기서는 다루지 않도록 하겠다. 

$ export PATH=${PATH}:/sbin:/usr/sbin

이것을 전통적인 본 셸 방식으로 풀어쓰면 다음과 같이 된다.

$ PATH=${PATH}:/sbin:/usr/sbin
$ export PATH

그러나 이 명령을 셀 하나를 실행할 때마다 타이핑하기에는 귀찮을 테니 이것을 셸이 실행될 때마다 같이 실행되도록 해 보자. bash 셸은 시작하면서 다음과 같은 파일의 내용을 실행시킨다. bash의 매뉴얼 페이지를 잠깐 살펴 보면 FILES 항목 아래쪽에 설정파일에 대한 설명이 나온다.

/etc/profile - system-wide initialization file, executed for login shells 
~/.bash_profile - personal initialization file, executed for login shells 
~/.bashrc - the individual per-interactive-shell startup file 

우리의 경우는 홈 디렉토리의 ~/.bash_profile이나 ~/.bashrc를 쓰는 것이 적당해 보인다. PATH 정보는 일반적으로 로그인 셸 설정에 속하니 .bash_profile에 넣어두면 된다. 참고로 리눅스 시스템 설정을 할 때는 별다른 필요가 없으면 홈 디렉토리 아래의 .로 시작하는 설정파일에 설정해 주는 것이 좋다. 물론, 수퍼유저로 변신해서 직접 /etc 아래의 설정파일을 바꾸어 줄 수도 있지만 항상 내가 수퍼유저 권한을 갖고 있을 수도 없는 것이고, 실제 일반 유저의 권한으로도 내 입맛에 맞게 시스템 환경을 상당한 부분 맞추어 볼 수 있다.

PATH 환경 변수와 관련해서 참고로 하나 알아 둘 것은 현재 디렉토리에 들어 있는 명령을 실행시키는 방법이다. 대부분의 리눅스 배포판에서 hello.c같은 소스를 컴파일하고 다음과 같은 명령을 실행해보면 에러가 난다. 

$ gcc -o hello hello.c
$ ls -F
hello* hello.c
$ hello
bash: hello: command not found
$ ./hello
hello, world

이것은 PATH 환경 변수에서 현재 디렉토리인 점(.)이 빠져 있기 때문이다. 왜 이런 복잡해 보이는 방식이 디폴트로 설정되어 있을까? 그것은 보안상의 문제 때문이다. 예를 들어, 수퍼유저가 /tmp나 /var/tmp 디렉토리에서 무심코 ls 명령을 실행시켰는데 이것이 /bin/ls가 아닌 누군가가 심어놓은 악성 /tmp/ls를 실행시키게 된다면 문제가 생길 수 있다. 이것을 아예 원천적으로 방지하기 위해 현재 디렉토리의 명령어를 실행하려면 보통 앞에 명시적으로 ./를 붙여주는 것이다.

디렉토리
리눅스의 파일 시스템 디렉토리 구조는 전통적인 방식을 지금도 따르고 있다. 루트 디렉토리 아래에 실행 파일은 /bin, 라이브러리는 /lib, 설정파일은 /etc, 수퍼유저용 명령어는 /sbin과 같은 디렉토리에 들어가게 되어 있다. 여기서 유심히 보아야 할 것은 이런 bin, lib, etc와 같은 디렉토리 명명법은 다른 디렉토리 안에서도 반복이 된다는 사실이다. 한번, 소프트웨어 패키지들이 위치하고 있는 /usr 디렉토리를 살펴보자.

$ cd /usr
$ ls -F
X11R6/  dict/  etc/      kerberos/  libexec/  lost+found/  root/  share/  tmp@
bin/    doc/   include/  lib/       local/    man/         sbin/  src/

여기서도 친숙한 bin, etc, lib, sbin과 같은 디렉토리가 보임을 알 수 있다. 실제, 필자가 쓰는 리눅스 시스템에서 /usr/etc 디렉토리에는 아무런 파일도 들어가 있지 않고 그냥 디렉토리 이름만 하나 만들어져 있지만 /usr 디렉토리에서도 / 디렉토리와 동일한 디렉토리 명명법이 반복되고 있음을 볼 수 있다. 이것은 /usr/local 과 /home/mewmew 홈디렉토리에서도 그대로 반복된다. 

$ cd /usr/local
$ ls -F
Zend/      games/                 lib/      mysql-old/     src/
apache/    gd/                    libexec/  netpbm/        var1/
bin/       giflib/                libpng/   python-2.2.1/  zend/
doc/       include/               man/      qmailscan/     zend-old/
etc/       jakarta-tomcat-4.0.3/  mrtg/     sbin/          zend-old-2/
freetype/  jpeg/                  mysql/    share/         zlib/

$ cd
$ ls -F
Mail/    doc/             public_html@  swarm_for_fedora_core_1/
backup/  fish.txt         share/        tel.txt
bin/     hangul_fonts/    src/          temp/
dkdk     man/             storage/      www/

직접 손으로 소프트웨어 패키지 소스코드를 컴파일 해 보면 이러한 명명법이 상당히 쓸모가 있음을 알 수 있다. 일반적으로 ./configure; make; make install로 설치가 되는 소스 패키지의 경우 ./configure 스크립트에서 디폴트로 소스 패키지가 설치되는 디렉토리 (PREFIX)가 /usr/local이다. 이것을 필요에 따라 /usr이라든지 혹은 사용자 홈 디렉토리를 가리키는 $HOME 환경 변수로 설정해 줄 수도 있는 것이다. 

$ ./configure --prefix=/usr
$ ./configure --prefix=${HOME}

다음의 경우 나중에 컴파일이 끝나고 make install을 수행하면 보통 ${HOME}/bin, ${HOME}/etc, 필요에 따라서 ${HOME}/lib나 매뉴얼 페이지가 들어가는 ${HOME}/man 까지 설치되는 경우도 있다. 특히, 관리자 권한을 얻기 힘든 일반 웹 호스팅 서비스를 이용할 때 이렇게 내 홈 디렉토리 안에 손수 필요한 패키지를 컴파일해 쓰는 경우가 흔한데 이럴 경우에는 앞의 PATH 환경 변수에 ${HOME}/bin 엔트리도 하나 넣어 주면 더욱 편리할 것이다.

그리고 리눅스 배포판에서 rpm이나 deb 같은 패키지 관리 시스템이 발전함에 따라 /usr 아래의 /usr/bin, /usr/lib와 같은 디렉토리는 직접 시스템 관리자가 필요한 소프트웨어를 컴파일해서 집어넣는 공간이라기보다는 패키지 관리자로 관리하는 공간이 되어버렸다. 실제, 손으로 컴파일하는 소프트웨어들은 /usr/local 아래쪽에 넣는 것이 나중에 시스템 유지/보수 측면에서도 편리하다. 반드시 /usr 안쪽으로 넣어야 하는 패키지들은 소스코드를 우선 컴파일해서 rpm이나 deb 패키지를 만들고 그것을 다시 설치하는 편이 의존성 유지의 측면에서 유리하다. 이제 리눅스의 전체적인 디렉토리 구조를 쉽게 넘기기 좋은 부분만 간단히 짚어보고 넘어가자. 

/etc
설정파일이 들어가는 곳이다. 사용자의 입장에서는 여기서 /etc/profile.d 디렉토리를 잠깐 지켜볼 필요가 있다. /etc 아래에서 .d로 끝나는 디렉토리들은 마치 그 디렉토리 안에 들어가 있는 파일들이 하나의 설정파일을 이루고 있는듯한 역할을 하는 경우가 많다. bash는 실행되면서 먼저 /etc/profile을 참조하는데 /etc/profile.d가 있는 경우 /etc/profile.d 안의 모든 스크립트들을 실행한다. 참고로 필자가 쓰는 시스템의 /etc/profile.d에는 다음과 같은 스크립트들이 들어있다. 

$ ls -F /etc/profile.d/
colorls.csh*  lang.csh*  less.csh*  mc.csh*  which.sh*
colorls.sh*   lang.sh*   less.sh*   mc.sh*

여기서 sh로 끝나는 파일들은 bash용, csh로 끝나는 파일들은 csh용 스크립트들인데 /etc/profile에 하나로 넣을 수도 있으나 편의상 나누어 놓은 것들이다. 비슷한 예로 /etc/xinet.d를 들 수 있다. inetd는 원래 대몬(daemon)의 대몬으로 불리는 역할을 하는데 inetd에 대몬 서비스를 등록해 놓으면 외부에서 서비스 요청이 들어올 때마다 필요한 대몬 프로세스를 실행시키고 서비스가 끝나면 종료시키는 역할을 하면서 좀 더 시스템 자원을 효율적으로 쓸 수 있도록 해 준다. 이것의 확장판이 xinetd인데 xinetd는 서비스 하나하나마다 설정파일을 따로 두고 이것들을 모아서 /etc/xinetd.d에 넣어놓도록 하고 있다. 

$ ls -F /etc/xinetd.d
chargen      daytime      echo      finger  rexec   rsh    talk    time
chargen-udp  daytime-udp  echo-udp  ntalk   rlogin  rsync  telnet  time-udp

참고로 구형 inetd의 설정파일인 /etc/inetd.conf를 잠깐 살펴보자. 어느 형태를 선호하는지는 사용자의 선택이겠지만 .d 로 끝나는 디렉토리 안에 설정 파일을 각각 분리해 놓는 아이디어가 있다는 점은 기억해 놓도록 하자. 

$ more /etc/inetd.conf
# /etc/inetd.conf:  see inetd(8) for further informations.
#
# Internet server configuration database
#
#       
#
#:INTERNAL: Internal services
#echo           stream  tcp     nowait  root    internal
#echo           dgram   udp     wait    root    internal
#chargen        stream  tcp     nowait  root    internal
#chargen        dgram   udp     wait    root    internal
#discard                stream  tcp     nowait  root    internal
#discard                dgram   udp     wait    root    internal
#daytime                stream  tcp     nowait  root    internal
#daytime        dgram   udp     wait    root    internal
#time           stream  tcp     nowait  root    internal
#time           dgram   udp     wait    root    internal

/etc/skel 디렉토리에는 얼핏 보면 아무 파일도 없는 것처럼 보인다. 하지만 이 디렉토리에는 새로 유저 계정을 만들었을 때 그 유저의 홈 디렉토리에 디폴트로 설치될 숨겨진 설정파일들이 들어가 있다. 

$ ls -al /etc/skel
합계 28
drwxr-xr-x    2 root     root         4096 2004-05-13 13:53 .
drwxr-xr-x  121 root     root         8192 2004-06-10 11:53 ..
-rw-r--r--    1 root     root          266 2002-07-09 23:20 .alias
-rw-r--r--    1 root     root          704 2004-05-09 01:43 .bash_profile
-rw-r--r--    1 root     root         1290 2004-05-09 01:43 .bashrc
-rw-r--r--    1 root     root          375 2002-07-09 23:20 .cshrc

리눅스에서 새로 유저 계정을 추가하는 방법은 여러 가지가 있다. 전통적으로 많이 쓰는 방법이 adduser, 혹은 useradd 스크립트 명령을 이용하는 방법이고 요즘은 KDE나 GNOME에서는 그래픽 유저 인터페이스 환경에서 새로운 사용자를 추가할 수도 있다. 하지만 원래 리눅스 시스템에서는 직접 손으로 사용자를 추가하는 것도 가능하다. 간단하게 새로 사용자 계정을 만들어보자. 우선, 리눅스의 사용자는 숫자로 된 사용자 ID와 그룹 ID가 필요하다. 이것이 uid와 gid인데 su 명령 등을 이용해서 수퍼유저가 된 다음 한번 /etc/passwd 파일을 보자. 

root:x:0:0:sysadmin,,,:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
.
...생략...
.
foobar:x:1000:1000:Foobar the great one,,,:/home/foobar:/bin/bash
doobar:x:1001:1001:Doobar the beautiful one,,,:/home/doobar:/bin/bash
langel:x:1002:1002:,,,:/home/langel:/bin/bash

여기에 새로 hoover라는 사용자를 추가하려면 /etc/passwd 파일을 적당히 비슷하게 고쳐주면 된다. 새로 uid 1003과 gid 1003를 langel에 추가해주자. 패스워드 필드에는 *를 넣어 아직 로그인을 못하는 계정임을 명시해 주자. 

hoover:*:1003:1003:,,,:/home/hoover:/bin/bash

마찬가지로 /etc/group 파일도 hoover 그룹을 추가해 주자. 어떤 시스템은 사용자의 그룹을 새로 만들지 않고 그냥 user 그룹에 추가하는 경우도 있다. 

root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:
tty:x:5:
.

... 생략 ...
.

foobar:x:1000
doobar:x:1001
langel:x:1002
hoover:x:1003

이렇게 한 다음 mkdir 명령으로 /home/hoover 디렉토리를 만들고 /etc/skel 디렉토리에서 기본 설정파일을 cp 명령으로 복사해준다. 그리고 chown -R hoover /home/hoover 명령과 chgrp -R hoover /home/hoover 명령으로 홈 디렉토리와 그 아래 파일들의 소유권을 명확하게 해 준 다음 마지막으로 passwd hoover 명령으로 새로운 hoover 사용자의 비밀번호를 설정해주면 직접 손으로 새 사용자 등록이 끝나게 된다. useradd와 같은 명령은 이 과정을 간단하게 자동화 시켜놓은 셈인 것이다.

여기서도 리눅스의 심플함의 매력이 묻어나는데 오래전에 필자는 솔라리스 워크스테이션을 처음 쓰면서 솔라리스에는 아예 리눅스에서 볼 수 있었던 대화식 useradd 명령어조차 없던 것을 보고 놀랬던 기억이 있다. 그래도 솔라리스에도 이런 useradd와 같은 사용자 생성 스크립트가 하나 기본으로 제공되어 있으면 좋지 않을까 했지만 오히려 이런 스크립트가 없는 것이 더 나을 수도 있다는 생각을 곧 하게 되었다. 사용자가 아닌 시스템 관리자의 입장에서는 사용자 계정 발급과 말소는 주요한 업무 중의 하나일 텐데 사용자 계정 생성과 삭제를 특정 명령어에 묶어 놓으면 좀 더 유연한 시스템을 만들기가 힘들다. 

한 예로, 갑자기 전산 실습을 위해 1000명의 ID를 새로 발급해야 한다든가, 혹은 하루에도 수백 명씩 ID가 생성되고 없어지는 시스템을 유지하려면 오히려 이런 심플함이 시스템 관리자의 입장에서 좀 더 편리하고 효율적인 사용자 계정 관리 시스템을 만드는데 도움이 된다. 반복 작업을 스크립트로 처리할 수도 있고 웹 인터페이스와 연동을 할 수도 있기 때문이다. 어쨌든, 우리가 관심이 있는 시스템은 이런 대규모 시스템은 아니니 심플함이 시스템이나 레이어 간에 인터페이스를 구성하는데 도움이 될 수 있다는 점만 체크하고 넘어가도록 하자. 

/dev, /proc
유닉스의 특징 중의 하나는 모든 것을 파일로 처리한다는 점이다. 이것이 유닉스 상의 프린팅과 같이 어떨 때는 자유도가 지나친 까닭에 오히려 복잡한 상황을 초래하기도 하지만 /dev 처럼 디바이스도 파일로, /proc 처럼 프로세스와 시스템 정보도 파일로 처리하는 아이디어는 한번쯤 짚고 넘어갈 필요가 있다. 여기서는 /proc 파일시스템을 위주로 살펴보자.

/proc 파일시스템에 들어가 보면 맨 처음 보이는 것이 수백MB 정도의 크기를 갖는 kcore 파일이다. 유심히 보면, kcore 파일의 사이즈가 자신의 시스템 메모리 크기와 일치한다는 사실을 알 수 있다. 이렇게, /proc 파일시스템은 가상의 파일시스템으로서 현재의 시스템 정보를 파일의 형식을 빌어 저장해 놓고 있는데 여기서 몇 가지 중요한 파일들은 다음과 같다. 대부분은 more 명령이나 vi와 같은 에디터로 직접 열어볼 수 있는 텍스트 형식의 파일들이다. 

하드웨어 정보 
/proc/cpuinfo - 시스템 CPU에 관한 정보가 들어가 있다. 
/proc/interrupts - 시스템 하드웨어 인터럽트 사용 정보 
/proc/dma - dma 정보 
/proc/devices - 커널에 설정되어 있는 디바이스 정보 

현재 시스템 구동 정보 
/proc/net - 네트워크 정보 
/proc/uptime - 시스템이 부팅 후 지금까지 계속해서 켜져 있는 시간 
/proc/stat - 여러 가지 시스템 정보 
/proc/loadavg - 시스템에 부하가 걸린 정도 
/proc/meminfo - 시스템 메모리 사용 정보 
/proc/modules - 현재 로딩되어 있는 모듈 정보 

/proc 디렉토리 아래에 숫자로 만들어진 디렉토리는 모두 실행중인 프로세스이다. 이 디렉토리 안에 들어가면 그 프로세스에 관한 여러가지 정보를 알 수 있다. 참고로 윈도우의 디바이스 드라이버와 리눅스의 모듈을 혼동하는 경우가 간혹 있는 것 같아 설명을 몇 자 덧붙이면, 리눅스의 모듈은 윈도우의 디바이스 드라이버와 같다고 생각해도 되지만 리눅스 모듈은 단어 그대로 modular하다는 장점이 있다. 리눅스 모듈을 이용하면 시스템을 재부팅하지 않고도 디바이스 드라이버를 로딩(loading)/언로딩(unloading) 할 수 있는 것이다. 이런 이유로 리눅스에서는 자주 쓰이지 않는 하드웨어는 필요할 때만 모듈을 로딩하고 그 하드웨어를 사용한 다음 다시 모듈을 언로드해서 시스템 자원을 아껴 쓸 수 있다. 

$ cd /proc/862
$ ls
cmdline  cwd  environ  exe  fd  maps  mem  mounts  root  stat  statm  status
$ ls -al
합계 0
dr-xr-xr-x    3 mp3      mp3           0 2004-06-17 16:55 .

dr-xr-xr-x  112 root     root          0 2004-06-10 20:52 ..

-r--r--r--    1 mp3      mp3           0 2004-06-17 16:56 cmdline

lrwxrwxrwx    1 mp3      mp3           0 2004-06-17 16:56 cwd -> /proc/862

-r--------    1 mp3      mp3           0 2004-06-17 16:56 environ

lrwxrwxrwx    1 mp3      mp3           0 2004-06-17 16:56 exe -> /bin/bash

dr-x------    2 mp3      mp3           0 2004-06-17 16:56 fd

-r--r--r--    1 mp3      mp3           0 2004-06-17 16:56 maps

-rw-------    1 mp3      mp3           0 2004-06-17 16:56 mem

-r--r--r--    1 mp3      mp3           0 2004-06-17 16:56 mounts

lrwxrwxrwx    1 mp3      mp3           0 2004-06-17 16:56 root -> /

-r--r--r--    1 mp3      mp3           0 2004-06-17 16:56 stat

-r--r--r--    1 mp3      mp3           0 2004-06-17 16:56 statm

-r--r--r--    1 mp3      mp3           0 2004-06-17 16:56 status

$ more cmdline
-bash
$ more environ
USER=foobar=foobarHOME=/home/foobar/resources/CD-RipPATH=/usr/local/bin:/bin:/u
sr/bin:/usr/X11R6/binMAIL=/var/mail/mp3SHELL=/bin/bashSSH_CLIENT=
220.73.160.119 
39288 22SSH_CONNECTION=220.73.160.119 39288 147.66.166.66 22SSH_TTY=/dev/pts/4TE
RM=xtermLANG=ko_KR.EUC-KR
$ more stat
862 (bash) S 861 862 862 34820 937 0 1253 1913 387 8166 7 1 15 37 15 0 0 0 62030
688 4096000 440 4294967295 134512640 135116540 3221224976 3221224296 1074886392 
0 65536 3686404 1266761467 3222391889 0 0 17 0

이러한 /proc 파일 시스템의 정보를 좀 더 사용자가 편하게 볼 수 있도록 해 주는 것들이 ps, top, uname, uptime과 같은 명령어들이다. 

/usr
/usr 디렉토리 아랫쪽에는 다양한 서브디렉토리가 존재한다. X윈도우가 /usr/X11R6 디렉토리 아래쪽으로 위치하고 있으며 /usr/share, /usr/doc과 같은 디렉토리들도 존재한다. 여기서 눈여겨 볼 것은 /usr/lib 디렉토리 아랫쪽의 라이브러리들과 /usr/include 아래쪽의 헤더 파일들이다. 이곳에 들어가 있는 라이브러리와 헤더파일들은 컴파일러가 명시적으로 디렉토리를 지정하지 않아도 알아서 찾아 준다. 

다음 호에서는 개발자에게 필요한 도구를
이번 호에서는 유닉스의 보안 모델의 기초가 되는 파일 퍼미션과 소유권에 관련한 내용, 그리고 리눅스 시스템을 접했을 때 쉽게 친숙해지지 않는 개념과 명시적으로 잘 기술되어 있지 않는 전통적인 관습을 명령어와 함께 알아보았다. 다음 호에서는 이번 호 기사에서 조금 더 보충할 필요가 있는 유닉스 명령어를 잠깐 추가하고 개발자들에게 필수적인 에디터와 다른 보조 도구들의 사용법을 다루어 보도록 하겠다. @

man 명령어 사용하기  
유닉스에 적응하면서 겪게 되는 낯설음 중의 하나가 man 명령어의 생소함이다. 윈도우의 도움말과는 달리 man 명령어는 상당히 내용이 어렵고 형식이 지나치게 딱딱한 면이 없지 않고, 웹과 같은 하이퍼텍스트가 이미 일반화된 지금에는 그다지 매력적으로 보이지 않을 수도 있지만 유닉스 매뉴얼 페이지의 강점은 그 간결함과 통일된 형식이다. 익숙해질수록 편리해지는 유닉스와 마찬가지로 매뉴얼 페이지도 익숙해지는 노력을 들일만한 가치가 있다.

man 명령의 형식은 man 명령어 다음에 궁금한 사항을 적어주면 되는 간단한 구조를 갖고 있다. 하지만 매뉴얼 페이지는 원래 서가에 있는 여러 권의 매뉴얼의 구조를 갖고 있다. 매뉴얼 페이지는 다음의 8가지 파트로 구성되어 있다.

Commands available to users 
Unix and C system calls 
C library routines for C programs 
Special file names 
File formats and conventions for files used in Unix 
Games 
Word processing packages 
System administration commands and procedures 

여기서 눈여겨 볼 부분은 섹션 3이다. 예를 들어, 웬만한 C 함수들은 이곳에 모두 정리가 되어 있다. man 명령어는 단순히 유닉스 명령어에 관한 설명만 다루는 것이 아니고 매뉴얼화 할 필요가 있는 항목들을 모두 담고 있는 것이다. 그런데 여기서 하나 조심해야 할 것이, man 다음의 항목이 여러 섹션에 걸쳐 있는 경우이다. 예를 들어 printf 함수에 대한 매뉴얼 페이지를 보고 싶어서 man printf를 하면 다음과 같이 섹션 1의 유닉스 명령어에 관한 매뉴얼 페이지가 디폴트로 출력된다. 

PRINTF(1)                      FSF                      PRINTF(1)

NAME
       printf - format and print data

SYNOPSIS
       printf FORMAT [ARGUMENT]...
       printf OPTION

DESCRIPTION
       Print ARGUMENT(s) according to FORMAT.

       --help display this help and exit

       --version
              output version information and exit

(이하 생략)

그러나 우리가 찾고 싶은 printf 함수의 매뉴얼 페이지는 프로그래밍에 관련된 섹션 3에 저장되어 있다. 섹션 3을 명시적으로 지정해 주려면 다음과 같이 섹션 넘버를 적어주면 된다.

$ man 3 printf

그러면 우리가 원하는 printf 함수에 대한 매뉴얼 페이지가 출력된다. 

PRINTF(3)           Linux Programmer's Manual           PRINTF(3)

NAME
       printf,  fprintf,  sprintf,  snprintf,  vprintf, vfprintf,
       vsprintf, vsnprintf - formatted output conversion

SYNOPSIS
       #include 

       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);
       int sprintf(char *str, const char *format, ...);
       int snprintf(char *str, size_t size, const  char  *format,
       ...);

이렇게 여러 섹션에 이름이 같은 항목이 존재하는 경우를 쉽게 찾기 위해서는 man -k 명령을 사용하면 좋다. (man -k 대신에 apropos명령어를 써도 된다) printf의 경우 man -k 명령을 쓰면 다음과 같은 결과를 얻을 수 있다. 

$ man -k printf

printf               (1)  - format and print data
printf               (3)  - formatted output conversion

시스템 설정 파일의 문법도 매뉴얼 페이지에서 다루는 항목 중의 하나이다. 예를 들어 /etc/passwd의 문법을 참조하고 싶을 때는 다음과 같이 원하는 매뉴얼 페이지를 찾을 수 있다. 참고로 섹션 5는 시스템 설정파일을 다룬다. 

$ man -k passwd

passwd               (1)  - update a user's authentication tokens(s)
passwd               (5)  - password file

$ man 5 passwd
PASSWD(5)                  File formats                 PASSWD(5)

NAME
       passwd - password file

DESCRIPTION
       Passwd  is  a  text file, that contains a list of the sys-
       tem's accounts, giving for each account some useful infor-
       mation like user ID, group ID, home directory, shell, etc.
       Often it also contains the encrypted  passwords  for  each
       account.   It  should  have  general read permission (many
       utilities, like ls(1) use it  to  map  user  IDs  to  user
       names), but write access only for the superuser.

매뉴얼 페이지를 계속 살펴보면 알겠지만, 리눅스 시스템의 매뉴얼 페이지는 단순히 리눅스 운영체제와 프로그래밍에 관련된 부분 이외에 Perl 레퍼런스 가이드(perl reference guide)와 같은 얼핏 생각하기에는 따로 다운받아야 할 것 같은 정보도 방대한 양이 이미 들어가 있음을 알 수 있다.

참고로 GNU 시스템에서는 man보다는 info의 사용을 권장하고 있다. info는 man과 비슷한 구조를 갖고 있지만 하이퍼텍스트 기능이 들어가 있어 브라우징이 좀 더 편리하다. 여기서는 info는 다루지 않기로 한다.

지금은 많이 잊혀져가는 테크닉이지만 고전적인 유닉스 방식으로 출력용 깔끔한 메뉴얼 페이지를 만들어 보자. man 명령어에는 -t 옵션을 주면 매뉴얼 페이지를 포스트스크립트 형식으로 출력을 해 준다. 한번 고스트스크립트를 이용해서 어떤 모습으로 출력이 될 것인지 프리뷰를 해 보자. gv의 - 옵션은 포스트스크립트 파일 입력을 표준 출력에서 받겠다는 옵션이다. 

$ man -t 5 passwd | gv -

이것을 pdf 파일로 만들려면 다음과 같은 간단한 명령어로 해결이 될 것이다. 심플한 명령어 한두 가지로 복잡한 기능까지 처리해 내는 유닉스의 미학이 드러나는 부분이다.

$ man -t 5 passwd | ps2pdf > passwd_manual.pdf

③ vim 편집기 활용법


사용자의 입장에서 윈도우와 가장 두드러지게 눈에 띄는 리눅스, 그리고 다른 유닉스 운영체제의 차이점은 무엇일까? 여러 차이점, 특히 관습이나 문화적인 차이점도 많이 있겠지만 vi 에디터의 독특함도 중요하게 작용하고 있다고 본다.

최근 들어서는 유닉스를 처음 배우면서 에디터로 vi를 반드시 배워야 하는 상황은 많이 줄었지만 여전히 vi는 많이 쓰이고 있으며 그 편리함과 독특한 매력은 많은 사용자를 확보하고 있다. 아직까지 vi를 제대로 쓰지 않고 리눅스를 사용하고 있다면 이번 기회에 조금 귀찮더라도 반드시 vi 기본 명령어는 습득해 보도록 하고, 또 추가로 많이 쓰이는 vi 관련 설정을 살펴보기로 하자.

vi를 처음 접하는 사람에게 가장 까다롭게 느껴지는 것은 vi가 소위 명령어 모드(command mode)와 입력 모드(input mode)가 나뉘어져 있다는 특성 때문이다. vi를 처음 실행시키고 타이핑을 해보면 아무런 글자도 입력되지 않고 심지어는 비프음까지 동반하게 되는데 많은 사용자들이 이런 vi의 편집기답지 않은 황당한(?) 모습에 금방 거부감을 갖게 되는 경우가 많다. 하지만 일단 vi의 명렁어 모드와 입력 모드의 차이점을 인식하고 몇 가지 커서 이동 명령어만 학습하고 나면 vi의 매력을 금방 느낄 수 있으니 겉모습만을 보고 오해해서 vi를 버리지 말도록 하자.

vim은 정확히 말하자면 여러 vi 클론들 중의 하나이다. vim의 이름은 vi improved에서 따왔다고 하는데 vim은 기본적인 vi의 기능에 덧붙여서 여러 개의 파일을 동시에 편집한다든가 프로그램 소스코드에 색깔을 덧붙여 가독성을 높여주는 syntax highlighting과 같은 기능을 추가로 지원한다. 

pico, nano
우선, vi를 배우는 동안 간단히 쓰기 좋은 에디터로는 pico나 pico의 자유 소프트웨어 클론인 nano를 권장한다(젠투 리눅스는 시스템 설치 과정에서 nano를 사용한다). pico나 nano는 어느 배포판이나 기본으로 설치되어 있는 경우가 많으며 명령어 안내가 간단하게 화면 아래쪽에 출력되기 때문에 불편하지만 쉽게 쓸 수 있다. 오래 전 PC통신 시절의 경험이 있는 사용자라면 이미 pico를 많이 사용해 보았을 수도 있다. pico의 사용 방법은 무척 간단하기 때문에 여기서는 생략하도록 한다. 

명령어 모드와 입력 모드
앞에서 잠깐 얘기했듯이 vi는 명령어 모드와 입력 모드가 나뉘어서 동작한다. 예를 들어 명령어 모드에서는 h 키를 누르면 왼쪽 화살표 키와 같이 커서가 왼쪽으로 한 칸 이동하지만 입력 모드에서는 화면에 h 글자가 입력된다.

vi는 실행되었을 때 기본으로 명령어 모드가 작동된다. 따라서 vi를 실행한 뒤 아무리 키를 눌러봐야 글자가 타이핑되지 않는다. 입력 모드로 들어가기 위해 i 키(insert, 삽입)를 눌러 보자. i 키를 누른 이후로는 타이핑이 될 것이다. 참고로 a 키를 누르면 커서 다음 글자부터 입력이 된다. 추가로 대문자 I와 A의 차이점도 직접 확인해 보자.

서너 줄 정도 간단한 글을 입력한 다음 이제 다시 명령어 모드로 돌아가기 위해 esc 키를 눌러보자. esc 키는 vi에서 가장 중요한 키 중의 하나이며 esc를 누르면 vi는 항상 명령어 모드로 돌아간다. 여기서 다시 i나 a 키를 누르면 삽입 모드로 들어간다(한 줄을 비우고 삽입 모드로 들어가는 o와 O도 시험해 보자). 이제 esc 키를 눌러서 명령어 모드로 돌아간 뒤 커서를 상하좌우로 이동해보자.

요즘은 상황이 많이 달라졌지만, vi에서는 화면 상의 커서를 상하좌우로 이동하기 위해 키보드 위의 상하좌우 화살표 키를 원칙적으로 쓰지 않는다. vim에서는 화살표 키를 써도 커서 이동이 가능하지만 일단은 vi에서는 화살표 키를 쓰지 않는 습관을 들여 보자. vi에서는 커서 이동키로 h(좌), j(하), k(상), l(우)을 사용한다. 한 페이지 위로 올라갈 때는 pgup 키 대신 , 한 페이지 아래로 내려갈 때에는 pgdn 키 대신를 사용한다. <표 1>를 참조해서 커서 이동키를 외우도록 하자.

vi의 커서 이동 키  
vi의 추가 커서 이동키  

vi에서는 왜 이렇게 낯선 커서 키 배열을 사용할까? 그 이유 중 하나는 vi가 커서 키로 사용하고 있는 h, j, k, l 키가 화살표 키보다 사용하기 훨씬 편리하기 때문이다. 다만 vi의 키 배열은 아무래도 처음 익숙해지고 외우기까지가 불편하다. 하지만 vi의 명령어 키는 모두 기본 자판만을 사용하기 때문에 커서를 움직이거나 페이지를 스크롤 하기 위해 오른손을 들어 화살표 키가 있는 곳까지 손을 뻗는 수고를 덜어준다. vi를 오래 쓰다보면 윈도우에서도 무심코 esc 키를 누르고 j, k 키를 눌러 커서를 무심코 이동시키려는 중독 증세가 나타나 간혹 사람을 당황하게 하는 경우도 생긴다.

일단 h, j, k, l 커서 키와 페이지 업, 페이지 다운 키로 에 익숙해진 다음 몇 가지 이동키를 더 익혀보자. 한 단어를 이동하는 키는 w, 한 단어를 뒤로 이동하는 키는 b이다. 시프트 키를 누르고 대문자 W, B를 누르면 띄어쓰기가 된 단어 단위로 커서가 이동한다. w와 비슷한 기능으로 e를 들 수 있는데 e 키는 커서가 단어의 맨 뒷부분에 위치하게 된다.

추가로 워드프로세서에서 Home 키에 해당하는 vi의 키는 0이고 End에 해당하는 키는 $이다. 윈도우 환경에서는 Home이나 End 키를 안 쓰는 사람도 많지만 vi에서는 많이 쓰이니 반드시 익혀두자. 여기에 커서 이동키로서의 (, ) (문장 단위 이동), {,} (문단 단위 이동), H, M, L(화면 첫줄, 가운뎃줄, 마지막줄) 키, 1G (첫줄), G(마지막줄)의 쓰임새까지 학습하고 나면 vi로 가장 기본적인 문서 편집을 하는 데는 문제가 없게 된다.

[로케일 설정]  

여기서 다른 에디터에서는 보기 힘든 vi의 또 다른 중요한 문법을 배워보자. w 키를 사용해 한번에 두 단어, 세 단어를 뛰어 넘어 커서를 움직이려면 어떻게 해야 할까? 방법은 그만큼의 숫자를 w 키를 타이핑하기 전에 먼저 입력해 주면 된다. 즉, 두 단어를 건너뛰고 싶으면 2w, 세 단어는 3w와 같이 타이핑을 해주면 되는 것이다. 같은 방법으로 위로 다섯줄을 올라가고 싶으면 5k, 아래로 열 줄을 내려가고 싶으면 10j와 같은 키 조합을 만들어 볼 수 있다.

vi를 종료하기 위해서는 ‘:’ 키를 눌러서 vi의 전신인 ex의 명령어 모드로 들어가야 한다. : 키를 누르면 화면 맨 아래쪽에 : 프롬프트가 뜨는 것을 볼 수 있는데 여기서 검색이라든가 새 파일 열기와 같은 좀 더 복잡한 명령을 입력할 수 있다. 종료 명령인 q를 누르고 엔터 키를 치면 vi가 종료되는데 지금은 편집 중인 파일이 있는 관계로 vi가 아직 파일이 저장되어 있지 않다는 에러 메시지를 보여준다. 강제로 종료하기 위해서는 :q! 명령을 입력하면 된다. 저장을 하려면 :w <파일이름> 명령을 실행한 뒤 q 명령으로 vi를 종료하자.

보통은 vi로 파일을 편집할 때는 $ vi 과 같이 셸에서 vi를 실행시킬 때 파일 이름을 명시해주고 종료할 때는 :wq 명령어로 저장을 하고 vi를 종료하는 방법을 쓰는 것이 편리하다. 

vi에서의 텍스트 편집
삭제
이제 글자 삭제를 해볼 차례이다. 물론 vi의 입력 모드에서는 백스페이스 키가 정상적으로 작동하지만 명령어 모드에서는 백스페이스가 아닌 x 키를 사용한다. 그런데 실제 문서 편집 작업을 하다보면 글자 단위의 삭제보다는 단어나 한 줄 단위, 혹은 커서가 위치한 부분부터 줄 끝까지 삭제를 하는 경우가 많다. 이런 경우에 쓰는 vi 명령키가 d이다. d 다음에는 지울 범위를 설정해 주어야 하는데 다음과 같은 키 조합을 만들어 볼 수 있다. 

◆ dw : 커서가 있는 위치부터 오른쪽으로 한 단어 삭제 
◆ db : 커서가 있는 위치부터 왼쪽으로 한 단어 삭제 
◆ d$ : 커서가 있는 위치부터 문장 끝까지 삭제 
◆ d} : 커서가 있는 위치부터 한 문단 끝가지 삭제 

앞의 경우와 마찬가지로 vi의 명령어 키 조합을 응용해 보자. 

◆ d3w : 커서가 있는 위치부터 오른쪽으로 세 단어 삭제 
◆ d5j : 커서가 있는 위치부터 아래로 다섯 줄 삭제 

참고로 dd 명령키는 한 줄을 지울 때 사용한다. d$는 D로 간편하게 줄여 쓸 수 있다. 또한 d(delete) 대신에 c (change) 명령키를 사용하면 vi는 삭제 대신에 치환 기능을 수행한다. 치환 기능은 vi가 같은 범위의 d 명령을 수행한 다음 i 키를 알아서 눌러 입력 모드로의 전환까지 자동으로 해준다고 생각하면 된다. 텍스트 편집은 결국 어느 부분을 삭제하고 나면 그 부분을 다른 단어들로 수정하기 마련이며 vi에서는 다른 비주얼한 에디터와는 달리 삭제와 수정까지 치환 기능을 통해 한번에 간편히 해결해 주고 있다. 실제, 필자의 경우는 vi를 쓰면서 d 키보다 c 키를 더 많이 쓰는 편이다. 비슷한 기능키로 s, S와 r도 간혹 사용된다.

자르고 붙이기 
윈도우나 기타 GUI 데스크탑 환경에서 자르고 붙이기(cut & paste) 기능은 이해하기도 쉽고 실제 많이 쓰는 기능이다. vi에서는 자르기(cut) 기능은 d 키로 삭제했을 때 삭제된 부분이 자동으로 기억되며, 이것을 소문자 p나 대문자 P 키를 눌러서 붙일(paste) 수 있다. 일례로 문단 하나를 cut & paste하는 경우를 생각해보자. 윈도우에서는 마우스로 해당 부분을 선택하고 풀다운 메뉴에서 「Edit | Cut」 메뉴를 선택하고 커서를 적절한 위치에 놓은 다음 풀다운 메뉴에서 「Edit | Paste」 메뉴를 선택 이런 방식으로 옮긴다.

이것을 vi에서는 d 명령으로 해당 부분을 지우고 커서를 적절한 위치에 놓은 다음 p 명령키로 붙여넣기의 방식을 따른다. 그렇다면 undo 기능은 어떻게 사용할 수 있을까? vi에서 undo 키는 단순히 u 키를 누르면 되며 vim의 경우 redo 기능이로 추가되어 있다.

복사하고 붙이기
vi에서 복사하고 붙이기(copy & paste)에서 복사 기능을 수행하는 vi의 명령키는 y(yank)이다. d 대신에 y 키를 누르고 복사하고 싶은 부분만큼 선택한 다음 p 키를 눌러서 붙여넣기 작업을 진행하면 된다. y 키 역시 마찬가지로 yw, y$, y}, y10j와 같은 다양한 응용이 가능할 것이다. yy 명령키는 dd와 비슷한 역할을 한다. yy 명령키는 p 명령키와 같이 조합해서 특히 많이 쓰이는 편이다.

두 줄을 한 줄로 합치기
두 줄을 한 줄로 합치기 위해서는 대문자 J 명령키를 사용한다. 반대로, 한 줄을 두 줄로 나눌 때는 r 키와 엔터 키의 조합을 쓰면 편리하다. r 명령키는 커서가 위치한 곳의 문자 하나를 치환(replace) 하고 명령어 모드로 복귀한다. 여기서 잠깐 유닉스의 파이프를 이용한 팁 하나를 알아보자. vi에서는 이렇게 J 키로 두 줄을 합치다 보면 문단의 오른쪽이 들쑥날쑥해지는 것이 그다지 보기 좋지 않은 경우가 많다.

이럴 때 유닉스의 파이프(pipe) 기능을 이용해서 문단 정렬을 해볼 수 있다. 유닉스에는 기본으로 fmt 명령이 제공된다. fmt는 문단을 72컬럼 근처에서 다듬어주는 역할을 한다. 보통 다음과 같은 방식으로 쓰면 document.txt의 문단을 예쁘게 정렬해서 출력해 주는 아주 간단한 유틸리티이다. 

$ cat document.txt | fmt

이것을 vi에서 사용해 보자. vi에서는 ‘!’ 키를 누르면 필터(filter)가 동작한다. ! 키를 누르고 처리할 텍스트 범위를 선택한 다음 외부 명령어 이름을 적어준 뒤 엔터를 누르면 그 외부 명령어가 처리한 텍스트 결과물이 다시 vi 편집 화면에 돌아오는 것이다. 이제 한 문단 정렬을 외부 명령어 fmt를 이용해 보자. 방법은 다음과 같다.

[1] 줄이 들쑥날쑥한 문단에 커서를 위치시킨다. 
[2] ! 키를 누른다. 
[3] } 키를 눌러 커서가 위치한 문단 끝까지 선택한다. 
[4] 외부 명령어인 fmt를 타이핑한 뒤 엔터 키를 친다.

이렇게 하면 문단이 fmt를 통해 예쁘게 정렬된다. ! 키를 누른 이후에 선택한 부분이 fmt에 들어가서 포맷팅이 되고, 그 결과가 다시 vi 편집 화면으로 돌아온 것이다. 조금 복잡해 보이지만 vi가 간단한 기능으로도 강력한 성능을 발휘할 수 있는 특징 중의 하나이므로 한번쯤 연습해 보도록 하자. 필자의 경우에는 } 대신 아예 파일 끝부분을 가리키는 G를 이용해서 편집중인 파일 전체를 fmt를 통해 문단 정렬을 자주 하는 편이다.

파일 저장 및 vi 끝내기
파일 열기, 저장, 그리고 vi 끝내기는 : 키를 눌러서 명령어 입력 모드로 전환한 다음 이루어진다. :w 명령어로 편집 중인 파일을 저장할 수 있다. :q 명령은 vi 종료 명령이다. 안전하게 파일을 저장하고 vi를 빠져 나오려면 :wq 명령을 이용한다.

vim의 추가기능
이제 vi의 확장판 클론인 vim의 유용한 기능들을 살펴보기로 하자. vim의 여러 가지 확장된 기능 중에서 우리가 중점적으로 살펴볼 부분은 다음과 같다. 

◆ syntax highlighting 
◆ 들여쓰기 
◆ 다중 윈도우 사용 

우선 vim 사용에 편리한 설정 몇 가지를 먼저 살펴보자. vim의 설정은 /etc/vimrc나 ~/.vimrc에 저장해 놓으면 된다. vimrc의 명령어는 vim의 : 명령 프롬프트에서 직접 입력해서 실행하는 것과 동일하다.

vim 사용시 한글 관련 옵션
vim에서 한글을 쓸 때 편집 도중에 한글이 깨지지 않도록 하기 위해서는 vimrc에 다음 옵션을 추가한다.

set fileencoding=korea
set encoding=korea

set fileencoding 명령은 vim이 편집 중인 파일을 저장할 때 어떤 인코딩을 사용할 것인가를 결정해 주고 set encoding은 vim 내부에서 어떤 문자 인코딩을 사용할 것인지를 결정해 준다. 이 옵션으로도 vim에서 한글 단어 단위의 커서 이동이나 한글 문자 삭제시 글자가 깨진다면 bash 셸에서 언어 환경을 한국어로 명시해 주자.

$ export LANG=ko_KR.eucKR

참고로 최근 들어 많은 배포판들이(특히 GNOME 데스크탑 환경에서) 한국어 환경의 한글 코드를 완성형에서 유니코드(utf-8)로 이동했다. 이런 경우는 굳이 앞의 encoding과 fileencoding 설정을 사용할 필요가 없으며 경우에 따라서 utf-8이나 korea를 상황에 맞게 설정해 주면 된다.

[bash 셸에서 vi 키 사용하기]  
로케일(locale)은 리눅스 시스템의 언어 설정을 가리킨다. 요즘은 윈도우도 윈도우 2000, XP부터 하나의 윈도우에서 여러 나라의 언어를 모두 지원하는 추세가 확립되고 있지만 아직까지도 윈도우는 한글 윈도우와 영문 윈도우가 구분되어 출시되고 있다. 이에 반해 비영어권 사용자의 숫자가 초기부터 많았던 리눅스나 유닉스 계열 운영체제에서는 처음부터 일관된 방법으로 하나의 애플리케이션이 여러 나라의 언어를 쉽게 지원할 수 있는 프레임워크를 만드는 데 신경을 많이 써 왔다. 이러한 작업을 국제화(internationalization, i18n)라고 부른다. 참고로 국제화의 줄임말로 i18n을 쓰는 이유는 국제화의 i와 n사이에 18개의 글자가 있기 때문이라고 한다.

따라서 기본적으로 리눅스 시스템이나 배포판을 한글화한다는 얘기는 일단 리눅스 시스템의 언어 설정을 한국어로 해놓고 필요한 한글 메시지 번역이라든가 폰트 추가 및 설정과 같은 작업을 추가하는 것이 된다. 리눅스 시스템의 언어는 LANG 환경 변수를 통해 설정한다. 로케일 설정에는 LANG 변수 이외에도 여러 가지 다른 설정이 있지만 사용자 입장에서는 LANG 변수만 바꾸어 주면 전체 시스템의 언어가 바뀐다. 한국어의 경우 다음과 같은 LANG 셋팅을 많이 쓴다.

$ export LANG=ko_KR.eucKR

여기서 ko_KR.eucKR의 의미를 짚어보고 넘어가자. 맨 앞의 ko는 언어를 가리킨다. 한국어 이외의 언어를 몇 가지 예를 들어보면 en은 영어, de는 독일어, jp는 일본어를 의미한다. 그 다음 KR은 지역을 나타낸다. 비슷하게 KR은 한국, JP는 일본, US는 미국, CA는 캐나다, DE는 독일과 같은 예를 들어볼 수 있다.

그렇다면 LANG 환경변수 설정에서 왜 ko_KR과 같이 나라와 지역을 같이 쓸까? 그 이유는 같은 나라에서도 여러 가지 언어가 쓰일 수 있으며, 같은 언어라도 여러 나라에서 쓰이는 경우가 있기 때문이다. 일례로 미국에서의 영어는 en_US, 캐나다에서의 영어는 en_CA로 표기한다. 캐나다의 경우 fr_CA의 불어도 쓸 수 있을 것이다. 따라서 우리나라의 경우 ko_KR은 대한민국에서 쓰는 한국어가 된다. 북한은 이 원칙을 따른다면 아마도 ko_KP로 쓸 수 있을 것이다.

그리고 맨 마지막으로 eucKR은 인코딩 방법을 의미한다. eucKR은 현재 우리가 쓰고 있는 완성형 코드이다. 최근 들어 리눅스 배포판은 유니코드로 넘어가고 있는 추세인데 유니코드를 지원하는 리눅스 시스템에서는 내부적으로 이제 한글을 쓸 때에도 eucKR을 쓰지 않고 유니코드를 사용하고 있다. 유니코드로 한국어를 쓰려면 다음과 같이 로케일 설정이 바뀔 것이다.

$ export LANG=ko_KR.utf-8

참고로, 이런 로케일 설정은 요즘은 gdm이나 kdm과 같은 그래픽 화면 로그인 스크린에서 설정하는 것이 가장 편리하다. GNOME이나 KDE 로그인 스크린에서 언어를 선택하는 옵션이 그것이다.

마지막으로 지역화(localization, l10n)에 대해서도 잠깐 알아두자. 지역화의 줄임말인 l10n도 i18n과 같은 명명법을 따른 표기이다. 지역화는 국제화와는 달리 어떠한 프로그램이나 운영체제를 아예 특정 언어 전용으로 만드는 것을 의미한다. 리눅스 환경에서는 한텀과 같은 애플리케이션이 대표적인 예가 될 것이며, 윈도우의 경우는 한글 윈도우 98이 좋은 예가 된다. 지역화는 언어마다 각기 다른 애플리케이션이 만들어져야 하기 때문에 국제화보다 범용성이 떨어지는 편이며, 장기적인 개발비용을 줄이는 관점에서 국제화가 많이 권장되고 있다.

syntax highlighting
vim에서는 vi와는 달리 프로그램 소스 파일을 편집할 때는 여러 가지 색깔을 사용해서 소스코드 가독성을 높여주는 syntax highlighting 기능을 지원한다. syntax hightlighting 기능을 켜는 명령은 다음과 같다. 이 명령 역시 vimrc에 추가해 놓으면 편리하다. 

:syntax on

참고로 언어에 따른 syntax hightlighting의 구체적인 설정은 /usr/share/vim 아래쪽의 설정 파일에서 다룬다. vim의 syntax highlighting 기능의 특징 중 하나는 화면 배경색의 밝기에 따라 syntax highlighting용 색상 구성을 다르게 쓸 수 있다는 점이다. 자신이 쓰고 있는 터미널의 배경색에 따라 다음 두 가지 중의 하나를 선택해 보자.

:set background=light 또는 :set background=dark

필자의 경우 흰색 배경의 한텀을 주로 쓰는 까닭에 light 옵션을 주로 쓰고 있다. 

들여쓰기 설정
소스코드에서 탭 크기를 얼마로 잡을 것인지의 문제는 종종 게시판에서 플레임까지도 잘 번지는 주제이다. 소스코드의 탭 크기를 공백 8개로 할 것인가 아니면 탭 문자 하나로 둘 것인가, 만약 공백으로 처리한다면 4개로 할 것인가 8개로 할 것인가부터 시작해서 실로 다양한 스타일이 존재한다. 어느 경우든, 사용자의 입장에서는 탭 키를 한번 눌렀을 때 사용자가 원하는 쪽으로 들여쓰기가 알아서 된다면 무척 편리할 것이다.

우선, 자동으로 들여쓰기를 할 것인지 아닐 것인지를 설정해보자. 프로그램 소스코드를 짤 때는 블럭이 바뀌면 줄이 바뀔 때 마다 탭 키를 눌러 들여쓰기를 해야 하기 때문에 이 옵션을 켜 놓고 코딩을 하는 경우가 많다. 이 기능은 다른 vi에서도 작동한다.

:set autoindent 또는 :set ai

이 옵션을 해제하려면 다음과 같은 명령을 사용한다. 

:set noautoindent 또는 :set noai

파일을 편집 중에 탭 키를 눌었을 때, 이것을 탭 문자 하나로 인식할 것인지, 혹은 디폴트 8개의 공백 문자로 인식할 것인지 설정하기 위해서는 expandtab 옵션을 설정한다. 

:set expandtab 또는 :set et

이 때, vim에서 탭 문자 하나를 강제로 입력하려면 , tab의 조합을 누르면 된다. 는 컨트롤 문자를 vi에서 입력할 때 쓰는 명령키이다. expandtab 옵션을 해제하려면 앞의 경우와 마찬가지로 set noexpandtab 명령을 쓰면 될 것이다.

여기서(필자의 의견으로는) expandtab 옵션은 항상 켜놓고 탭 문자 하나를 여러 개의 공백 문자로 치환해서 사용하는 것이 좋다고 생각한다. 이렇게 작성한 소스코드는 어느 에디터에서 열어보더라도 똑같은 모습으로 들여쓰기가 잘 되어 있는 상태가 유지될 것이기 때문이다. 다만, 탭 문자의 들여쓰기를 공백 8개, 4개, 혹은 2개로 할 것인지에 대해서는 사람마다 취향이 다르고 프로젝트마다 스타일이 다를 수 있으며 그때 그때 상황에 맞추는 것이 좋다고 생각한다. tabstop 옵션은 vim에서 탭 문자 하나가 몇 개의 공백으로 보일 것인지를 설정할 때 사용한다.

:set tabstop=4     디폴트 값은 8이다. 약자로 set ts=4도 사용한다.

앞의 expandtab 옵션과 연계해서 생각해 보면, tabstop이 4로 설정되어 있고 noexpandtab 옵션이 설정되어 있다면 vim에서 탭 키는 탭 키를 누를 때마다 공백 4개의 폭으로 커서가 이동하고 내부적으로 탭 키는 누를 때마다 탭 문자 하나씩으로 인식하게 된다. 만약, 이렇게 편집한 파일을 다른 tabstop이 디폴트 8로 설정되어 있는 다른 vi나 기타 에디터에서 열어본다면 에디터가 탭 문자 하나를 8개의 공백 크기로 인식하는 바람에 소스코드가 완전히 다른 모습으로 보인다는 점을 고려해야 한다.

탭과 관련된 옵션으로 shiftwidth 옵션도 살펴볼 필요가 있다. shiftwidth 옵션은 들여쓰기를 할 때마다(탭 키를 누를 때마다가 아니라는 점에 유의하자) 몇 개의 공백 크기를 쓸 것인가를 설정해 준다. cindent 옵션과 같이 많이 쓰인다. tabstop과 gpt갈릴 수 있으므로 항상 tabstop 값과 같이 놓고 쓰면 편리하다.

:set shiftwidth=4 디폴트 값은 8이다. 약자로는 set sw=4를 쓴다.

C 소스코드를 작성할 때는 C 문법 스타일을 맞추어 주는 cindent 옵션을 켜놓으면 편리하다. 

:set cindent

탭에 관해서 얘기가 조금 복잡해진 감도 있지만 간단히 정리를 해 보면 이렇게 생각해 볼 수 있다. 우선, 탭 문자를 여러 개의 공백 문자로 치환할 것인지 혹은 탭 문자를 있는 그대로 하나로 쓸 것인지 결정하는 것이 중요하다. 그리고 일단 여러 개의 공백 문자를 쓰기로 결정했다면 tabstop 값과 shiftwidth 값을 일치시켜서 혼동을 없애는 편이 좋으며, 반대로 탭 문자를 그대로 사용하겠다면 다른 에디터와의 호환 여부를 고려할 필요가 있다. 필자가 쓰는 탭 관련 설정은 다음과 같다.

모든 탭을 공백 4개로 설정한다.
set tabstop=4
set shiftwidth=4
set expandtab

set autoindent

다중 윈도우 사용
원래 vi는 다중 윈도우를 연다든가, 여러 개의 파일을 동시에 편집하는 기능이 들어 있지 않았다. emacs가 에디터 내에서 에디터 이상의 기능을 수행할 수 있는 통합 환경이라면 vi는 심플한 유닉스의 초기 설계 철학을 닮은 에디터라고 볼 수 있다. 하지만 vim의 추가 기능 중 다중 윈도우나 복수 개의 파일 편집 기능은 상당히 편리하며 널리 쓰이고 있다.

다중 윈도우 기능은 요즘 아래아한글이나 MS 워드에서도 손쉽게 볼 수 있다. 마우스로 오른쪽 스크롤바 위쪽의 핸들을 아래로 주욱 잡아당기면 파일 하나에 화면이 2개 생기는 것을 볼 수 있다. vim에서는 화면을 나누기 위해서는 split 명령을 사용한다. vsplit 명령을 쓰면 화면을 세로로 나눌 수도 있다.

:split 줄여서 sp로 쓴다

이 상태에서 , w 키를 차례로 누르면 커서가 다른 화면으로 이동하게 된다. 다음에 j 키를 누르면 아래 화면으로 이동하게 되고, 다음에 k 키를 누르면 윗 화면으로 이동하게 된다(h, j, k, l 모두 사용 가능하다). 여기서 화면 하나를 닫기 위해서는, c 명령키나 close 명령을 사용한다. 

:close 줄이면 cl로 쓴다

여러 개의 파일을 동시에 편집
이제 화면을 둘로 나누면서 새로운 파일을 편집해 보자.

:new

new 명령을 내리면 같은 파일을 두 개의 윈도우에서 같이 편집하던 아까의 split 명령과는 달리, new 명령으로 열린 윈도우는 완전히 새로운 파일을 편집하게 됨을 볼 수 있다. :new 과 같이 파일명을 명시해 줄 수도 있다. 이제 vim에서 화면을 윈도우로 나누지 말고 복수 개의 파일을 편집하는 방법을 알아보자. vim에서는 여러 개의 파일을 편집하기 위해 다음과 같이 vim을 실행하는 것이 가능하다.

$ vi hello.c hello.h main.c

파일을 전환하기 위해서는 다음 명령을 사용한다. 다음과 같이 간단히 정리해 보았다. 

이동 : next (:n) - previous (:prev) 
저장 후 이동 : wnext (:wn) - wprevious (:wp) 

현재 커서가 위치하고 있는 파일 정보와 다른 파일들의 리스트를 보기 위해서는 :args 명령을 사용한다. 현재 편집 중인 모든 파일을 저장하는 :wall 명령어도 자주 사용된다. 

대부분 리눅스 배포판에서 기본 셸로 쓰고 있는 bash는 기본적으로 emacs 스타일의 편집키를 지원한다. 하지만 bash에서는 vi 편집 모드도 지원한다.

아마도 대부분의 리눅스 사용자들은 셸을 쓰면서 조금 전에 입력한 명령어로 다시 돌아가기 위해 직관적으로 위쪽 화살표 키를 누르는 습관이 있을 것이다. 이렇게 셸에서 명령어 history 기능을 이용하는데 오른쪽으로 손을 뻗어야 하는 불편이 있는 위쪽 화살표 키 말고 다른 키를 쓸 수도 있다. 이것이 bash에서는 emacs 스타일의 편집키를 쓸 수도 있고 vi 스타일의 편집키를 쓸 수도 있도록 되어 있다. bash 역시 GNU 프로젝트의 영향이 큰 까닭에 아무래도 디폴트는 emacs 스타일로 맞추어져 있다. emacs에서 위쪽 화살표에 해당하는 키는 이다. 개인적인 편차가 있겠지만 필자의 경우는 위쪽 화살표 키에 손을 뻗기보다는 그냥 <ctrl+p>를 누르는 편이 훨씬 편리하고 손목에 부담을 적게 받는다. 이제 vi 모드로 bash를 써 보자. vi 모드를 켜는 명령은 다음과 같다. .bash_profile에 넣어두고 써도 된다.

$ set -o vi

혹은 /etc/inputrc나 .inputrc에 다음 설정을 사용해도 된다. 

set editing-mode vi 
set keymap vi

이후로는 셸 프롬프트에서 esc 키를 누르면 vi의 명령어 모드로 진입한 것처럼 vi 편집키가 작동한다. 이제 위쪽 화살표 대신에 k 키를 쓸 수 있게 된 것이다. 추가로 자주 쓰는 cw와 같은 명령어를 사용해 보자.

굳이 vi 모드로 bash를 쓰지 않더라도, bash의 기본 설정인 emacs 스타일의 편집키는 항상 사용하는 습관을 들여 보자. emacs에서 커서 상하좌우 이동키는 각각 <ctrl+p>, <ctrl+n>, <ctrl+f>, <ctrl+b>이다. 여기에 Home에 해당하는 <ctrl+a>와 end의 <ctrl+e>까지라도 알아두자. emacs 편집키 역시 타이핑을 할 때 손목의 피로도를 줄일 수 있을 뿐만 아니라 다른 프로그램, 특히 GNU 계열의 응용 프로그램에서 디폴트로 많이 쓰인다.

참고로 이렇게 하나의 유틸리티에서 쓰는 명령어를 다른 곳에서도 그대로 지원해서 좀 더 사용자가 새로운 프로그램을 쉽게 쓸 수 있게 해주는 방식이 유닉스에서는 특히 많이 보이는 편이다. 간단한 예로, 페이지 단위로 텍스트 파일을 출력시켜 주는 more 프로그램에서는 스페이스바를 누르면 다음 페이지로 넘어가게 되는데 유닉스 환경에서는 스페이스바를 누르면 다음 페이지로 넘어가는 프로그램들이 한둘이 아니다. 심지어는 윈도우의 대표 브라우저인 인터넷 익스플로러도 스페이스바를 누르면 다음 페이지로 넘어간다. 인터넷 익스플로러가 오래된 유닉스의 모자이크(Mosaic) 브라우저의 소스코드를 기반으로 하고 있기 때문이다. 모질라 브라우저 역시 스페이스바를 누르면 다음 페이지로 넘어간다.

이런 이유로 유닉스를 쓰면서 vi와 emacs의 편집키는 적어도 둘 중 하나는 외워두는 것이 상식이다. 이들 편집키는 vi나 emacs를 벗어난 환경에서도 많이 쓰이며, 실제 사용해보면 무척 편리하다.

자신에게 맞는 편집기를 선택하자
이번 호에서는 리눅스의 대표적인 편집기인 vim에 대해서 알아보았다. 실제, 여러 설문조사 결과를 보면 리눅스 환경에서는 emacs 사용자와 vi 사용자가 대략 50대 50의 비율을 나타내고 있다. emacs를 쓸 것인지 vi를 쓸 것인지는 사용자의 선택이다. emacs는 편집기 이상의 기능을 발휘하는 통합 환경인 반면 vi는 작으면서도 기능이 강력한 편집기로서의 특성을 보인다. 필자 개인적으로는 vi를 주로 사용하고 emacs는 잘 쓰지 않는 까닭에 이번 기사에서는 emacs를 다루지 않았지만, vi 대신에 emacs를 사용하는 것도 좋은 선택임을 강조해 보고 싶다.

다음 호에서는 오픈소스 개발에서 많이 쓰이는 diff, patch부터 시작해서 cvs의 사용법을 리뷰하고 최근의 오픈소스 환경에서 중요하게 부상하고 있는 경향을 몇 가지 추가해 보기로 하겠다. @

④ 필수 소스버전 관리툴


리눅스 개발자들에게 중요한 것 중의 하나가 오픈소스 프로젝트 진행이다. 사실 국내에서도 오픈소스 프로젝트는 많은 사람들이 관심을 갖고 있지만 막상 프로젝트에 참여하는 사람들의 숫자는 여전히 부족하다. 여기서는 여러 개발자들이 동시에 오픈소스 프로젝트에 참가할 때 거의 필수적으로 쓰이는 diff, patch, CVS(Concurrent Versions System)와 같은 소스코드 버전 관리툴에 대해 살펴 보고 오픈소스 프로젝트를 진행할 때 알아두면 좋은 특성이나 작업 방식에 대해서도 알아 본다.

오픈소스 프로젝트란 도대체 어떤 것일까? 리눅스와 오픈소스의 부상과 더불어 오픈소스 개발 방식은 기존의 상용 소프트웨어 개발 방식에 비해 적은 비용으로 양질의 소프트웨어를 개발할 수 있으며 소프트웨어 시장 독점의 문제가 없다는 의견이 설득력을 계속 높여가고 있다.

그러나 필자가 보기에는 오픈소스 개발 방식은 그 효율성을 논의하기 전에 왜 이런 식의 개발 방식이 만들어 졌으며 왜 오픈소스 스타일의 개발 방식이 자연스럽게 정착되었는지를 이해하는 것이 더욱 중요하다고 생각한다. 필자의 의견으로는 오픈소스는 소프트웨어를 소프트웨어 그 자체가 지닌 특성에 맞도록 자연스럽게 개발하는 한 방법이라고 생각한다.

당연한 얘기지만 어떠한 소프트웨어의 소스코드를 공개하면 그 소프트웨어가 계속 바뀌어 나갈 수 있는 길이 열리게 된다. 소스코드를 공개해서 개발 작업을 진행하는 오픈소스 개발 방법이 두드러지게 나타난 것은 1970년대 초 AT&T에서 자사의 운영체제인 유닉스의 소스코드를 공개한 이후부터라고 보는 것이 정설이다. AT&T에서는 대학과 같은 교육, 연구 기관에 자사의 제품인 유닉스를 공급하면서 돈을 받고 소스코드를 그대로 제공하는 라이선스 방식을 취했다.

소프트웨어를 배포할 때 소스가 아닌 바이너리를 주로 배포하는 지금으로서는 언뜻 상상하기 어려운 관행일 수도 있지만 컴퓨터의 종류가 통일되어 있지 않고, 소수의 전문가 집단에서 유닉스를 주로 사용했다는 점을 생각해 본다면 AT&T 입장에서 유지보수 비용을 줄여주는 이러한 소스코드 형태의 배포는 나름대로 합리적인 선택이라고도 할 수 있겠다.

그러나 여기서 예상하지 못한 일이 벌어진 것이 이들 사용자, 혹은 사용자이면서 개발자이기도 한 사람들이 마음대로 뜯어고치고 덧붙이기 시작한 코드들이 오리지널 AT&T 유닉스보다 오히려 더 중요한 위치를 차지하게 된 것이다(BSD 유닉스의 발전도 이러한 관습에 뿌리를 두고 있다).

따라서 자유 소프트웨어나 오픈소스와 같은 용어들은 소프트웨어 개발 방식의 측면에서 볼 때 이미 존재하고 있던 개발 방식을 새롭게 재조명하고 있다고 생각할 수 있다. 자유 소프트웨어에서는 소스코드가 공개된 소프트웨어의 보호에 좀 더 중점을 두고 있으며 오픈소스에서는 소스코드가 공개된 채로 개발되는 소프트웨어의 개발 효율성에 좀 더 관심을 집중하는 편이다. 어느 경우이든 간에, 소스코드를 공개해서 개발자의 참여를 이끌어 내는 소프트웨어 개발 방식은 변함이 없으며, 오픈소스라는 단어가 아예 없던 시절에도 이것은 마찬가지인 것이다.

그렇다면, 이제 간단한 상황 하나를 가정해 보기로 하자. 여러분들이 초기 유닉스 시절 대학 전산실에 근무하던 도중 유닉스가 도입되었다고 생각해보자. 고된 포팅과 설정 작업 끝에 시스템이 제대로 돌아가기 시작했는데 이 와중에서 버그를 하나 발견하고 그 부분의 소스코드를 수정했다. 이럴 때 다음 버전의 유닉스에 여러분들이 고친 부분이 반영되도록 하려면 어떻게 해야 할까?

이럴 때 가장 상식적인 해법은 고친 부분의 소스코드를 원저자에게 보내주면 될 것이다. 그리고 그 방법으로 가장 편리한 것은 아마도 이메일이 좋을 것이다. 인터넷이 없던 시절이라면 아마도 일반 메일을 이용했을 것이다. 약간은 논외의 이야기지만, 인터넷 초창기에는 국내에서 유즈넷 뉴스그룹에 올라온 글을 보기 위해 정기적으로 뉴스서버 데이터 백업을 외국에서 자기 테이프에 받아 소포로 전송받기도 했다고 한다. 어쨌든 이러한 이메일의 간편함 덕분에 이메일은 오픈소스 개발 작업에서 가장 중요한 통신 수단이며 패치 전송 수단으로 자리 잡게 된다.

여기서 하나 생각해 봐야 할 것이 원저자의 입장이다. 이렇게 패치를 담고 있는 메일의 숫자가 적을 때는 원저자는 그저 전송된 패치를 고맙게 받아 적용하기만 하면 되겠지만 패치의 숫자가 늘어나고, 같은 버그에 대해서도 두 종류 이상의 중복 패치가 생기게 되면 어떤 패치를 선택할 것인지, 그리고 모은 패치를 어떻게 통합해서 하나의 소스코드 트리로 만들고 그것을 배포(public release)할 것인지 선택해야 하는 문제가 생긴다. 이런 경우, 보통 오픈소스계의 관습은 원저자, 혹은 프로젝트 리더에게 어떤 패치를 받아들일 것인지의 결정권을 맡겨버리는 경향이 있다. 이럴 때 원저자나 프로젝트 리더는 ‘자비로운 독재자(benevolent dictator)’라는 역할을 맡게 되는 것이다.

또한, 패치가 전송될 때 사람들마다 통일되지 않은 방식으로 패치를 전송하게 되면 프로젝트 리더의 입장에서는 여러 종류의 패치를 하나의 소스코드 트리에 적용시키는 데 많은 혼란을 겪게 될 것이다. 이를 해결하기 위해 등장한 심플한 도구가 바로 diff와 patch이다.

diff와 patch
diff는 유닉스 사용자 튜토리얼에도 가끔씩 등장하는 간단한 유틸리티이다. diff의 역할은 두 파일간의 차이점을 보여주는 데 소스코드의 바뀐 부분을 보여 줄 때 많이 쓰인다. patch는 이러한 diff의 출력 결과를 이용해서 이 바뀐 부분을 원래의 소스코드에 업데이트할 때 쓰는 유틸리티이다.

diff의 일반용법
우선, diff의 형식은 다음과 같다.

diff [options] from-file to-file

diff는 두 개의 파일을 필요로 한다는데 주의하자. from-file은 원래의 파일, 즉 구 버전의 파일이며, to-file은 새로이 바뀐 새 버전의 파일이다. diff는 이렇게 하면 from-file에서 to-file로 어떠한 변화가 있었는지를 출력해 준다. from-file과 to-file은 모두 디렉토리가 올 수도 있는데 디렉토리가 오는 경우는 조금 뒤에 살펴보기로 하자. 참고로 간단한 예제 hello1.c와 hello2.c의 예를 들어보자. 다음에서 볼 수 있듯이 hello2.c는 hello1.c에서 hello, world 부분이 hello, the world of linux로 대치되었고 그 아랫줄에 공백 라인 하나와 printf("Testing one two three.\n");가 추가되었음을 볼 수 있다. 

*** hello1.c:
#include 
#include 
main()
{
    printf("hello, world.\n");
}

*** hello2.c:
#include 
main()
{
    printf("hello, the world of Linux.\n");
    printf("Testing one two three.\n");
}

diff 결과는 다음과 같다. 

$ diff hello1.c hello2.c 
2d1
< #include 
6c5,7
<     printf("hello, world.\n");
---
>     printf("hello, the world of Linux.\n");

>     printf("Testing one two three.\n");

첫 줄의 2d1은 hello1의 두 번째 줄에서 한 줄을 삭제(delete)하는 변화가 일어났다는 의미이다. 그리고 조금 아래의 6c5,7은 hello1의 6번째 줄을 아랫부분으로 바꾸는데(change) 그 결과가 5번째부터 7번째 라인까지 들어가게 된다는 의미이다. 그러나 실제로 프로그램 소스코드에서는 오리지널 diff의 결과물보다는 unified format의 diff 출력을 쓰는 경우가 많다. unified format을 쓰려면 diff에 -u 옵션을 추가한다.

$ diff -u hello1.c hello2.c 
--- hello1.c    Tue Aug  3 14:34:46 2004
+++ hello2.c    Tue Aug  3 13:25:49 2004
@@ -1,7 +1,8 @@
#include 
-#include 

main()
{
-    printf("hello, world.\n");
+    printf("hello, the world of Linux.\n");
+
+    printf("Testing one two three.\n");
}

참고로 unified format에서는 변경되는 부분만이 아닌 변경되는 부분 근처의 내용(context)도 같이 출력됨을 볼 수 있다. 사람이 좀 더 읽기 편리한 context format 출력 옵션인 -c를 사용한 결과는 다음과 같다. context format 역시 바뀌는 부분 근처의 내용도 참고하기 좋게 출력을 해 준다. 어쨌거나 오픈소스 프로젝트에서는 diff를 쓸 때 주로 -u 옵션을 붙인다는 점을 꼭 외워 두도록 하자.

$ diff -c hello1.c hello2.c 
*** hello1.c    Tue Aug  3 14:34:46 2004
--- hello2.c    Tue Aug  3 13:25:49 2004
***************
*** 1,7 ****
  #include 
- #include 
  
  main()
  {
!     printf("hello, world.\n");
  }
--- 1,8 ----
  #include 
  
  main()
  {
!     printf("hello, the world of Linux.\n");

!     printf("Testing one two three.\n");
  }

이렇게 diff로 소스코드의 변경된 부분을 저장한 다음 이것을 원저자에게 메일로 보내면 된다. 

$ diff -u hello1.c hello2.c > hello.diff

여러 개의 소스 파일을 diff로 비교하기
앞의 경우는 소스코드 파일 하나만이 변경되었지만 상황에 따라서는 패치 과정에 여러 파일이 수정되고 새로운 파일이 추가되는 경우가 발생할 수도 있다. diff는 디렉토리 단위의 파일 비교도 가능하다. 우선, 다음 예제를 보자.

$ pwd
/home/foobar
$ ls -F
src1/  src2/            # src1은 원본, src2는 새로운 기능 추가본
$ ls src1
hello1.c  hello2.c
$ ls src2
hello1.c  hello2.c  hello3.c
$ more src1/*c
::::::::::::::
src1/hello1.c
::::::::::::::
#include 
#include 

main()
{
    printf("hello, world.\n");
}
::::::::::::::
src1/hello2.c
::::::::::::::
#include 

main()
{
    printf("hello, the world of Linux.\n");

    printf("Testing one two three.\n");
}
$ more src2/*c
::::::::::::::
src2/hello1.c
::::::::::::::
#include 

main()
{
    printf("hello, world.\n");
}
::::::::::::::
src2/hello2.c
::::::::::::::
#include 

main()
{
    printf("hello, the world of Linux.\n");

    printf("Testing one two three four.\n");
}
::::::::::::::
src2/hello3.c
::::::::::::::
#include 
#include 

main()
{
    /* needs to be filled in */
}

src2에서는 hello3.c 파일이 새로 추가되었으며, hello2.c에서 수정 부분이 있고, hello1.c에서 빠진 부분이 있다. 이 두 디렉토리 사이에서 diff를 실행하려면 다음과 같은 명령을 쓴다.

$ pwd
/home/foobar
$ ls -F
src1/ src2/             # 경로를 제대로 확인한 뒤 diff를 실행한다
$ diff -urN src1 src2
diff -urN src1/hello1.c src2/hello1.c
--- src1/hello1.c       Tue Aug  3 14:34:46 2004
+++ src2/hello1.c       Tue Aug  3 13:35:44 2004
@@ -1,5 +1,4 @@
#include 
-#include 

main()
{
diff -urN src1/hello2.c src2/hello2.c
--- src1/hello2.c       Tue Aug  3 13:25:49 2004
+++ src2/hello2.c       Tue Aug  3 13:35:57 2004
@@ -4,5 +4,5 @@
{
     printf("hello, the world of Linux.\n");

-    printf("Testing one two three.\n");
+    printf("Testing one two three four.\n");
}
diff -urN src1/hello3.c src2/hello3.c
--- src1/hello3.c       Thu Jan  1 09:00:00 1970
+++ src2/hello3.c       Tue Aug  3 13:37:02 2004
@@ -0,0 +1,7 @@
+#include 
+#include 
+
+main()
+{
+    /* needs to be filled in */
+}

diff 명령에서 -r 옵션은 recursive 옵션으로 서브 디렉토리까지 diff가 모두 탐색하라는 의미이고, -N 옵션은 hello3.c와 같이 새로 만들어진 파일까지도 포함해 diff 출력을 생성하라는 의미다. 이 옵션 역시 -urN으로 외워 두는 것이 좋다.

patch 사용하기
이렇게 만들어진 diff의 결과물은 patch 명령을 통해서 원저자의 소스코드로 업데이트된다. patch 명령은 -p 옵션만 정확히 이해하면 사용하는데 무리가 없다.
-p 옵션은 strip 옵션이라고 부르는데 diff 파일에 명시되어 있는 디렉토리에서 몇 단계를 벗겨(strip)낼 것인가를 결정한다. -p0 옵션은 디렉토리 단계를 하나도 벗겨내지 않겠다는 것이고, -p1 옵션은 한 단계를 벗겨낸다는 의미이고 -p2는 두 단계를 의미한다. 쉽게 이해하기 위해 <표 1>를 보자. foobar/include/net 디렉토리가 있다고 할 때 p 옵션을 적용하면 다음과 같이 디렉토리가 벗겨져 나간다.

p0 foobar/include/net 
p1 include/net 
p2 net 

이것이 diff로 생성시킨 패치를 적용할 때 어떤 의미를 가지게 될까? 우선, 파일 두 개를 비교했을 때 생성된 diff 패치와 디렉토리 두 개를 비교했을 때 생성된 diff 패치의 헤더 부분을 비교해보자.

◆ 파일 두 개를 비교했을 경우:
$ diff -u hello1.c hello2.c
--- hello1.c    Tue Aug  3 14:34:46 2004
+++ hello2.c    Tue Aug  3 13:25:49 2004
(이하 생략)

◆ 디렉토리 두 개를 비교했을 경우:
$ diff -urN src1 src2
diff -urN src1/hello1.c src2/hello1.c
--- src1/hello1.c       Tue Aug  3 14:34:46 2004
+++ src2/hello1.c       Tue Aug  3 13:35:44 2004
(이하 생략)

즉, diff가 생성한 패치 파일에는 원본 파일과 바뀐 파일의 디렉토리가 명시되어 있음을 알 수 있다. patch 명령을 사용할 때는 이 경로명을 고려해서 patch 명령을 실행시켜 줘야 한다.

패치 적용하기
여러분이 원저자의 입장에서 제공받은 패치를 적용시키려면 다음과 같은 방법을 사용하면 된다. 패치 파일은 표준 입력(standard input)으로 들어가며, 항상 -p 옵션을 주의 깊게 사용해야 한다. 이번 예제에서는 디렉토리 두 개를 비교한 패치가 전송되었다고 가정해 보자. 원저자는 diff 패치의 헤더 부분을 읽고 패치의 경로명을 확인한 다음 적절한 디렉토리로 가서 patch 명령을 실행한다. 우선, -p0 옵션 사용 예부터 보자. diff 패치의 헤더 부분은 다음과 같다. 

diff -urN src1/hello1.c src2/hello1.c
--- src1/hello1.c       Tue Aug  3 14:34:46 2004
+++ src2/hello1.c       Tue Aug  3 13:35:44 2004
... 이하 생략 ...

따라서 원저자는 이 패치를 hello.diff로 저장한 다음 자신의 소스코드가 있는 src1까지 가서 패치를 적용한다.

$ cd projects
$ pwd
/home/foobar/projects
$ ls -F
hello.diff   src1/

diff 파일에 기술된 경로명과 현재 경로명이 일치하고 있음을 주의깊게 보자. 

$ patch -p0 < hello.diff
$ patch -p0 < hello.diff
patching file src1/hello1.c
patching file src1/hello2.c
patching file src1/hello3.c

패치 전과 패치 후의 결과를 비교해 보면 다음과 같다.

패치 전:

$ pwd
/home/foobar/projects/src1
$ ls -al
total 16
drwxr-xr-x    2 jwsohn   jwsohn       4096  8월  3 16:35 .
drwxr-xr-x    3 jwsohn   jwsohn       4096  8월  3 16:35 ..
-rw-r--r--    1 jwsohn   jwsohn         82  8월  3 14:34 hello1.c
-rw-r--r--    1 jwsohn   jwsohn        116  8월  3 13:25 hello2.c

패치 후:

$ pwd
/home/foobar/projects/src1
$ ls -al
total 20
drwxr-xr-x    2 jwsohn   jwsohn       4096  8월  3 16:41 .
drwxr-xr-x    3 jwsohn   jwsohn       4096  8월  3 16:35 ..
-rw-r--r--    1 jwsohn   jwsohn         62  8월  3 16:41 hello1.c
-rw-r--r--    1 jwsohn   jwsohn        121  8월  3 16:41 hello2.c
-rw-r--r--    1 jwsohn   jwsohn         83  8월  3 16:41 hello3.c

이제 -p1 옵션을 적용해서 patch 명령을 써 보자. diff 파일에 기술된 경로명에서 디렉토리를 한 단계 벗겨내면 src1 디렉토리가 없어지므로 다음과 같은 방식으로 패치 파일이 적용된다. 

$ pwd
/home/foobar/projects
$ ls -F
hello.diff  src1/
$ cd src1
$ patch -p1 < ../hello.diff 
patching file hello1.c
patching file hello2.c
patching file hello3.c

참고로 diff 파일의 경로는 어디에 위치하든 상관이 없다. 파일 하나에 대한 패치를 적용할 때는 -p0 옵션을 쓰면 될 것이다.

CVS 사용
CVS는 약자 중 Concurrent가 의미하듯이 한번에 여러 명의 개발자가 동일한 소스코드 트리에 동시에 수정을 가하면서 작업을 할 수 있도록 도와주는 도구이다. 사실 오픈소스 프로젝트에 참가하는 일반 개발자의 입장에서는 CVS에 대한 일반적인 지식은 별다른 필요가 없다고 생각해도 무방하다. 실제 소스코드 트리를 수정할 수 있는 권한을 갖고 있는 사람들은 프로젝트 리더인 자비로운 독재자 한 사람이나 프로젝트와 관련이 깊은 소수의 개발자들일 것이기 때문이다.

즉, 소스코드 수정 사항이 생기면 CVS를 굳이 쓸 필요가 없이 그냥 FTP나 http로 다운받은 소스코드 위에 diff를 돌려서 패치만 이메일과 같은 수단으로 보내 주면 소스코드 수정 권한이 있는 그쪽 사람들이(보통 ‘커미터’라고 부른다) 알아서 처리를 해 줄 것이기 때문이다.
하지만 가장 최신 버전의 nightly build된 소프트웨어는 CVS 서버에 접속해야만 구할 수 있는 경우가 대부분이다. 따라서 오픈소스 개발 프로젝트에 관심이 있는 개발자라면 다음 측면에서 CVS 사용법을 알아야 할 필요가 있을 것이다.

[1] FTP나 http 서버에서 소스코드를 다운받는 대신에 
[2] CVS 서버에서 가장 최신 소스코드를 다운 받고 
[3] 자신이 패치한 소스코드와 CVS 서버의 소스코드의 diff를 생성하는 방법 

익명 CVS 체크아웃
이제 CVS에서 소스코드를 다운받는 방법을 알아보기로 하자. 일반적으로 오픈소스 프로젝트들은 대부분 CVS 서버를 읽기 전용으로 세팅해 놓고 누구든지 들어와서 소스코드를 다운받아 갈 수 있도록 해 놓고 있다. 익명 FTP(anonymous FTP)와 비슷한 개념으로 생각하면 되겠다. CVS 서버에 사용자가 접속을 해서 소스코드를 한 카피 다운받아 가는 것을 CVS에서는 체크아웃(check-out)이라는 용어로 표현한다.
참고로 CVS는 로컬 서버에서 사용할 수도 있고, 따로 CVS 서버를 두고 원격으로 접속할 수도 있다. 여기서는 CVS가 원격 서버라고 가정하기로 한다. CVS 서버에서 소스코드를 다운받는 명령은 다음과 같다. KLDP.net 서버에 위치한 moniwiki 프로젝트를 예로 들어 보겠다.

$ cvs -d:pserver:anonymous@cvs.kldp.net:cvsroot/moniwiki login
$ cvs -d:pserver:anonymous@cvs.kldp.net:cvsroot/moniwiki checkout moniwiki

익명 FTP에 접속할 때 보다는 조금 복잡해 보인다. 우선, 첫줄의 cvs 명령은 CVS 서버에 anonmous, 즉 익명 사용자로 로그인하는 과정이다. CVS 서버가 암호를 요구하면 그냥 엔터 키를 쳐 주면 인증이 끝나고 읽기 권한이 부여된다. 일단 로그인을 한번 한 다음부터는 사용자의 홈 디렉토리에 .cvspass 파일이 생기면서 인증 절차가 생략된다. 즉, 매번 접속할 때마다 anonymous 인증 과정을 거쳐야 하는 익명 FTP와는 달리, CVS에서는 한번만 익명 로그인을 해서 .cvspass 파일을 생성하고 나면 다시 cvs login 명령으로 인증 과정을 거칠 필요가 없다.

그 다음 -d 옵션은 CVS 서버에서 제공하고 있는 루트 디렉토리를 의미한다. moniwiki 프로젝트의 경우는 cvsroot/moniwiki로 지정되어 있다. 앞의 pserver는 CVS 명령이 소스코드를 다운받으면서 쓸 프로토콜명이며 뒤의 anonymous@cvs.kldp.net은 CVS 로그인시 사용할 계정이다.

두 번째 cvs 명령은 소스코드 한 카피를 다운받는 체크아웃 과정을 실행하게 된다. 체크아웃(checkout)한 다음의 moniwki는 CVS 서버에서 지정해 놓은 프로젝트 이름이며(모듈이라고 부른다) 이곳 CVS 서버에서는 moniwki로 지정해 두었다. 두 번째 명령을 실행하면 사용자의 현재 디렉토리에 moniwiki라는 디렉토리가 생성되고 소스코드 다운로드가 시작된다. 여기에, -z3 옵션을 주면 전송시 압축을 사용하기 때문에 전송 속도가 빨라진다.

$ cvs -z3 -d:pserver:anonymous@cvs.kldp.net:cvsroot/moniwiki checkout moniwiki

그런데 CVS 서버에 접속할 때마다 -d 옵션 뒤의 긴 디렉토리 이름을 타이핑하기는 아무래도 불편한 감이 있다. -d 옵션을 생략하려면 환경변수 CVSROOT에 -d 옵션을 등록해 둔다.

$ export CVSROOT=:pserver:anonymous@cvs.kldp.net:/cvsroot/moniwiki

그 다음부터는 다음 명령으로도 충분하다.

$ cvs login
$ cvs checkout moniwiki

소스코드를 받은 지 시간이 어느 정도 지났다면 그동안 CVS 서버의 내용이 새롭게 업데이트되어 있을 수도 있다. 바뀐 부분을 다운받으려면 CVS에서 update 명령을 사용한다.

$ cvs update -dP

여기서 -P 옵션은 Prune 옵션으로 비어있는 디렉토리를 자동으로 삭제해 주는 역할을 한다. -d 옵션은 그동안 서버 쪽에 새로 만들어진 디렉토리가 있으면 다운 받은 이쪽에도 동일한 디렉토리를 만들어 준다.

CVS 서버 원본에서 diff로 패치 파일 만들기
이제 기본적인 CVS 서버에서 소스코드 다운로드 방법을 알았으니 직접 CVS 서버의 소스코드를 이용해서 패치 파일을 만들어 보자. CVS는 diff 명령을 아예 자체적으로 내장하고 있다. 조금 전 다운받은 moniwiki의 소스코드를 예로 들어보자. 여기서, 필자는 monisetup.php 파일에 간단히 /* testing one two three */라는 주석문을 하나 삽입했다.

$ cd moniwiki
$ ls 
COPYING                 doc                     secure.sh
CVS                     imgs                    theme
INSTALL                 index.html              tools
README                  lib                     wiki.php
THANKS                  locale                  wikihttpd.php
applets                 monisetup.bat           wikilib.php
config.php.default      monisetup.php           wikismiley.php
css                     monisetup.sh
data                    plugin
$ cvs diff -u -p monisetup.php
Index: monisetup.php
===================================================================
RCS file: /cvsroot/moniwiki/moniwiki/monisetup.php,v
retrieving revision 1.11
diff -u -p -r1.11 monisetup.php
--- monisetup.php       3 Jan 2004 14:26:50 -0000       1.11
+++ monisetup.php       3 Aug 2004 09:14:21 -0000
@@ -486,4 +486,5 @@ if (

댓글 없음:

댓글 쓰기