리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 27 / 50 · Part 3. Linux 명령어 활용
실습 환경: Rocky Linux 9 (10.0.0.200) · 21편 실습용 샘플 로그
이전 글: 26. sed 활용
21~26편에서 grep … | sort | uniq -c | sort -rn, 2>/dev/null, > 결과.txt를 계속 썼다. 이번 글은 이 기호들이 실제로 무엇을 연결하는지 정리한다.
파이프와 리다이렉션을 정확히 이해해야 하는 이유는 세 가지다.
>는 파일을 덮어쓴다. 순서 하나 틀리면 기존 결과나 원본을 날린다.tee, script로 화면에 본 것과 같은 내용을 파일로 남겨야 보고서의 근거가 된다.| 번호 | 이름 | 기본 연결 | 용도 |
|---|---|---|---|
| 0 | stdin | 키보드 | 입력 |
| 1 | stdout | 화면 | 정상 출력 |
| 2 | stderr | 화면 | 오류·경고 메시지 |
4편의 /proc/<PID>/fd에서 본 번호가 바로 이것이다. 프로그램이 파일이나 소켓을 더 열면 3번부터 번호가 붙는다.
| 표기 | 의미 |
|---|---|
> file | stdout을 파일로 (덮어쓰기) |
>> file | stdout을 파일 끝에 추가 |
2> file | stderr를 파일로 |
2>/dev/null | stderr 버리기 |
> file 2>&1 | stdout과 stderr 모두 파일로 |
&> file | 위와 같음 (bash) |
< file | 파일을 stdin으로 |
<<EOF … EOF | 여러 줄을 stdin으로 (here-document) |
| 표기 / 도구 | 의미 |
|---|---|
| `A | B` |
| `A | & B` |
tee file | 입력을 화면(stdout)과 파일에 동시에 |
tee -a file | 파일에 추가 |
<(명령) | 명령 결과를 파일처럼 사용 (process substitution) |
xargs | stdin의 줄을 다른 명령의 인자 로 |
set -o pipefail | 파이프 중 하나라도 실패하면 전체 실패 |
${PIPESTATUS[@]} | 파이프 각 단계의 종료 코드 |

grep | sort | uniq는 grep이 끝난 뒤 sort가 시작되는 구조가 아니다. 쉘이 세 프로세스를 동시에 만들고, 커널의 파이프 버퍼로 grep의 1번 출력을 sort의 0번 입력에 연결한다. 그래서 ps로 보면 파이프의 모든 명령이 함께 떠 있다.
2>&1은 "2번을 지금 1번이 가리키는 곳으로 복사"라는 뜻이다. 그래서 순서에 따라 결과가 달라진다. 아래는 실제로 실행한 결과다.
$ ls /etc/hostname /nope > out1.txt 2>&1
$ cat out1.txt
ls: cannot access '/nope': No such file or directory
/etc/hostname
$ ls /etc/hostname /nope 2>&1 > out2.txt
ls: cannot access '/nope': No such file or directory ← 화면에 출력됨
$ cat out2.txt
/etc/hostname
쉘은 기본적으로 파이프의 마지막 명령 의 종료 코드만 $?에 남긴다.
$ grep zzz /nope | sort; echo "exit=$?"
grep: /nope: No such file or directory
exit=0 ← grep 실패를 놓침
$ set -o pipefail; grep zzz /nope | sort; echo "exit=$?"
grep: /nope: No such file or directory
exit=2
$ false | true | true; echo ${PIPESTATUS[@]}
1 0 0
로그 파일 경로가 틀려 grep이 실패했는데도 결과가 "0건"으로 보고되는 사고를 막으려면, 분석 스크립트에는 set -o pipefail 을 넣는다(28편).
cd ~/lab
# 1) 결과 저장 — 덮어쓰기 vs 추가
grep -c "Failed password" secure > report.txt
grep -c "Accepted" secure >> report.txt
cat report.txt
# 2) 권한 오류는 버리고 결과만
find / -xdev -name authorized_keys 2>/dev/null
# 3) 결과와 오류를 따로 저장
sudo find / -xdev -perm -4000 -type f > suid.txt 2> suid_err.txt
wc -l suid.txt suid_err.txt
# 4) 화면에 보면서 동시에 파일로 기록
grep "Accepted" secure | tee accepted.txt | wc -l
# 5) 두 명령 결과를 파일 없이 비교 (process substitution)
# comm -12: 양쪽에 모두 있는 줄만
comm -12 <(grep -oE "from [0-9.]+" secure | sort -u) \
<(awk '{print "from "$1}' access_log | sort -u)
# 6) stdin 줄을 인자로 — 의심 파일 해시 일괄 계산
printf '/usr/bin/ls\n/usr/bin/cat\n' | xargs sha256sum
# 7) 여러 줄 입력 (here-document)
cat > ioc.txt <<EOF
10.0.0.128
203.0.113.7
EOF
grep -F -f ioc.txt secure | wc -l
# 8) 분석 세션 전체 기록
script -q analysis_$(date +%F).log # 이후 입력·출력 모두 기록, exit 로 종료
샘플 로그에 실행한 결과다.
① report.txt
113
4
첫 줄은 >로 만들고 두 번째 줄은 >>로 추가했다. 두 번째에도 >를 썼다면 113은 사라진다.
② process substitution 비교
from 10.0.0.10
from 10.0.0.128
SSH 로그의 출발지 목록과 웹 로그의 출발지 목록을 임시 파일 없이 비교했다. 양쪽에 모두 있는 IP 는 SSH와 웹을 모두 사용한 출발지다. 10.0.0.10은 관리자 PC, 10.0.0.128은 공격 출발지다. 공격 IP가 두 서비스에 모두 나타난다는 것은 다단계 공격을 의심할 근거가 된다.
| 실수 | 결과 | 예방 |
|---|---|---|
>로 원본 경로에 출력 | 원본 로그를 빈 파일로 덮어씀 (grep x secure > secure) | 결과는 항상 별도 디렉터리, set -o noclobber |
2>/dev/null 남용 | 권한 부족으로 검색 못 한 영역을 모른 채 "없음" 판단 | 오류를 별도 파일로 저장해 확인 |
| 파이프 종료 코드 무시 | 입력 파일 오류를 "0건"으로 보고 | set -o pipefail, PIPESTATUS |
| 분석 과정 미기록 | 결과의 근거를 재현할 수 없음 | tee, script, 명령과 결과를 함께 저장 |
| 공격자의 출력 숨기기 | > /dev/null 2>&1로 악성 명령 출력 은닉 | 출력이 아닌 실행 자체 를 기록 (auditd execve) |
set -o noclobber를 켜면 >가 기존 파일을 덮어쓰지 않고 오류를 낸다. 덮어써야 할 때는 >|를 명시한다. 분석 작업용 셸에서 켜 두면 실수를 줄일 수 있다.
set -o pipefail
set -o noclobber
OUT=~/case_$(date +%Y%m%d_%H%M); mkdir -p "$OUT"
# 명령과 결과를 함께 남기기
{
echo "### $(date -Is) grep Accepted"
grep "Accepted" /var/log/secure
} 2>&1 | tee "$OUT/01_accepted.txt"
sha256sum "$OUT"/*.txt > "$OUT/SHA256SUMS"
10편 Triage 스크립트의 run() 함수도 같은 원리(명령·시각·결과를 >로 묶어 저장)였다.
공격 명령에는 흔히 > /dev/null 2>&1, nohup … &가 붙는다. 출력을 숨기고 세션이 끊겨도 계속 실행 되게 하기 위해서다. 그래서 관제에서는 명령의 출력이 아니라 실행 기록(auditd execve)과 프로세스 상태(34편 signal·백그라운드) 를 본다.
[분석 시작] set -o pipefail / noclobber, 케이스 디렉터리 생성
↓
[수집] 명령 | tee 케이스/NN_이름.txt (오류도 함께 2>&1)
↓
[비교] diff <(A) <(B) — SSH 출발지 vs 웹 출발지
↓
[검증] PIPESTATUS·오류 파일로 누락 영역 확인
↓
[보존] sha256sum → 보고서에 해시 기재
|는 stdout만 다음 명령으로 넘긴다. 오류까지 넘기려면 2>&1 | 또는 |&.> file 2>&1 순서.$?는 마지막 명령 것이다. 분석 스크립트에는 set -o pipefail.tee, script로 분석 과정을 기록하고, noclobber로 덮어쓰기 사고를 막는다.<(명령)으로 두 결과를 임시 파일 없이 비교할 수 있다.다음 글 「28. Linux Shell Script 기초」 에서는 지금까지의 명령어 조합을 스크립트로 묶는다. 변수, 조건문, 반복문, 함수, 종료 코드를 정리하고 set -euo pipefail, 따옴표, 절대경로 같은 안전한 스크립트 작성 원칙 과 함께 인증 로그 요약 리포트 스크립트를 만든다.