12.4. 텍스트 처리 명령어

명령어 목록

sort

주로 파이프에서 필터로 쓰여 파일을 정렬할 때 쓰입니다. 다양한 키나 문자 위치에 따라 텍스트 스트림이나 파일 전체를 정렬할 수 있습니다. -m 옵션을 쓰면 이미 정렬된 파일을 합쳐줍니다. info page에서 많은 기능과 옵션들을 볼 수 있습니다. 예 10-8예 10-9를 참고하세요.

tsort

위상 정렬(Topological sort) 명령어로서, 공백문자로 구분되는 문자열의 쌍을 읽어 패턴에 따라 정렬.

diff, patch

diff: 유연한 파일 비교 유틸리티. 대상 파일들을 줄 단위로 차례 차례 비교해 줍니다. 예를 들어 낱말 사전을 비교하는 어플리케이션에서 sortuniq를 써서 파일을 필터링하고 파이프를 통해 diff로 넘겨주는 상황등에서 유용하게 쓰일 수 있습니다. diff file-1 file-2 는 두 파일에서 서로 다른 줄이 있으면 그 줄이 속해 있는 파일을 캐럿과 함께 보여줍니다.

--side-by-side 옵션을 주면 비교할 파일을 서로 구분된 컬럼에 두고 줄 단위로 비교하면서 일치하지 않는 줄을 표시해 줍니다.

diff의 다양한 프론트엔드(frontend)로는 spiff, wdiff, xdiff, mgdiff가 있습니다.

작은 정보: diff 는 비교할 두 파일이 똑같으면 종료 상태 0을 리턴하고 다르면 1을 리턴합니다. 이것 때문에 diff를 쉘 스크립트의 테스트문에서 쓸 수 있습니다(아래 참조).

diff는 보통 patch에 쓸 다른 파일을 만들어 내는데 쓰입니다. -e 옵션은 diff가 자신의 출력을 edex 스크립트에서 쓸 수 있는 파일로 만들어 내게 합니다.

patch: 유연한 버전 관리 유틸리티. patchdiff가 만들어낸 다른 파일이 주어지면 패키지의 이전 버전을 새로운 버전으로 업그레이드 해 줄 수 있습니다. 이렇게 하면 새롭게 발표된 패키지 전체를 배포할 필요 없이 비교적 작은 "diff" 파일만 배포하면 되므로 아주 편리합니다. 따라서 커널 "패치"는 리눅스 커널의 빈번한 배포시 더 선호됩니다.

patch -p1 <patch-file
# 'patch-file'에 나열된 모든 변경 사항을 
# 그 파일 내부에서 참조하고 있는 파일에 대해 적용 해 줍니다.
# 이렇게 하면 패키지의 새로운 버전으로 업그레이드가 됩니다.

커널 패치 하기:

cd /usr/src
gzip -cd patchXX.gz | patch -p0
# 'patch'로 커널 소스를 업그레이드 하기.
# 익명의 저자(알란 콕스?)가 쓴 리눅스 커널 문서 "README"에서 인용.

참고: diff는 디렉토리 트리 전체도 비교할 수 있습니다(실제로 존재하는 파일에 대해서).

bash$ diff ~/notes1 ~/notes2
Only in /home/bozo/notes1: file02
Only in /home/bozo/notes1: file03
Only in /home/bozo/notes2: file04
	      

작은 정보: gzip으로 묶인 파일을 비교하려면 zdiff 를 쓰세요.

diff3

diff의 확장 버전으로 동시에 세 개의 파일을 비교해 줍니다. 성공시에는 0을 리턴하지만 불행하게도 비교 결과에 대한 정보를 얻을 수는 없습니다.

bash$ diff3 file-1 file-2 file-3
====
 1:1c
   This is line 1 of "file-1".
 2:1c
   This is line 1 of "file-2".
 3:1c
   This is line 1 of "file-3"
	      

sdiff

두 개의 파일을 한 파일로 합치기 위해서 비교하거나 편집해 줍니다. 이 명령어는 사용자와 대화(interactive) 해야 하는 특성이 있어서 스크립트에서 쓰기는 어렵습니다.

cmp

cmp 명령어는 위에서 설명한 diff의 간단한 버전입니다. diff가 두 파일간의 차이점에 대해서 보고해 주는 반면, cmp는 단지 두 파일간에 서로 다른 부분만을 보여줍니다.

참고: cmpdiff처럼 두 파일이 똑같다면 종료 상태 0을 리턴하고 다르다면 1을 리턴합니다. 따라서 쉘 스크립트의 테스트문에서 cmp를 쓸 수 있습니다.

예 12-8. 스크립트에서 두 파일을 비교하기 위해 cmp 쓰기.

#!/bin/bash

ARGS=2  # 인자가 두 개 필요합니다.
E_BADARGS=65

if [ $# -ne "$ARGS" ]
then
  echo "사용법: `basename $0` file1 file2"
  exit $E_BADARGS
fi


cmp $1 $2 > /dev/null  # /dev/null 은 "cmp"의 출력을 안 보이게 해줍니다.
# 'diff'에서도 동작합니다. 즉, diff $1 $2 > /dev/null 

if [ $? -eq 0 ]        # "cmp"의 종료 상태를 확인.
then
  echo "\"$1\" 은 \"$2\" 와 똑같은 파일입니다."
else  
  echo "\"$1\" 은 \"$2\" 와 다른 파일입니다."
fi

exit 0

작은 정보: gzip으로 묶인 파일에는 zcmp를 쓰세요.

comm

다목적 파일 비교 유틸리티. 제대로 된 결과를 얻으려면 파일 내용이 정렬돼 있어야 합니다.

comm -options first-file second-file

comm file-1 file-2 출력은 세 칸으로 이루어 집니다:

  • column 1 = file-1에 유일한 줄

  • column 2 = file-2에 유일한 줄

  • column 3 = 두 파일 양쪽에 공통으로 나타나는 줄

다음 옵션은 하나 이상의 출력 칸을 제거해 줍니다.

  • -11번 칸을 제거

  • -22번 칸을 제거

  • -33번 칸을 제거

  • -121번과 2번 칸을 제거, 등등.

uniq

이 필터는 정렬된 파일에서 중복된 줄을 제거합니다. 보통 파이프에서 sort와 같이 쓰입니다.
cat list-1 list-2 list-3 | sort | uniq > final.list
# list 파일들을 붙이고,
# 졍렬한 다음,
# 중복된 줄을 제거한 후,
# 마지막으로 결과를 출력 파일로 저장.

유용한 옵션인 -c을 쓰면 입력 파일의 각 줄 앞에 중복된 수를 표시해 줍니다.

bash$ cat testfile
이 줄은 한 번만 나옵니다.
이 줄은 두 번 나옵니다.
이 줄은 두 번 나옵니다.
이 줄은 세 번 나옵니다.
이 줄은 세 번 나옵니다.
이 줄은 세 번 나옵니다.


bash$ uniq -c testfile
      1 이 줄은 한 번만 나옵니다.
      2 이 줄은 두 번 나옵니다.
      3 이 줄은 세 번 나옵니다.


bash$ sort testfile | uniq -c | sort -nr
      3 이 줄은 세 번 나옵니다.
      2 이 줄은 두 번 나옵니다.
      1 이 줄은 한 번만 나옵니다.
             

sort INPUTFILE | uniq -c | sort -nr 이라고 하면 INPUTFILE 파일의 발생 빈도 목록을 만들어 줍니다(sort 명령어의 -nr은 숫자를 거꾸로 정렬). 이 템플릿은 로그 파일이나 사전 목록, 구문 분석이 필요한 어떤 것에도 쓰일 수 있습니다.

예 12-9. 낱말 빈도수 분석

#!/bin/bash
# wf.sh: 조잡한 텍스트 파일의 낱말 빈도 분석.


# 명령어줄에서의 입력 파일을 확인용.
ARGS=1
E_BADARGS=65
E_NOFILE=66

if [ $# -ne $ARGS ]  # 필요한 인자가 스크립트로 맞게 넘어왔는지?
then
  echo "사용법: `basename $0` filename"
  exit $E_BADARGS
fi

if [ -f "$1" ]       # 파일이 존재하는지 확인.
then
  file_name=$1
else
  echo "\"$1\" 는 없는 파일입니다."
  exit $E_NOFILE
fi



########################################################
# 메인
sed -e 's/\.//g'  -e 's/ /\
/g' "$1" | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr
#                           =========================
#                            발생 빈도

#  점(period)을 걸러내고 낱말 사이의 빈 칸을 라인피드로 바꾼 다음
#+ 모든 문자를 소문자로 변환한 뒤, 
#+ 각 낱말의 발생 빈도를 맨 앞에 두고 숫자대로 정렬.
########################################################

# 독자들을 위한 연습문제:
# 1) 'sed' 명령어가 콤마같은 다른 구둣점도 걸러내도록 해 보세요.
# 2) 여러개의 빈 칸과 다른 공백문자도 처리하도록 고쳐 보세요.
# 3) 다른 정렬용 키를 추가해서 동일한 발생 빈도를 갖는 낱말에 대해서
#+   알파벳 순으로 정렬되도록 해 보세요.

exit 0

bash$ cat testfile
이 줄은 한 번만 나옵니다.
이 줄은 두 번 나옵니다.
이 줄은 두 번 나옵니다.
이 줄은 세 번 나옵니다.
이 줄은 세 번 나옵니다.
이 줄은 세 번 나옵니다.


bash$ ./wf.sh testfile
      6 이
      6 나옵니다
      6 줄은
      5 번
      3 세
      2 두
      1 한
      1 번만
              

expand, unexpand

탭을 빈 칸으로 만들어 주는 필터로서 파이프에서 주로 쓰입니다.

unexpand 필터는 빈 칸을 탭으로 바꿔주는 필터로 expand와 완전히 반대로 동작합니다.

cut

파일에서 필드를 뽑아 내는 툴. awkprint $N 명령어 셋과 비슷하지만 약간의 제한 사항을 갖고 있습니다. 스크립트에서 awk보다 더 간단하게 쓸 수 있습니다. 특별히 중요한 옵션으로 -d(구분자)와 -f(필드 지시자)가 있습니다.

cut으로 마운트 된 파일 시스템 목록 알아내기: 쓰기:
cat /etc/mtab | cut -d ' ' -f1,2

cut으로 OS와 커널 버전 알아내기:
uname -a | cut -d" " -f1,3,11,12

cut 으로 이 메일 폴더에서 메세지 헤더를 뽑아내기:
bash$ grep '^Subject:' read-messages | cut -c10-80
Re: Linux suitable for mission-critical apps?
 MAKE MILLIONS WORKING AT HOME!!!
 Spam complaint
 Re: Spam complaint

cut으로 파일을 파싱하기:
# /etc/passwd 에 들어있는 모든 사용자 목록.

FILENAME=/etc/passwd

for user in $(cut -d: -f1 $FILENAME)
do
  echo $user
done

# Oleg Philon 이 제공해준 예제.

cut -d ' ' -f2,3 filenameawk -F'[ ]' '{ print $2, $3 }' filename 과 같은 결과를 보여줍니다.

예 12-29 참고.

colrm

칸 제거 필터. 파일에서 여러 칸(글자들 단위)을 지워주는데, 칸 범위를 지정하지 않으면 원래 파일을 표준출력으로 다시 내보냅니다. colrm 2 4 <filename 이라고 하면 텍스트 파일인 filename 각 줄의 두 번째 칸에서 4 번째 칸의 글자를 지웁니다.

주의

파일에 탭이나 출력 할 수 없는 글자가 포함되어 있다면 예상치 못한 동작을 할 수도 있습니다. 이런 경우에는 colrm 앞에 expandunexpand를 파이프로 걸어서 써 보기 바랍니다.

paste

서로 다른 파일들을 여러 단으로 나뉘어진 하나의 파일로 만들어 주는 툴로서, cut과 같이 써서 시스템 로그 파일을 만드는데 유용합니다.

join

특수한 목적을 가진 paste류의 명령어라고 보면 됩니다. 두 파일을 의미있는 형태로 묶어서 본질적으로는 간단한 관계 데이터베이스를 만들어 주는 강력한 유틸리티입니다.

join 명령어는 정확히 두 파일에 대해서 동작하지만 두 파일 사이에 공통으로 표시된 필드(tagged field)(보통은 숫자 라벨)가 들어 있는 줄에 대해서만 합쳐서 결과를 표준출력에 씁니다. 제대로 동작하려면 두 파일 모두, 표시 필드가 제대로 정렬되어 있어야 합니다.

File: 1.data

100 Shoes
200 Laces
300 Socks

File: 2.data

100 $40.00
200 $1.00
300 $2.00

bash$ join 1.data 2.data
File: 1.data 2.data

 100 Shoes $40.00
 200 Laces $1.00
 300 Socks $2.00
	      

참고: 표시 필드는 결과에서 한 번만 나옵니다.

head

파일 앞부분을 표준출력으로 보여 줍니다(기본적으로 10줄을 보여주지만 따로 지정해 줄 수도 있습니다). head는 재밌는 옵션이 몇 개 있습니다.

예 12-10. 10자리 랜덤한 숫자 만들기

#!/bin/bash
# rnd.sh: 10 자리 랜덤한 숫자 출력

# Script by Stephane Chazelas.

head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'


# =================================================================== #

# 분석
# ----

# head:
# -c4 옵션은 첫 번째 4 바이트만 받아 들입니다.

# od:
# -N4 옵션은 출력을 4 바이트로 제한해 줍니다.
# -tu4 옵션은 출력을 unsigned 10진 포맷으로 해줍니다.

#  sed: 
#  -n 옵션은 "s" 명령어의 "p" 플래그와 같이 쓰여서 
#+ 오직 일치하는 줄만 출력해 줍니다.



# 다음은 이 스크립트의 저자가 'sed'의 동작에 대해서 설명한 내용입니다.

# head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
# ----------------------------------> |

# "sed"로 들어가는 출력을   --------> |
# 0000000 1198195154\n 라고 가정하면

# sed가 0000000 1198195154\n 을 읽으면,
# 뉴라인(\n) 문자를 발견하기 때문에 첫 번째 줄(0000000 1198195154)에 
# 대해서 처리할 준비가 됩니다.
# 그 다음에는 자신의 <주소 범위><동작> 을 참고하는데 여기서는

#   주소 범위  동작
#   1          s/.* //p

# 가 됩니다.

# 줄 번호가 주소 범위안에 들어가기 때문에 동작을 수행합니다:
# 그 줄에서 공백문자로 끝나는 가장 긴 문자열("0000000 ")을 
# 아무것도 아닌 것으로(//) 치환시키고, 성공한다면 그 결과를 출력해 줍니다
# (여기에서, "p"는 "s" 명령어에 대한 플래그지 "p" 명령어가 아닙니다).

# sed 는 이제 다음 입력을 받을 준비를 합니다(여기서 주의할 점은,
# 만약에 -n 옵션이 없다면, 그 줄을 한 번 더 출력할 것이라는 것입니다).

# 그 다음에는 나머지 문자들을 읽어 들이는데, 파일의 끝을 발견하게 됩니다.
# 이제 마지막 줄임을 나타내는 '$'가 매겨지는 두 번째 줄을 처리할 준비가 된거죠.
# 그런데 두 번째 줄은 어떤 <범위> 에도 들지 않기 때문에 동작을 멈춥니다.

# 이 동작들을 간단하게 설명해 보면 다음과 같습니다:
# "첫번째 줄만 읽은 다음 제일 오른쪽에 나오는 빈 칸까지 삭제한 다음 출력"

# 이렇게 하면 더 좋을 수도 있습니다:
#           sed -e 's/.* //;q'

# 두 개의 <주소 범위><동작> 으로 쓰일 수도 있습니다.
#           sed -e 's/.* //' -e q):

#   주소 범위                동작
#   일치하는 줄 없음         s/.* //
#   일치하는 줄 없음         q (quit)

# 이렇게 하면, sed 는 오직 첫 번째 줄만 입력으로 받아 들입니다.
# "-n" 옵션이 없기 때문에 종료하기("q" 명령어) 전에 바뀐 줄을 출력해 줍니다.

# =================================================================== #

# 위의 한 줄짜리 스크립트는 더 간단하게 쓸 수도 있습니다:
#           head -c4 /dev/urandom| od -An -tu4

exit 0
예 12-27 참고.

tail

파일의 마지막 부분을 표준출력으로 보여 줍니다(기본적으로10줄). 보통, 시스템 로그 파일의 변경 사항을 추적할 때 쓰는데, 파일 뒷부분에 계속 덧붙여지는 사항을 볼 수 있게 해 주는 -f 옵션을 주면 됩니다.

예 12-11. tail로 시스템 로그를 모니터하기

#!/bin/bash

filename=sys.log

cat /dev/null > $filename; echo "깨끗한 파일 생성."
# 존재하지 않는 파일이라면 새로 만들고,
# 길이를 0 으로 만듦.
# : > filename   이라고 해도 됩니다.

tail /var/log/messages > $filename  
# 이 명령이 제대로 동작하려면 /var/log/messages 에 아무나 읽을 수 있는 
# 퍼미션이 걸려 있어야 됩니다.

echo "$filename 은 시스템 로그의 마지막 부분을 보여줍니다."

exit 0

예 12-4, 예 12-27, 예 30-5 참고.

grep

정규 표현식을 쓰는 다목적 파일 검색 도구로서, 원래 예전의 라인 에디터인 ed의 명령어나 필터였던 g/re/p에서 따온 것으로 global - regular expresstion - print란 뜻입니다.

grep pattern [file...]

대상 파일에서 보통 텍스트이거나 정규 표현식인 pattern을 찾아 줍니다.

bash$ grep '[rst]ystem.$' osinfo.txt
The GPL governs the distribution of the Linux operating system.
	      

대상 파일이 주어지지 않는다면 파이프에서 쓰여서 다른 명령어의 표준출력에 대한 필터로 동작합니다.

bash$ ps ax | grep clock
765 tty1     S      0:00 xclock
901 pts/1    S      0:00 grep clock
	      

-i 옵션은 대소문자 구분 없이 찾도록 해줍니다.

-l 옵션은 일치하는 줄이 아니라 일치하는 줄이 들어 있는 파일만 보여줍니다.

-n 옵션은 일치하는 줄과 그 줄번호를 같이 보여 줍니다.

bash$ grep -n Linux osinfo.txt
2:This is a file containing information about Linux.
6:The GPL governs the distribution of the Linux operating system.
	      

-v (혹은 --invert-match) 옵션은 일치하는 패턴을 걸러내 줍니다.
grep pattern1 *.txt | grep -v pattern2

# "*.txt"에서 "pattern2"는 제외하고 "pattern1"을 포함하는 모든 줄.

-c (--count) 옵션은 일치하는 패턴을 보여주지 않고 일치한 횟수만 보여줍니다.
grep -c txt *.sgml   # ("*.sgml"에서 "txt"가 나오는 횟수)


#   grep -cz .
#            ^ 점이 있어요.
# "."과 일치하고 0으로 구분된(-z) 아이템 갯수(-c)
# 즉, 최소한 1 글자 이상을 포함하는 아이템.
# 
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz .     # 4
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '$'   # 5
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '^'   # 5
#
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -c '$'    # 9
# 기본적으로 뉴라인 문자(\n)가 아이템을 구분해 줍니다.

# -z 옵션은 GNU "grep"에서만 쓰이는 옵션임에 주의하세요.


# Thanks, S.C.

대상 파일을 하나 이상 적어주면 일치하는 파일도 같이 보여 줍니다.

bash$ grep Linux osinfo.txt misc.txt
osinfo.txt:This is a file containing information about Linux.
osinfo.txt:The GPL governs the distribution of the Linux operating system.
misc.txt:The Linux operating system is steadily gaining in popularity.
	      

작은 정보: 오직 하나의 파일에서만 찾으려고 할 때 grep이 강제로 파일이름을 보여주게 하고 싶으면 두 번째 파일로 /dev/null을 주면 됩니다.

bash$ grep Linux osinfo.txt /dev/null
osinfo.txt:This is a file containing information about Linux.
 osinfo.txt:The GPL governs the distribution of the Linux operating system.
	      

grep은 일치하는 패턴을 찾으면 종료 상태 0을 리턴하는데 출력을 안 하게 해 주는 -q 옵션과 같이 스크립트의 테스트 문에서 쓰면 유용하게 쓸 수 있습니다.
SUCCESS=0                      # grep 검색이 성공하면
word=Linux
filename=data.file

grep -q "$word" "$filename"    # "-q" 옵션은 표준출력으로 아무것도 에코하지
않습니다.

if [ $? -eq $SUCCESS ]
then
  echo "$filename 에서 $word 발견"
else
  echo "$filename 에서 $word 발견 못 함"
fi

예 30-5 은 시스템 로그 파일에서 grep으로 특정 낱말 패턴을 찾는 것을 보여줍니다.

예 12-12. 스크립트에서 "grep"을 에뮬레이트 하기

#!/bin/bash
# grp.sh: 'grep'을 아주 조잡하게 다시 구현.

E_BADARGS=65

if [ -z "$1" ]    # 인자 확인.
then
  echo "사용법: `basename $0` pattern"
  exit $E_BADARGS
fi  

echo

for file in *     # $PWD 의 모든 파일을 탐색.
do
  output=$(sed -n /"$1"/p $file)  # 명령어 치환.

  if [ ! -z "$output" ]           # "$output" 을 쿼우트 안 하면 어떻게 될까요?
  then
    echo -n "$file: "
    echo $output
  fi              #  sed -ne "/$1/s|^|${file}: |p"  라고 해도 똑같습니다.

  echo
done  

echo

exit 0

# 독자용 연습문제:
# ---------------
# 1) 주어진 파일에서 하나 이상 일치한다면 출력에 뉴라인을 추가해 보세요.
# 2) 여러가지 특징들을 추가해 보세요.

참고: egrepgrep -E와 같습니다. 좀 더 유연한 검색 능력을 갖는 확장 정규 표현식 셋을 사용합니다.

fgrepgrep -F와 같습니다. 문자 그대로의 검색(정규 표현식 안 씀)만 하기 때문에 속도가 약간 빠릅니다.

agrepgrep이 유사(approximate) 매칭을 할 수 있게 확장해 줍니다. 찾을 문자열과 찾은 문자열은 주어진 숫자 만큼의 문자가 다를 수도 있습니다. 이 유틸리티는 리눅스 배포판에서 기본으로 포함되지 않습니다.

작은 정보: 압축된 파일에서 검색을 하려면 zgrep, zegrep, zfgrep을 쓰세요. 압축 안 된 파일에 대해서도 동작하지만 grep, egrep, fgrep 보다는 약간 느립니다. 압축 파일과 비압축 파일이 섞여 있을 때 사용하면 편리합니다.

bzip으로 압축된 파일에서 검색을 하려면 bzgrep 을 쓰세요.

look

lookgrep과 비슷하게 동작하지만 정렬된 낱말 목록인 "사전"에 들어 있는 낱말에 대해서만 찾습니다. 따로 지정하지 않으면 /usr/dict/words 에 들어 있는 낱말만 찾는데 다른 사전 파일을 지정해 줄 수도 있습니다.

예 12-13. 목록에 들어 있는 낱말들의 유효성 확인하기

#!/bin/bash
# lookup: 데이타 파일에 들어 있는 모든 낱말들에 대해서 사전 검색을 수행.

file=words.data  # 테스트용 낱말들이 들어 있는 데이타 파일.

echo

while [ "$word" != end ]  # 데이타 파일의 마지막 낱말.
do
  read word      # 루프의 끝에서 재지향을 걸었기 때문에 데이타 파일에서 읽음.
  look $word > /dev/null  # 사전 파일의 결과를 표시하지 않음.
  lookup=$?      # 'look' 명령어의 종료 상태.

  if [ "$lookup" -eq 0 ]
  then
    echo "\"$word\" 는 유효함."
  else
    echo "\"$word\" 는 유효하지 않음."
  fi  

done <"$file"    # 표준입력을 $file로 재지향 하기 때문에 $file에서 "읽음".

echo

exit 0

# ----------------------------------------------------------------
# 위의 "exit" 명령어 때문에 다음 코드는 실행되지 않습니다.


# Stephane Chazelas 가 더 간결한 다음 코드를 제안해 주었습니다:

while read word && [[ $word != end ]]
do if look "$word" > /dev/null
   then echo "\"$word\" 는 유효함."
   else echo "\"$word\" 는 유효하지 않음."
   fi
done <"$file"

exit 0
sed, awk

텍스트 파일이나 명령어 출력을 파싱하는데 특히 알맞은 스크립트 언어입니다. 홀로 쓰일 수도 있고 파이프 중간이나 쉘 스크립트에서 쓰일 수도 있습니다.

sed

많은 ex 명령어들을 배치 모드에서 쓸 수 있게 해주는 비대화형(non-interactive) "스트림 에디터"입니다. 쉘 스크립트에서 아주 자주 쓰입니다.

awk

프로그램 가능한 파일 분석및 형식화 명령어로서, 구조화된 텍스트 파일의 필드나 컬럼을 뽑아내고 조작하는데 아주 적당하며, 문법은 C와 비슷합니다.

wc

wc는 파일이나 I/O 스트림에 나타나는 "낱말 갯수"(word count)를 알려줍니다:
bash $ wc /usr/doc/sed-3.02/README
20     127     838 /usr/doc/sed-3.02/README
[20 lines  127 words  838 characters]

wc -w 는 낱말 갯수만 알려줍니다.

wc -l 은 줄 수만 알려줍니다.

wc -c 는 글자 수만 알려줍니다.

wc -L 을 가장 긴 줄의 길이만 알려줍니다.

wc 로 현재 디렉토리에 .txt 파일이 몇 개 있는지 알아내기:
$ ls *.txt | wc -l
# "*.txt" 중, 파일명에 라인피드가 들어 있지 않는 한, 잘 동작합니다.

# 다른 방법:
#      find . -maxdepth 1 -name \*.txt -print0 | grep -cz .
#      (shopt -s nullglob; set -- *.txt; echo $#)

# Thanks, S.C.

wc 로 d 에서 h 사이의 문자로 시작되는 파일들 크기의 전체 합을 구하기.
bash$ wc [d-h]* | grep total | awk '{print $3}'
71832
	      

wc 로 이 책의 메인 소스 파일에서 "Linux"가 몇 번이나 나오는지 알아보기.
bash$ grep Linux abs-book.sgml | wc -l
50
	      

예 12-27예 16-5 도 참고.

몇몇 명령어는 자신의 옵션으로 wc의 일부 기능을 갖고 있기도 합니다.
... | grep foo | wc -l
# 자주 쓰던거죠? 하지만 좀 더 간단하게 쓸 수 있습니다.

... | grep -c foo
# grep의 "-c"(나 "--count") 옵션을 쓰세요.

# Thanks, S.C.

tr

문자 변환 필터.

경고

적절하게 쿼우팅이나 대괄호로 묶어줘야 합니다. 쿼우팅은 tr 명령어에서 쓰이는 특수한 문자들이 쉘에 의해 재해석 되지 않도록 막아줍니다. 대괄호는 쉘이 확장을 못 하도록 쿼우트 되어야 합니다.

tr "A-Z" "*" <filename 이나 tr A-Z \* <filenamefilename에 들어 있는 모든 대문자를 별표로 변환해서 표준출력으로 내 보냅니다. 몇몇 시스템에서는 이렇게 하면 안 되고 tr A-Z '[**]' 이라고 해야 제대로 동작할 수도 있습니다.

-d 옵션은 지정된 범위에 해당하는 문자들을 지워 줍니다.
tr -d 0-9 <filename
# "filename" 에 들어 있는 모든 숫자를 지워 줍니다.

--squeeze-repeats(나 -s) 옵션은 연속적인 문자들 중에서 첫번째만 남기고 나머지 문자들은 지워 줍니다. 이 옵션은 과도한 공백 문자를 지울 때 유용합니다.
bash$ echo "XXXXX" | tr --squeeze-repeats 'X'
X

예 12-14. toupper: 파일 내용을 모두 대문자로 바꿈.

#!/bin/bash
# 파일 내용을 모두 대문자로 바꿈.

E_BADARGS=65

if [ -z "$1" ]  # 명령어줄 인자 여부의 표준 확인 작업.
then
  echo "사용법: `basename $0` filename"
  exit $E_BADARGS
fi  

tr a-z A-Z <"$1"

# 위와 같지만 POSIX 문자셋 표기법을 쓰는 방법:
#        tr '[:lower:]' '[:upper:]' <"$1"
# Thanks, S.C.

exit 0

예 12-15. lowercase: 현재 디렉토리의 모든 파일명을 소문자로 바꿈.

#! /bin/bash
#
# 현재 디렉토리의 모든 파일 이름을 다 소문자로 바꿈
#
# 원래 John Dubois의 스크립트를 Chet Ramey가 bash용으로 수정한 것에서 
# 영감을 얻어 본 문서의 저자인 Mendel Cooper가 상당히 간단하게 수정했음.


for filename in *                # 현재 디렉토리의 모든 파일을 탐색.
do
   fname=`basename $filename`
   n=`echo $fname | tr A-Z a-z`  # 이름을 소문자로 바꾸고,
   if [ "$fname" != "$n" ]       # 원래 소문자가 아닌 파일만 소문자로 바꿈.
   then
     mv $fname $n
   fi  
done   

exit 0


# "exit" 때문에 다음 코드는 실행되지 않습니다.
#--------------------------------------------#
# 이 부분을 실행시키려면 위 스크립트를 모두 지우세요.

# 위 스크립트는 파일이름에 공백이나 뉴라인이 들어 있을 때에는 제대로 동작하지 않습니다.

# 그래서 Stephane Chazelas 가 다음 스크립트를 제안해 주었습니다.


for filename in *    # "*"는 "/"가 들어 있는 파일은 리턴하지 않기 때문에
                     # basename 을 쓸 필요가 없습니다.
do n=`echo "$filename/" | tr '[:upper:]' '[:lower:]'`
#                             POSIX 문자셋 표기법.
#                    명령어 치환을 해도 꼬리부분(trailing)의 뉴라인이 
#                    지워지지 않기 때문에 / 를 붙였습니다.
   # 변수 치환:
   n=${n%/}          # 파일이름에서 아까 붙인 / 를 제거.
   [[ $filename == $n ]] || mv "$filename" "$n"
                     # 파일이름이 이미 소문자인지 확인.
done

exit 0

예 12-16. du: 도스용 텍스트 파일을 UNIX용으로 변환.

#!/bin/bash
# du.sh: DOS 텍스트 파일을 UNIX 텍스트 파일로 변환.

E_WRONGARGS=65

if [ -z "$1" ]
then
  echo "사용법: `basename $0` filename-to-convert"
  exit $E_WRONGARGS
fi

NEWFILENAME=$1.unx

CR='\015'  # 캐리지 리턴.
# DOS 텍스트 파일에서는 줄 끝에 CR-LF 가 붙습니다.

tr -d $CR < $1 > $NEWFILENAME
# CR 을 지우고 새 파일로 쓰기.

echo "원래 DOS 텍스트 파일은 \"$1\" 이고,"
echo "변환된 UNIX 텍스트 파일은 \"$NEWFILENAME\" 입니다."

exit 0

예 12-17. rot13: 초허접(ultra-weak) 암호화, rot13.

#!/bin/bash
# rot13.sh: 아주 유치한 고전적인 rot13 알고리즘

# 사용법: ./rot13.sh filename
# or      ./rot13.sh <filename
# or      ./rot13.sh 라고 한 다음 키보드에서 입력(stdin)

cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M'   # "a"는 "n"이 되고, "b"는 "o"가 되는등..
# 'cat "$@"' 라고 하면 표준입력이나 파일에서 입력을 받을 수 있게 해 줍니다.

exit 0

예 12-18. "Crypto-Quote" 퍼즐 만들기

#!/bin/bash
# crypto-quote.sh: 인용문을 암호화

# 이 스크립트는 유명한 인용문을 간단한 1:1 알파벳 치환을 통해 암호화 시켜줍니다.
#  결과는 일요 신문의 Op Ed(옮긴이: Opposite Editorial, 신문의 서명 기사나 
#+ 논평이 실리는 페이지)에서 볼 수 있는 "Crypto Quote" 퍼즐과 비슷합니다.


key=ETAOINSHRDLUBCFGJMQPVWZYXK
# "key" 는 단순히 알파벳을 뒤섞어 놓은 것입니다.
# 이 "key"를 바꾸면 암호화가 바뀌게 됩니다.

# 'cat "$@"' 는 표준입력이나 파일에서 입력을 받아 들입니다.
# 표준입력에서 입력을 받는 다면 Control-D 로 끝내면 되고,
# 파일이라면 명령어줄 매개변수로 파일이름을 지정해 주면 됩니다.

cat "$@" | tr "a-z" "A-Z" | tr "A-Z" "$key"
#        |  대문자로      |     암호화       
# 소문자, 대문자, 대소문자가 섞인 인용문에 대해서 동작합니다.
# 알파벳이 아닌 문자들은 그대로 둡니다.


# 이 스크립트에 이런 입력을 넣는다면,
# "Nothing so needs reforming as other people's habits."
# --Mark Twain
#
# 결과는 다음과 같을 겁니다:
# "CFPHRCS QF CIIOQ MINFMBRCS EQ FPHIM GIFGUI'Q HETRPQ."
# --BEML PZERC

# 복호화를 하려면 이렇게 하면 됩니다:
# cat "$@" | tr "$key" "A-Z"


#  이 간단한 암호화는 연필과 종이를 가지고 
#+ 평균 12 년 정도 계산하면 금방 깨집니다.

exit 0
fold

입력 줄을 주어진 넓이로 접어주는(wrap) 필터. 특별히 유용한 -s 옵션을 쓰면 낱말 사이의 빈 칸에서 줄을 나눠줍니다(예 12-19예 A-2 참고).

fmt

간단한 파일 형식화 명령어로 파이프 중간에 필터로 쓰여 긴 줄을 "접기"(wrap) 위해 쓰입니다.

예 12-19. 파일 목록 형식화.

#!/bin/bash

WIDTH=40                    # 넓이는 40 행.

b=`ls /usr/local/bin`       # 파일 목록을 얻은 다음...

echo $b | fmt -w $WIDTH

#  echo $b | fold - -s -w $WIDTH
#  라고 해도 됩니다.
 
exit 0

예 12-4 참고.

작은 정보: fmt의 강력한 대체품인 Kamil Toman의 par 유틸리티는 http://www.cs.berkeley.edu/ ~amc/Par/ 에서 구할 수 있습니다.

ptx

ptx [targetfile] 명령어는 targetfile의 permuted index(상호 참조 리스트 - cross-reference list)를 출력해 줍니다. 필요하다면 여기서 나온 결과를 나중에 필터링이나 형식화해서 쓸 수도 있습니다.

column

컬럼 형식화 명령어. 목록 형태의 텍스트 출력의 적당한 곳에 탭을 넣어서 "예쁜 출력" 테이블을 얻게 해 주는 필터입니다.

예 12-20. column 으로 디렉토리 목록을 형식화 하기

#!/bin/bash
# "column" 맨 페이지에 있는 에제를 약간 수정했습니다.


(printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" \
; ls -l | sed 1d) | column -t

# "sed 1d" 는 "total        N" 이라고 나오는 첫번째 줄을 지워줍니다.
# 여기서 "N"은 "ls -l"이라고 했을 때의 전체 파일 수를 나타냅니다. 

# "column"의 -t 는 표를 예쁘게 찍기(pretty-print) 옵션입니다.

exit 0
nl

줄 번호 매기기 필터. nl filename 이라고 하면 filename의 빈 칸을 제외한 각 줄에 연속적인 번호를 붙여서 표준출력으로 보여 줍니다. filename을 지정해 주지 않으면 표준입력에 대해서 동작합니다.

예 12-21. nl: 자기 자신에게 번호를 붙이는 스크립트.

#!/bin/bash

# 이 스크립트는 이 스크립트 파일에 줄 번호를 붙여서 표준출력으로 두 번씩 출력해 줍니다.

# 'nl' 명령어는 빈 줄을 세지 않기 때문에 지금 이 줄을 3 번째 줄로 봅니다.
# 'cat -n' 은 바로 윗줄을 5 번째 줄로 봅니다.

nl `basename $0`

echo; echo  # 이제 'cat -n'으로 해 볼까요.

cat -n `basename $0`
# 'cat -n' 이 다른 점은 빈 줄에도 번호를 붙인다는 겁니다.
# 주의할 점은 'nl -ba' 라고 하면 'cat -n' 과 똑같이 동작한다는 것입니다.

exit 0
pr

출력 형식화 필터. 파일이나 표준출력을 프린터로 찍거나 출판용이나 스크린으로 보기 좋은 섹션 형태로 페이지를 매겨 줍니다. 행과 열을 조작하기, 여러 줄을 합치기, 마진을 넣어 주기, 줄에 번호를 매겨 주기, 페이지 헤더를 붙여 주기, 여러 파일을 합치기등을 비롯한 다양한 옵션이 가능합니다. prnl, paste, fold, column, expand의 기능을 합친 것보다 더한 능력을 가진 명령어입니다.

pr -o 5 --width=65 fileZZZ | more 라고 하면 fileZZZ를 마진을 5로 하고 전체 폭을 65로 하고 멋있게 페이지를 매겨서 스크린에 뿌려 줍니다.

특별히 유용한 옵션인 -d는 한 줄마다 강제로 빈 줄을 넣어줍니다(double-spacing, sed -G와 같습니다).

gettext

프로그램의 출력을 다른 언어로 번역해서 보여주는 GNU 지역화(localization) 유틸리티입니다. 처음엔 C 프로그램을 위해서 쓰였지만 쉘 스크립트에서도 쓰입니다. info page를 참고하기 바랍니다.

iconv

주로 지역화에서 쓰이는 명령어로 파일을 다른 인코딩(문자셋)으로 변환해 줍니다.

recode

이 명령어는 iconv의 개선판이라고 보면 됩니다. 파일을 다른 인코딩으로 변환해주는 이 다목적 유틸리티는 표준 리눅스 설치시에는 포함되지 않습니다.

groff, gs, TeX

Groff, TeX, 포스트스크립트(postscript)는 출판용 원고나 형식화된 비디오 디스플레이용 텍스트 마크업 언어(text markup language)들입니다.

맨 페이지groff을 씁니다(예 A-1 참고). 고스트스크립트(ghostscript, gs)는 포스트스크립트 해석기의 GPL 버전입니다. TeX는 Donald Knuth의 정교한 조판 시스템입니다. 흔히, 이 마크업 언어들에 넘길 인자나 옵션들을 쉘 스크립트로 처리를 해서 편하게 씁니다.

lex, yacc

구문 분석기(lexical analyzer)인 lex는 패턴 매칭을 위한 프로그램을 만들어 냅니다. 리눅스 시스템에서는 이 명령어의 비특허 버전인 flex로 바뀌었습니다.

yacc 유틸리티는 스펙셋에 의거한 파서를 만들어 냅니다. 리눅스 시스템에서는 이 명령어의 비특허 버전인 bison으로 바뀌었습니다.