grep
텍스트 검색 및 파일 내용 매칭에 사용되며, 구문 형식은 grep pattern filename입니다. 예:
# for를 포함하는 모든 행 찾기
grep 'for' test.sh
# 여러 파일에서 검색
grep 'for' test.sh bak.sh
# 매칭된 부분 하이라이트
grep 'for' test.sh --color=auto
기본적으로 와일드카드 매칭이며, 정규 표현식을 사용하려면 E(extended) 옵션을 활성화해야 합니다.
# echo로 시작하는 모든 행 찾기
grep -E '^\s*echo' test.sh
또는 기본적으로 정규 표현식을 허용하는 egrep 명령을 사용합니다.
# 위와 동일
egrep '^\s*echo' test.sh
기타 옵션 및 특징:
# 매칭된 부분만 출력
grep -o -E '\s[a-zA-Z]\s' test.sh
# 매칭되지 않는 행만 출력(반전 선택)
grep -v -E '\s[a-zA-Z]\s' test.sh
# 매칭된 행 수 카운트
grep -c -E '\s[a-zA-Z]\s' test.sh
# 매칭된 항목 수 카운트
grep -o -E '\s[a-zA-Z]\s' test.sh | wc -l
# 매칭된 행과 행 번호 출력
grep -n -E '\s[a-zA-Z]\s' test.sh
# 매칭된 항목이 있는 파일명 출력(반전 선택은 L)
grep -l 'return' test.sh bak.sh return.sh
# 디렉토리 재귀 검색, 파일명 및 행 번호 출력
grep -n -R 'echo' .
# 대소문자 무시
grep -i "ECho" test.sh
# 디렉토리 검색 시 파일명 형식 제한
# 주의: include 파라미터 값은 find 명령과 달리 반드시 따옴표로 감싸야 합니다.
grep -R '=>' . --include '*.jsx}'
# 디렉토리 검색 시 특정 형식의 파일명 및 디렉토리 제외
grep -R '' . --exclude '*.md' --exclude-dir 'node_modules'
# \0을 종결자로 출력하며, 보통 -l과 함께 사용하여 파일명만 출력한 뒤 xargs -0으로 다음 명령에 전달합니다.
grep "echo" . -R -l -Z | xargs ls -l
# 저소음(Silent) 매칭, stdin에 아무것도 출력하지 않으며 매칭 성공 시 0을 반환합니다.
if echo ' abcd' | grep -q -E '^\s*abc'; then echo 'starts with abc'; fi
매칭된 항목을 찾는 것 외에도 컨텍스트(앞뒤 내용)를 출력할 수 있습니다.
# 매칭된 행과 그 다음 2행 출력
seq 10 | grep '4' -A 2
# 매칭된 행과 그 이전 2행 출력
seq 10 | grep '4' -B 2
# 매칭된 행과 전후 각 2행 출력
seq 10 | grep '4' -C 2
cut
3가지 분할 방식이 있습니다: -c는 문자 기준, -f는 필드 기준, -b는 바이트 기준 분할입니다.
문자 기준 분할:
# 각 행의 3번째 문자부터 5번째 문자까지 잘라내기
echo $'1 2 3 4\n5 6 7 8' | cut -c 3-5
# 3번째 문자부터 행 끝까지
echo $'1 2 3 4\n5 6 7 8' | cut -c 3-
# 5번째 문자 및 그 이전
echo $'1 2 3 4\n5 6 7 8' | cut -c -5
필드(열) 기준 분할, 한 열을 하나의 필드로 취급하며 awk와 유사하게 지정된 열을 추출합니다.
echo $'1 2 3 4\n5 6 7 8' | cut -d ' ' -f 1,3
주의: 매우 중요한 문제는 구분자(delimiter)입니다. 기본값은 탭 문자(Ctrl + v 후 tab)이며, -d 옵션으로 다른 문자를 지정할 수 있지만 단일 문자만 가능하여 사용하기 불편합니다(공백이 여러 개인 경우 대응할 수 없으며, 단일 문자로 구분된 내용에만 적합합니다).
예를 들어 ps 결과에서 PID와 CMD 열을 잘라내는 경우:
# awk로 완벽하게 해결 가능
ps | awk '{print $1,$4}'
# cut은 사용하기 불편함
# 기본 탭 기준 cut은 무효함
ps | cut -f 1,4
# 공백을 지정해도 cut 결과가 올바르지 않음
ps | cut -d ' ' -f 1,4
바이트 기준 분할, 기본적으로 다중 바이트 문자 경계를 무시합니다.
# 기본적으로 문자를 가로질러 분할하여 한자가 깨짐
echo "想做个好人" | cut -b 2-4
# -n 옵션은 다중 바이트 문자를 분할하지 않아 `想`을 얻음
echo "想做个好人" | cut -n -b 2-4
sed
stream editor의 약자로 비대화형 편집기이며 자주 사용되는 텍스트 처리 도구입니다. 가장 흔히 쓰이는 기능은 텍스트 치환입니다.
# 행 시작 부분의 공백 문자 삭제
echo $' \t 我想左对齐' | sed $'s/^[[:space:]]*\t*//g'
또 다른 자주 쓰이는 기능은 파일 인플레이스(In-place) 치환(치환 후 결과를 원본 파일에 쓰기)입니다.
# test.txt의 모든 단어를 [word]로 치환
echo $'this is a new file\nnext line' > test.txt
sed -i '' -E 's/[[:alpha:]]{1,}/[word]/g' test.txt
P.S. Mac에서 sed -i를 이용한 인플레이스 치환 시 반드시 백업 파일 확장자를 지정해야 합니다(빈 문자열도 가능). 또한 Mac의 sed는 GNU sed와 차이가 매우 큽니다. 예를 들어 +, ?가 없거나 \b가 없는 등의 차이가 있으니 자세한 내용은 Differences between sed on Mac OSX and other “standard” sed?를 확인하세요.
일반적으로 구분자는 /를 사용하지만 어떤 기호든 가능합니다.
# 세미콜론
echo $'\t\t\t我想左对齐' | sed $'s;^\t*;;'
# Mac에서는 | 도 가능함
echo $'\t\t\t我想左对齐' | sed $'s|^\t*||'
# 구분자 의미가 없는 기호는 이스케이프가 필요함
echo '&c' | sed -E 's;&[[:alpha:]]{1,}\;;\&;'
기타 자주 쓰이는 옵션:
# /pattern/d 매칭되는 행 삭제
sed '/^$/d' test.sh
# &는 이번에 매칭된 부분을 의미함
echo 'abc de' | sed -E 's/[[:alpha:]]{1,}/[&]/g'
# \123.. 역참조(Back-reference)
echo 'aabcc' | sed 's/\([[:alpha:]]\)\1/[\1x2]/g'
# sed 'expr1; expr2...' 여러 정규식을 순차적으로 적용, 파이프와 동일한 효과
echo 'aabcc' | sed 's/\([[:alpha:]]\)\1/[\1x2]/g;s/\].*\[/][/'
주의: 역참조 예시의 캡처 괄호는 반드시 이스케이프해야 합니다.
awk
보통 열 단위 추출에 사용됩니다. 예:
# 파일명
ps | awk '{print $1, $4}'
매우 강력하며 열과 행 모두 조작할 수 있습니다. 일반적인 형식은 다음과 같습니다.
awk 'BEGIN{ print "start" } pattern1{ command } END{ print "end" }' file
BEGIN, END 및 패턴 블록은 모두 선택 사항입니다. 먼저 BEGIN 블록이 실행된 후 입력 내용에서 한 행씩 읽어 들여 각 패턴 블록을 차례로 실행합니다. 모든 내용을 다 읽으면 END 블록이 실행됩니다.
pattern 또한 선택 사항이며, 제공하지 않으면 모든 행에 대해 블록 내의 명령문을 무조건 실행합니다. 예:
# 그대로 출력
echo $'1 2\n3 4' | awk '{print}'
# 행 수 통계
echo $'1 2\n3 4' | awk 'BEGIN{lineCount=0} {let lineCount++} END{print lineCount}'
print는 조금 특이합니다. 공백으로 구분된 인수는 출력 시 붙어서 나오고, 쉼표로 구분된 인수는 출력 시 공백으로 구분됩니다. 예:
# 123 출력
echo '' | awk '{print 1 2 3}'
# 1 2 3 출력
echo '' | awk '{print 1,2,3}'
# 1-2-3 출력
echo '' | awk '{print 1"-"2"-"3}'
내장 변수
awk에는 몇 가지 특수한 내장 변수가 있습니다.
-
NR: number of records, 현재 행 번호
-
NF: number of fields, 현재 행의 필드 수
-
$0: 현재 행의 텍스트 내용
-
$1, $2, $3...: 현재 행의 n번째 필드 텍스트 내용
따라서 행 수를 세는 더 간단한 방법이 있습니다.
echo $'1 2\n3 4' | awk 'END{print NR}'
한 행을 읽을 때마다 NR이 업데이트되므로 END 블록에 도달했을 때의 값이 총 행 수가 됩니다.
주의: awk에서 변수 값을 가져올 때는 내장 변수든 사용자 정의 변수든 $를 붙일 필요가 없습니다.
외부 변수 전달
awk 내에서 외부 변수를 직접 사용할 수 없으므로 전달해 주어야 합니다.
# 빈 값 출력
x=3; echo '' | awk '{print x}'
# 3 출력
x=3; echo '' | awk -v x=$x '{print x}'
여러 외부 변수를 전달하는 더 간단한 방법이 있습니다.
# 3 4 5 출력
x=3; y=4; z=5; echo '' | awk -v x=$x -v y=$y -v z=$z '{print x,y,z}'
# 간단한 방식
x=3; y=4; z=5; echo '' | awk '{print x,y,z}' x=$x y=$y z=$z
명령문 블록 뒤에 키-값 쌍으로 붙여서 커맨드 라인 인수로 전달합니다.
getline
일반적으로 다음 행을 읽는 데 사용됩니다. 용법은 다음과 같습니다.
# 첫 번째 행 출력
echo $'1 2\n3 4' | awk 'BEGIN{getline line; print line}'
# 첫 번째 행 건너뛰기(첫 행의 total xxx를 버림)
ls -l | awk 'BEGIN{getline} {print $0}'
인수가 없는 getline은 $0, $1, $2...를 업데이트하지만, 인수가 있는 경우는 업데이트하지 않습니다. 예:
# 인수가 있으면 필드 변수를 업데이트하지 않음
echo $'1 2\n3 4' | awk 'BEGIN{print $0; getline line; print $0}'
# 인수가 없으면 필드 변수를 업데이트함
echo $'1 2\n3 4' | awk 'BEGIN{print $0; getline; print $0}'
기타 명령 실행
awk에서 다른 명령을 실행하는 방식도 조금 특이합니다.
# $0은 md5 test.sh의 출력 결과임
echo '' | awk '{"md5 test.sh" | getline; print $0}'
# 또는
echo '' | awk '{"md5 test.sh" | getline md5; print md5}'
반복문, 조건문
awk에서는 C 언어 스타일의 반복문, 조건문 등의 구조를 사용할 수 있습니다.
# while 반복문
seq 10 | awk 'BEGIN{while (getline){print $0}}'
# for 반복문
seq 10 | awk 'BEGIN{for(i=0; i<10; i++){getline; print $0}}'
# 조건문
seq 10 | awk 'BEGIN{for(i=0; i<10; i++){getline; if ($1 % 2) {print $0}}}'
이러한 기능 덕분에 awk는 매우 강력해지며 파일을 행 단위로 처리하기가 매우 편리합니다.
P.S. 더 많은 문장 구조 및 내장 함수는 man awk를 확인하세요.
기타 옵션
자주 사용되는 옵션:
# 구분자 지정, 기본값은 공백
echo 'a;b;c' | awk -F ';' '{print $2}'
# 또는
echo 'a;b;c' | awk 'BEGIN{FS=";"} {print $2}'
# 출력 구분자 지정
echo 'a b c' | awk 'BEGIN{OFS="\t"} {print $1,$2,$3}'
# 패턴 필터링
# 행 번호가 2보다 작음
echo $'1 2\n3 4' | awk 'NR < 2{print $0}'
# 행 번호가 2에서 4 사이
seq 10 | awk 'NR==2,NR==4{print $0}'
# 정규 표현식 매칭
echo $'1 2\n3 4' | awk '/^3/{print $0}'
파일 내용 처리
행 단위 읽기:
# 입력 리다이렉션
while read line; do echo $line; done < test.sh
# 또는 서브셸
cat test.sh | (while read line; do echo $line; done)
한 행의 각 필드 읽기:
line='1 2 3 4'; IFS=' '; for field in $line; do echo $field; done
한 필드의 각 문자 읽기:
field='word'; for ((i=0;i<${#field};i++)) do echo ${field:i:1}; done
여기서는 부분 문자열 추출 기술인 ${field:i:1}을 사용했습니다. 형식은 ${var:start_index:length}이며 시작점은 음수(뒤에서부터의 위치)가 될 수 있습니다.
# 마지막 2개 문자 추출
field='abcdef'; echo ${field:(-2):2}
P.S. shell의 이러한 문자열 처리 지원은 정말 강력합니다.
paste
텍스트 내용을 열 단위로 병합합니다. cat은 행 단위로 병합하지만, paste는 열 단위로 병합할 수 있습니다.
seq 3 > no.txt
echo $'吃饭\n睡觉\n打豆豆' > action.txt
# 행 단위 병합
cat no.txt action.txt
# 열 단위 병합
paste no.txt action.txt
paste 결과는 다음과 같습니다.
# paste no.txt action.txt | sed -n l
1\t吃饭$
2\t睡觉$
3\t打豆豆$
기본 구분자는 탭 문자이며, -d 옵션으로 다른 구분자를 지정할 수 있습니다.
# 병합 결과를 세미콜론으로 구분
paste -d ';' no.txt action.txt | sed -n l
아직 댓글이 없습니다