안녕하세요 이번 시간에는 Coreutils를 빌드해 보겠습니다.
빌드하기 앞서 Coretuils의 역할과 구성에 대해 한번 알아보고 가시겠습니다.
1. Coreutils의 역할
Coreutils는 매일 사용하는 ls, cp, mv, rm, cat, mkdir 같은 가장 기본적인 명령어 100여 개가 모여 있는 패키지입니다.
이게 없으면 리눅스는 아무런 작업도 할 수 없는 빈 껍데기에 불과하죠.
앞서 Bash를 빌드할때 External(외장) 명령어에 대하여 잠깐 다뤄 본적이 있습니다.
네, 바로 이 Coreutils가 외장명령어 ls, cp, mv 등을 모두 /usr/bin 폴더 안에 각각 별도의 실행 파일(Binary)로 존재합니다.
따라서, Bash에서 ls를 치면, Bash는 자신의 내장 명령어 목록에 ls가 없다는 것을 확인하고 $PATH경로를 뒤져서 /usr/bin/ls라는 외부 프로그램을 찾아 실행시키죠,
그렇다면 Coreutils의 100여 개 명령어, 무엇이 있을까요?
전체 목록은 100개가 넘지만, 성격에 따라 그룹을 묶어보면 이 친구들이 리눅스에서 어떤 일을 하는지 한눈에 보입니다. 주요 명령어들을 분류해 봤습니다.
- 파일 및 디렉토리 관리
가장 많이 쓰는 기능들입니다. 파일을 생성하고, 옮기고, 삭제합니다.
- ls : 목록 보기
- cp, mv, rm : 복사, 이동, 삭제
- mkdir, rmdir : 디렉토리 생성, 삭제
- ln : 링크 생성(심볼릭/하드 링크)
- chmod, chown, chgrp : 권한 및 소유자 변경
- touch : 빈 파일 생성 및 시간 수정
- 텍스트 처리
파일 내부의 데이터를 주무르는 도구들입니다.- cat, head, tail : 내용 출력(전체, 앞부분, 뒷부분)
- cut, paste, join : 텍스트 열 자르기 및 합치기
- sort, uniq : 정렬 및 중복 제거
- wc : 단어, 줄 ,바이트 수 세기
- tr : 문자 치환 (대문자-> 소문자 등)
- od : 바이너리 파일 내용 보기
- 시스템 및 환경 정보
현재 시스템이 어떤 상태인지 알려줍니다.- whoami : 현재 내가 누구인지 확인
- hostname : 호스트 이름 출력 (이번 설정에서 활성화할 것!)
- du, df : 디렉토리/디스크 사용량 확인
- env : 환경 변수 확인
- uname : 커널 및 하드웨어 정보 출력
- uptime : 시스템 가동 시간 확인
- 쉘 스크립트 도우미
스크립트를 짤 때 논리적인 판단을 돕습니다.- true, false : 항상 참/거짓 반환 (조건문에서 사용)
- test ( [ ) : 조건 검사
- echo : 텍스트 출력
- sleep : 일정 시간 대기
- yes : "y" 글자를 무한 반복 (자동 응답용)
좋습니다 여기까지 명령어 목록을 성격에 따라 묶어서 만들어 보았습니다.
생각보다 익숙한 친구들이 많이보입니다. 특히, 1,2번 항목의 명령어들이 많이 익숙한 친구들이죠,
그 외 에도 시스템 및 환경정보에 whoami, env는 어느정도 익숙한 명령어 들입니다.
자 그럼, Coreutils가 왜 LFS에서 중요한지 생각해보죠,
리눅스 커널(Kernel)만 달랑 있으면 아무것도 할 수 없습니다. 커널에게 파일 목록 좀 보여줘(ls)라고 시키려면 그 명령을 수행할 수 있는 최소한의 도구가 필요한데, 그게 바로 Coreutils입니다.
지금 제가 5.10에서 Coretuils를 빌드하는 이유는, 호스트(우분투)의 힘을 빌려 쓰는 것을 벗어나기 위해 직접 제가 만든 리눅스의 힘으로 명령어를 실행하기 위해서입니다.
여기서 또 재미난 점이 발생합니다. 바로 중복의 존재이죠,
사실 echo나 pwd, test 같은 명령어는 Bash 내장 명령어로도 존재하고, Coretuils 외장 명령어로도 존재합니다. 보통은 성능을 위해 Bash 내장 버전을 먼저 쓰지만, 외장 프로그램이 꼭 필요한 특수한 상황을 위해 Coreutils가 별도로 제공하는 것이죠,
그러한 상황이 어떤 상황이 있을지 한번 알아봅시다.
- find, xargs, sudo 같은 외부 도구와 협업할 때
이것이 가장 결정적인 이유입니다. 외부 도구들은 Bash 내부를 들여다볼 수 없습니다.- sudo의 경우 : sudo는 외부 바이너리를 실행해 주는 프로그램입니다. Bash 내장 명령어인 cd를 sudo cd /root라고 칠 수 없는 이유가 바로 이것입니다. cd라는 파일이 없기 때문이죠. 반면 sudo /usr/bin/echo "hello"는 가능합니다.
- find 명령의 -exec 옵션 : find . -namme "*.txt" -exec echo {} \;를 실행할 때, find는 파일 목록을 찾은 뒤 echo라는 실행 파일을 호출하려고 합니다. 이때 Bash 내장 echo는 파일이 아니기 때문에 호출할 수 없고, 반드시 /usr/bin/echo라는 외장 파일이 있어야 합니다.


- 다른 프로그램(C, Python, Java 등)에서 호출할 때
Bash 쉘 안이 아닌, 유저가 나중에 개발할 프로그램이나, 블로그 서버 코드(C 언어 등)에서 system("echo hello") 또는 execvp("echo", ...)를 호출한다고 가정해 봅시다.
이때 운영체제는 실행 가능한 파일을 찾습니다. 만약 /usr/bin/echo가 없다면, C프로그램은 echo를 실행하지 못하고 에러를 냅니다. 즉, Bash가 아닌 다른 환경에서도 이 기능들을 쓸 수 있게 보장하기 위해 외장 프로그램이 존재합니다. - 미묘한 기능(옵션)의 차이가 필요할 때
Bash 내장 명령어와 Coreutils 외장 명령어는 사실 완벽하게 똑같지 않습니다.- echo의 예 : GNU Coreutils의 echo는 --help나 --version 같은 옵션을 지원하지만, Bash 내장 echo는 이런 옵션을 문자로 취급해 그냥 화면에 출력해 버리는 경우가 많습니다.
- 표준 준수 : 어떤 스크립트가 특정 표준(POSIX 등)을 엄격하게 지켜야 할 때, 쉘마다 조금씩 다른 내장 명령어 대신 어떤 환경에서든 동일하게 작동하는 Coreutils 바이너리를 강제로 지정해서 쓰기도 합니다.(예 : env pwd 또는 /usr/bin/pwd라고 명시)
***POSIX란? : POSIX(포직스, Portable Operating System Interface)는 서로 다른 UNIX 기반 운영체제(리눅스, macOS 등)에서 응용 프로그램이 이식성 있게 동작하도록 IEEE가 정의한 공통 API 표준 규격입니다. 파일 시스템, 프로세스 제어, 스레드(Pthreads) 등 운영체제 서비스의 표준 인터페이스를 정의하여 코드 수정 없이 다양한 환경에서 실행을 보장합니다.
- 쉘 자체가 없는 환경
시스템 복구 모드나 아주 작은 임베디드 환경에서는 Bash 같은 무거운 쉘이 없을 수도 있습니다.
아주 단순한 /bin/sh만 있거나, 혹은 아예 쉘 없이 특정 서비스만 돌아가는 환경에서도 파일을 옮기거나 (cp), 목록을 보고(ls), 텍스트를 출력해야 할 일이 생깁니다. 이때 독립적으로 존재하는 Coreutils 바이너리들은 쉘의 존재 여부와 상관없이 자기 할 일을 수행합니다.
따라서 정리하자면
내장 명령어는 "쉘 안에서 빠르게 처리하기 위한 도구" 이고,
외장 명령어는 "어디서든 누구라도 호출할 수 있는 표준 도구"입니다.
2. Coreutils의 내부 구조
Coreutils 소스 폴더를 열어보면 신기한 구조를 발견할 수 있습니다.
- src/ 디렉토리
src/ 폴더 안에는 우리가 알고 있는 명령어들이 ls.c, cp.c, cat.c 같은 이름으로 각각 존재합니다.- 개별 독립성 : 각 명령어는 각자의 main() 함수를 가진 완전한 프로그램입니다. 즉, ls.c를 컴파일하면 ls가 되고, cp.c를 컴파일하면 cp가 됩니다.
- 명령어의 개성 : 각 파일 안에는 해당 명령어만이 가진 독특한 로직이 담겨 있습니다. 예를 들어,
ls.c에는 "파일 목록을 정렬하고 색깔을 입히는 법"이 들어있고, cat.c에는 "파일의 내용을 버퍼에 담아 표준 출력으로 쏴주는 법"이 들어있습니다.
- lib/ 디렉토리
여기가 진짜 핵심입니다. 사실 src/에 있는 코드들만 가지고는 프로그램을 만들 수 없습니다. 수많은 공통 기능들이 이 lib/에 몰려 있는데, 이를 보통 Gnulib(GNU Protability Library)라고 부른답니다.- 중복 제거 : 파일을 열고(oepn), 에러 메시지를 출력하고, 메모리를 할당하는 일은 ls나 cp나 똑같습니다.
이걸 100여개의 .c 파일에 다 적으면 관리가 안 되겠죠? 그래서 공통 기능을 lib/에 한 번만 정의해 둡니다. - 이식성 해결사 : 리눅스, 유닉스, BSD 등 시스템마다 시스템 콜(system call)방식이 조금씩 다를 수 있습니다. lib/은 이 차이를 흡수합니다.
- src/ls.c가 "파일 정보 좀 가져와"라고 lib/에 요청하면, lib/은 현재 OS가 무엇인지 판단해서 가정 적절한 방식으로 정보를 가져다줍니다.
- 중복 제거 : 파일을 열고(oepn), 에러 메시지를 출력하고, 메모리를 할당하는 일은 ls나 cp나 똑같습니다.
- 왜 이런 구조를 사용하는가?
- 유지 보수 : 만약 에러 메시지를 출력하는 방식에 보안 취약점이 발견되었다면? 100개의 파일을 다 고칠 필요 없이 lib/에 있는 코드 한 줄만 고치고 다시 컴파일하면 모든 명령어가 한꺼번에 수정됩니다.
- 보안 관점 : lib와 src를 나누는 구조는 단순한 '코드 정리' 그 이상입니다. 이는 서로를 격리하여 시스템의 방어력을 극대화하는 전략입니다.
3. Coreutils-9.9 빌드
1. 소스 준비 및 이동
반드시 lfs 유저 상태에서 진행합니다.
cd $LFS/sources
tar -xvf coreutils-9.9.tar.xz
cd coreutils-9.9
2. 설정 (Configure)
Coreutils는 크로스 컴파일 환경에서 몇 가지 특수한 옵션이 필요합니다.
./configure --prefix=/usr \
--host=$LFS_TGT \
--build=$(build-aux/config.guess) \
--enable-install-program=hostname \
--enable-no-install-program=kill,uptime
- --enable-install-program=hostname : 기본적으로 빌드되지 않는 hostname 명령어를 포함시킵니다.
LFS처럼 밑바닥부터 만드는 환경에서는 나중에 네트워크 도구를 깔기 전까지는 hostname 명령어가 아예 없을 수도 있습니다. 그래서 가장 기초적인 Coreutils를 빌드할 때, 나중에 고생하지 말고 지금 그냥 하나 같이 만들어두자라고 강제 옵션을 넣는 것입니다. - --enable-no-install-program=kill,uptime : 이 두 명령어는 나중에 다른 패키지(Procps-ng 등)에서 더 좋은 버전으로 설치할 것이기 때문에 여기서는 제외합니다.
3. 컴파일 및 설치
make #컴파일
make DESTDIR=$LFS install #지정된 prefix 경로로 설치
4. 경로 조정 (표준 준수)
리눅스 표준에 맞춰 일부 명령어의 위치와 매뉴얼 페이지 경로를 조정해 줍니다.
# chroot 명령어를 sbin(시스템 관리용)으로 이동
mv -v $LFS/usr/bin/chroot $LFS/usr/sbin
# 매뉴얼 페이지 위치 조정
mkdir -pv $LFS/usr/share/man/man8
mv -v $LFS/usr/share/man/man1/chroot.1 $LFS/usr/share/man/man8/chroot.8
sed -i 's/"1"/"8"/' $LFS/usr/share/man/man8/chroot.8
5. 확인 및 뒷정리
설치가 끝나면 제가 만든 시스템 폴더 안에서 ls 같은 명령어들이 보이기 시작합니다.
아래와 같은 방법으로 테스트합니다.
ls -l $LFS/usr/bin/ls
ls -l $LFS/usr/bin/cp
#남은 소스 정리
cd $LFS/sources
rm -rf coreutils-9.9
자 이것으로, Coreutils 빌드를 마쳤습니다. 해당 빌드를 함에 있어서, 특별한 오류는 발생하지 않았습니다. 아직까지는 완벽하게 빌드되고 있습니다.
여러분이 느낄지 모르겠습니다만, LFS 한층 한층 쌓아 갈 때마다, 저의 탐구열이 늘어가고, 그에 따라 포스팅 내용도 많이 발전하고 있다고 생각이 듭니다.
초기에는 매뉴얼을 따라가며 포스팅하는 단순한 설치 가이드에 불과했다면, 이제는 라이브러리와 패키지의 내부 구조를 분석하며 시스템의 작동 원리를 깊이 있게 이해하는 단계에 들어서는 포스팅이라고 생각 듭니다.
앞으로도 깊이 있는 분석과 꼼꼼한 기록을 유지하며 포스팅하겠습니다. 감사합니다.
------------------------------- 오늘의 옵션 -------------------------------------
exec 옵션 : 리눅스 및 유닉스 계열 시스템에서 -exec 옵션은 검색된 파일이나 디렉토리에 대해 특정 명령어를 즉시 실행할 때 사용됩니다. 즉시 실행의 의미는 find 명령어가 파일을 찾자마자, 그 파일을 재료 삼아 다른 프로그램을 바로 돌린다는 뜻입니다.
보통은 파일을 찾은 뒤에 그 목록을 보고 나서, 일일이 복사(cp)하거나 삭제(rm)해야 했지만 -exec는 이 과정을 자동으로 연결해 줍니다.
1. 기본 문법
find [경로] [조건] -exec [명령어] {} \;
{} : find가 찾아낸 파일명(또는 경로)이 들어갈 자리표시자(Paceholder)입니다.
\; : -exec 옵션의 끝을 알리는 기호입니다. 쉘이 세미콜론을 직접 해석하지 않도록 백슬레시를 붙여야 합니다.
2. 주요 사용 예시
- 파일 삭제 : 확장자가 .log인 모든 파일을 찾아 삭제합니다.
- find . -name "*.log" -exec rm -f {} \;
- 권한 변경 : 모든 디렉토리의 권한을 755로 변경합니다.
- find . -type d -exec chmod 755 {} \;
- 내용 검색 : 찾은 파일들 안에서 특정 문자열을 검색합니다.
- find . -type f -exec grep "error" {} \;
3. 주의사항
- 실행 전 확인 : -exec는 조건에 맞는 파일을 즉시 처리하므로, rm 같은 위함한 명령어를 쓸 때는 먼저 -print 옵션으로 대상 파일을 확인하는것이 좋다.
- 대체 옵션 : 실행 전 사용자 확인이 필요하다면 -exec 대신 -ok 옵션을 사용합니다. 각 파일마다 실행 여부를 묻습니다.
4. -exec가 보안에서 중요한 이유
- 명령어 삽입 공격 (Command Injection) : 해커드르은 -exec 옵션 뒤에 자기들의 악성 코드를 몰래 끼워 넣으려고 합니다.
(예 : find . -exec "rm -rf /" \;)
find . : 현재 디렉토리( . )에서 아무 파일이나 다 찾아라
-exec ... \; : 파일을 하나 찾을 때마다, 뒤에 오는 명령어를 실행해라
rm -rf / : 리눅스 시스템의 뿌리(/ , 루트 디렉토리)부터 모든 파일을 강제로( -f ), 하위 폴더까지 싹( -r ) 지워라 - 권한 상승 : 만약 find가 루트(Root) 권한으로 실행 중인데 -exec로 다른 프로그램을 실행한다면, 그 프로그램도 루트 권한을 갖게 됩니다. 이 권한의 전이를 아주 꼼꼼하게 설계 해야합니다.
'리눅스 > LFS' 카테고리의 다른 글
| [LFS9] 텍스트 가공 및 분석의 유틸리티 Gawk 패키지 빌드 (0) | 2026.03.11 |
|---|---|
| [LFS8] 파일 및 디렉토리 검사를 위한 Diffutils 빌드 (0) | 2026.03.04 |
| [LFS6] 리눅스 시스템의 기본 쉘 Bash 빌드 (0) | 2026.02.11 |
| [LFS5] M4와 Ncurses 빌드 중 지속되는 트러블슈팅과 그것을 해결하기 위한 여정 (0) | 2026.01.31 |
| [LFS4] Glibc(GNU C Library) 빌드 : 시스템의 심장을 뛰게 해보자 (0) | 2026.01.27 |