12.2. 복잡한 명령어

명령어 목록

find

-exec COMMAND \;

find가 찾아낸 각각의 파일에 대해 COMMAND를 실행합니다. COMMAND\;으로 끝나야 합니다(find로 넘어가는 명령어의 끝을 나타내는 ;를 쉘이 해석하지 않도록 이스케이프 시켜야 합니다). COMMAND{}이 포함되어 있으면 선택된 파일을 완전한 경로명으로 바꿔 줍니다.

bash$ find ~/ -name '*.txt'
/home/bozo/.kde/share/apps/karm/karmdata.txt
/home/bozo/misc/irmeyc.txt
/home/bozo/test-scripts/1.txt
	      

find /home/bozo/projects -mtime 1
# /home/bozo/projects 디렉토리안에 있는 파일중에서 
# 하루 전에 변경된 파일들을 모두 보여 줍니다.

find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;

# /etc 디렉토리에 들어 있는 파일들에 포함된 
# 모든 IP 주소(xxx.xxx.xxx.xxx)를 찾아줍니다.
# IP 가 아닌 것도 나오는데 이것들을 어떻게 걸러낼 수 있을까요?

# 이건 어때요?

find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
 | grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'

# Thanks, S.C. 

경고

find에서 쓰이는 -exec 옵션을 쉘 내장 명령인 exec과 헷갈리면 안 됩니다.

예 12-2. Badname, 파일 이름에 일반적이지 않은 문자나 공백 문자를 포함하는 파일을 지우기.

#!/bin/bash

# 현재 디렉토리에 들어 있는 파일중, 이름에 정상적이지 않은 글자가 포함된 파일을 지우기

for filename in *
do
badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
# 이런 고약한 글자를 포함하는 파일들: + { ; " \ = ? ~ ( ) < > & * | $
rm $badname 2>/dev/null    # 에러 메세지는 무시.
done

# 다음은 모든 종류의 공백 문자를 포함하는 파일들을 처리하겠습니다.
find . -name "* *" -exec rm -f {} \;
# "find"가 찾은 파일이름이 "{}"로 바뀝니다.
# '\'를 써서 ';'가 명령어 끝을 나타낸다는 원래의 의미로 해석되게 합니다.

exit 0

#---------------------------------------------------------------------
# 다음의 명령어들은 위에서 "exit"를 했기 때문에 실행되지 않습니다.

# 위 스크립트의 다른 방법:
find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;
exit 0
# (Thanks, S.C.)

예 12-3. inode 로 파일을 지우기

#!/bin/bash
# idelete.sh: inode 로 파일을 지우기.

#  이 스크립트는 파일이름이 ? 나 - 처럼 부적절한 문자로 시작될 때 유용합니다.

ARGCOUNT=1                      # 인자로 파일이름이 필요함.
E_WRONGARGS=70
E_FILE_NOT_EXIST=71
E_CHANGED_MIND=72

if [ $# -ne "$ARGCOUNT" ]
then
  echo "사용법: `basename $0` filename"
  exit $E_WRONGARGS
fi  

if [ ! -e "$1" ]
then
  echo "\""$1"\" 는 존재하지 않는 파일입니다."
  exit $E_FILE_NOT_EXIST
fi  

inum=`ls -i | grep "$1" | awk '{print $1}'`
# inum = 파일의 inode (index node)
# 모든 파일은 자신의 물리적 주소 정보를 담고 있는 inode를 갖고 있습니다.

echo; echo -n "\"$1\" 를 진짜로 지우실 겁니까(y/n)? "
read answer
case "$answer" in
[nN]) echo "마음을 바꿨군요, 그렇죠?"
      exit $E_CHANGED_MIND
      ;;
*)    echo "\"$1\" 를 지우는 중.";;
esac

find . -inum $inum -exec rm {} \;
echo ""\"$1"\" 가 지워졌습니다!"

exit 0

스크립트에서 find를 사용하는 다음 예제들을 참고하세요. 예 12-22, 예 4-3, 예 10-8. 이 복잡하고 강력한 명령어에 대해서 더 알고 싶으면 맨페이지를 살펴보세요.

xargs

명령어에 인자들을 필터링해서 넘겨 주고 그 명령어를 다시 조합하는 데 쓸 수도 있습니다. xargs는 입력을 필터용으로 작게 조각내서 명령어가 처리하게 해 줍니다. 역따옴표의 강력한 대용품이라고 생각하면 됩니다. 역따옴표를 써서 too many arguments란 에러가 났을 때, xargs로 바꿔 쓰면 성공할 수도 있습니다. 보통은 표준 입력이나 파이프에서 데이터를 읽어 들이지만 파일의 출력에서도 읽을 수 있습니다.

xargs의 기본 명령어는 echo입니다.

ls | xargs -p -l gzip 은 현재 디렉토리의 모든 파일에 대해 한번에 한 파일씩 물어보면서 gzips으로 묶어줍니다.

작은 정보: 재밌는 옵션중 하나인 -n XX을 쓰면 넘길 인자의 갯수를 XX로 제한합니다.

ls | xargs -n 8 echo 는 현재 디렉토리의 파일들을 한 줄에 8 개씩 끊어서 보여줍니다.

작은 정보: 다른 유용한 옵션으로 find -print0grep -lZ와 함께 쓰는 -0이 있습니다. 이 옵션은 공백 문자나 따옴표가 들어간 인자를 처리할 수 있게 해줍니다.

find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f

grep -rliwZ GUI / | xargs -0 rm -f

위의 두 가지 모두 "GUI"를 포함하고 있는 어떤 파일도 지워 줍니다. (Thanks, S.C.)

예 12-4. 시스템 로그 모니터링용 xargs 로그 파일

#!/bin/bash

# 현재 디렉토리에 /var/log/messages 의 끝 부분을 포함하는 로그 파일을 만들기

# 주의: 일반 사용자도 이 스크립트를 쓰게 하려면 
#       루트로 chmod 644 /var/log/messages 라고 해서
#       누구나 /var/log/messages 를 읽을 수 있게 해야 됩니다.

LINES=5

( date; uname -a ) >>logfile
# 시간과 머신 이름
echo --------------------------------------------------------------------- >>logfile
tail -$LINES /var/log/messages | xargs |  fmt -s >>logfile
echo >>logfile
echo >>logfile

exit 0

예 12-5. copydir. xargs로 현재 디렉토리를 다른 곳으로 복사하기

#!/bin/bash

# 현재 디렉토리의 모든 파일을
# 명령어줄에서 지정한 디렉토리로 복사하기(verbose).

if [ -z "$1" ]   # 인자가 없다면 종료.
then
  echo "사용법: `basename $0` directory-to-copy-to"
  exit 65
fi  

ls . | xargs -i -t cp ./{} $1
# 어떤 파일이름에도 "공백문자"가 들어 있지 않다면
#    cp * $1 
# 이라고 해도 동일합니다.

exit 0
expr

다목적 표현식 평가 명령어: 주어진 연산에 따라 자동으로 계산하거나 평가합니다. 이 때, 인자는 빈칸으로 분리되어야 합니다. 산술, 비교, 문자열, 논리 연산등이 가능합니다.

expr 3 + 5

8 리턴

expr 5 % 3

2 리턴

y=`expr $y + 1`

변수를 증가. let y=y+1 이나 y=$(($y+1)) 과 같음. 이것은 산술 확장 예제입니다.

z=`expr substr $string $position $length`

$string의 $position에서부터 $length만큼의 문자열조각(substring)을 추출해 냄.

예 12-6. expr 쓰기

#!/bin/bash

# 'expr'의 몇가지 사용법 보여주기
# ===============================

echo

# 산술 연산자
# ---- ------

echo "산술 연산자"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"

a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(변수 증가)"

a=`expr 5 % 3`
# 나머지(modulo)
echo
echo "5 mod 3 = $a"

echo
echo

# 논리 연산자
# ---- ------

#  참이면 1, 거짓이면 0 리턴.
#  Bash 관례와 반대입니다.

echo "논리 연산자"
echo

x=24
y=25
b=`expr $x = $y`         # 같은 값인지 확인하기.
echo "b = $b"            # 0  ( $x -ne $y )
echo

a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, 즉...'
echo "If a > 10, b = 0 (거짓)"
echo "b = $b"            # 0  ( 3 ! -gt 10 )
echo

b=`expr $a \< 10`
echo "If a < 10, b = 1 (참)"
echo "b = $b"            # 1  ( 3 -lt 10 )
echo
# 연산자를 이스케이프 시킨것에 주의.

b=`expr $a \<= 3`
echo "If a <= 3, b = 1 (참)"
echo "b = $b"            # 1  ( 3 -le 3 )
# "\>=" 연산자도 있어요(크거나 같음).


echo
echo

# 비교 연산자
# ---- ------

echo "비교 연산자"
echo
a=zipper
echo "a 는 $a"
if [ `expr $a = snap` ]
# 변수 'a'를 강제로 재평가(re-evaluation)
then
   echo "a 는 zipper 가 아님"
fi   

echo
echo



# 문자열 연산자
# ------ ------

echo "문자열 연산자"
echo

a=1234zipper43231
echo "\"$a\" 를 가지고 조작해 보겠습니다."

# length: 문자열 길이
b=`expr length $a`
echo "\"$a\" 의 길이는 $b."

# index: 문자열에서 문자열조각(substring)이 일치하는 첫번째 문자의 위치
b=`expr index $a 23`
echo "\"$a\" 에서 \"2\" 가 첫번째로 나오는 위치는 \"$b\" 입니다."

# substr: 문자열조각 추출, 추출할 시작 위치와 추출할 길이 지정
b=`expr substr $a 2 6`
echo "시작위치는 2이고 길이가 6인 \"$a\" 의 문자열조각은 \"$b\" 입니다."


# 'match' 연산은 정규표현식을 쓰는 'grep'과 비슷합니다.
b=`expr match "$a" '[0-9]*'`
echo \"$a\" 에서 앞쪽에 나오는 숫자의 갯수는 $b 입니다.
b=`expr match "$a" '\([0-9]*\)'`        # 중괄호가 이스케이프된 것에 주의하세요.
echo "\"$a\" 에서 앞쪽에 나오는 숫자는 \"$b\" 입니다."

echo

exit 0

중요: match 대신 : 연산자를 쓸 수 있습니다. 예를 들면, 위의 예제에서 b=`expr $a : [0-9]*`b=`expr match $a [0-9]*` 과 완전히 동일합니다.

#!/bin/bash

echo
echo "\"expr $string :\" 를 쓰는 문자열 연산"
echo "--------------------------------------"
echo

a=1234zipper43231
echo "\"`expr "$a" : '\(.*\)'`\" 를 가지고 문자열 연산을 합니다."
#       이스케이프된 소괄호.
#       정규표현식 파싱.

echo "\"$a\" 의 길이는 `expr "$a" : '.*'` 입니다."   # 문자열 길이

echo "\"$a\" 에서 앞쪽에 나오는 숫자의 갯수는 `expr "$a" : '[0-9]*'` 입니다."

echo "\"$a\" 에서 앞쪽에 나오는 숫자는 `expr "$a" : '\([0-9]*\)'` 입니다."

echo

exit 0

sed가 더 뛰어난 문자열 파싱 능력을 가지고 있기 때문에 스크립트에서 Perl이나 sed의 간단한 "서브루틴"을 쓰는 것이 expr을 쓰는 것 보다 더 좋은 방법입니다.

문자열 연산에 대한 더 자세한 사항은 9.2절을 참고하세요.