Monthly Archives: December 2009

More explanation of “Find” command

Here are some of the horror stories:

30 Maroo 07.01.09 at 4:46 pm
I issued the following command on a BackOffice Trading Box in an attempt to clean out a user’s directory. But issued it in the /local. The command ended up taking out the Application mounted SAN directory and the /local directory.

find . -name “foo.log*” -exec ls -l {} \; | cut -f2 -d “/” | while read NAME; do gzip -c $NAME > $NAME.gz; rm -r $NAME;

done

Took out the server for an entire day.

Ville 07.14.09 at 12:17 am
I run a periodic (daily) script on a BSD system to clean out a temp directory for joe (the editor). Anything older than a day gets wiped out. For some historical reason the temp directory sits in /usr/joe-cache rather than in, for instance, /usr/local/joe-cache or /var/joe-cache or /tmp/joe-cache. The first version of the line in the script that does the deleting looked like this:find /usr/joe-cache/.* -maxdepth 1 -mtime +1 -exec rm {} \;

Good thing the only files in /usr were two symlinks that were neither mission critical nor difficult to recreate as the above also matches “/usr/edit-cache/..” In the above the rather extraneous (joe doesn’t save backup files in sub-directories) “-maxdepth 1″ saved the entire /usr from being wiped out!

The revised version:

find -E /usr/joe-cache/ -regex '/usr/joe-cache/\.?.+$' -maxdepth 1 -mtime +1 -exec rm {} \;

.. which matches files beginning with a dot within “/usr/joe-cache”, but won’t match “/usr/joe-cache/..”

Lesson learned: always test find statements with “-print” before adding “-exec rm {} \;”.

Find is able to execute one or more commands for each file it has found with the -exec option. Unfortunately, one cannot simply enter the command. You need to remember two syntactic tricks:

  1. The command that you want to execute need to contain a special macro argument {}, which will be replaced by the matched filename on each invocation of -exec predicate.
  2. You need to specify \; (or ‘;’ ) at the end of the command. (If the \ is left out, the shell will interpret the ; as the end of the find command.) . For example, the following two commands are eqivalent:

    find . -name “*rc.conf”  -exec chmod o+r ‘{}’ \;

    find . -name “*rc.conf” -exec chmod o+r ‘{} ;’

    In case  {} macro parameter is the last item in the command then it should be a space between the {} and the \;. For example:

    <span style="font-family: Fixedsys; color: #0000ff;">find . -type d -exec ls -ld {} \;</span>

Here are several “global” chmod tricks based on find -exec capabilities (remember that if you change attributes or delete files you need first to test the command using print before running it with the -exec option):

Test command

  • <span style="font-family: Fixedsys; color: #0000ff;">find . -type f -ls</span>

Final command

  • <span style="font-family: Fixedsys; color: #0000ff;">find . -type f -exec chmod 500 {} ';'</span>

The command bellow search in the current directory and all sub directories and change permissions of each file as specified. Here an additional danger is connected with being in a wring directory.

Test command

  • <span style="font-family: Fixedsys; color: #0000ff;">find . -name "*rc.conf"  -ls</span>

Final command

  • <span style="font-family: Fixedsys; color: #0000ff;">find . -name "*rc.conf"  -exec chmod o+r '{}' \; </span>

This command will search in the current directory and all sub directories. All files named *rc.conf will be processed by the chmod -o+r command. The argument ‘{}’ is a macro that expands to each found file. The \; argument indicates the exec argument has ended.

The end results of this command is all *rc.conf files have the other permissions set to read access (if the operator is the owner of the file).

Note: The -print option will print out the path of any file that is found with that name. In general -print is a default option.

The find command is commonly used to remove core files that are more than a few 24-hour periods (days) old. These core files are copies of the actual memory image of a running program when the program dies unexpectedly. They can be huge, so occasionally trimming them is wise:

Test command

  • <span style="color: #0000ff;">find . -name core -ctime +4 -</span>ls

Final command

  • <span style="color: #0000ff;">find . -name core -ctime +4 -exec /bin/rm -f {} \;</span>

For grep the /dev/null argument can by used to show the name of the file before the text that is found. Without it, only the text found is printed. An equivalent mechanism in GNU find is to use the “-H” or “–with-filename” option to grep:

find /tmp -exec grep “search string” ‘{}’ /dev/null \; -print

An alternative to -exec option is piping output into xargs command which we will discuss in the next section.

Feeding find output to pipes with xargs

One of the biggest limitations of the -exec option (or predicate with the side effect to be more correct) is that it can only run the specified command on one file at a time. The xargs command solves this problem by enabling users to run a single command on many files at one time. In general, it is much faster to run one command on many files, because this cuts down on the number of invocations of particular command/utility.

For example often one needs to find files containing a specific pattern in multiple directories one can use an exec option in find (please note that you should use the -l flag for grep so that grep specifies the matched filenames):

<span style="color: #0000ff;">find . -type f -exec grep -li '/bin/ksh' {} \;</span>

But there is more elegant and more Unix-like way of accomplishing the same task using xarg and pipes. You can use the xargs to read the output of find and build a pipelines that invokes grep. This way, grep is called only four or five times even though it might check through 200 or 300 files. By default, xargs always appends the list of filenames to the end of the specified command, so using it is as easy as can be:

<span style="color: #0000ff;">find . -type f -print | xargs grep -li 'bin/ksh'</span>

This gave the same output, but it was a lot faster. Also when grep is getting multiple filenames, it will automatically include the filename of any file that contains a match so option for grep -l is redundant:

<span style="color: #0000ff;">find . -type f -print | xargs grep -i 'bin/ksh'</span>

When used in combination, find, grep, and xargs are a potent team to help find files lost or misplaced anywhere in the UNIX file system. I encourage you to experiment further with these important commands to find ways they can help you work with UNIX. You can use time to find the difference in speed with -exec option vs xarg in the following way:

<span style="color: #0000ff;">time find /usr/src -name "*.html" -exec grep -l foo '{}' ';' | wc -l

time find /usr/src -name "*.html" | xargs grep -l foo | wc -l</span>

xargs works considerably faster. The difference becomes even greater when more complex commands are run and the list of files is longer.

<span style="color: #0000ff;">find /mnt/zip -name "*prefs copy" -print | xargs rm</span>

This is actually dangerous if you have a filename with spaces. If you add option -print0, you can avoid this danger:

<span style="color: #0000ff;">find /mnt/zip -name "*prefs copy" -print0 | xargs rm</span>

Two other useful options for xargs are the -p option, which makes xargs interactive, and the -n args option, which makes xargs run the specified command with only args number of arguments.

Some people wonder why there is a -p option. xargs runs the specified command on the filenames from its standard input, so interactive commands such as cp -i, mv -i, and rm -i don’t work right.

The -p option solves that problem. In the preceding example, the -p option would have made the command safe because I could answer yes or no to each file. Thus, the command I typed was the following:

find /mnt/zip -name "*prefs copy" -print0 | xargs -p rm

Many users frequently ask why xargs should be used when shell command substitution archives the same results. Take a look at this example:

grep -l foo ´find /usr/src/linux -name "*.html"´

The drawback with commands such as this is that if the set of files returned by find is longer than the system’s command-line length limit, the command will fail. The xargs approach gets around this problem because xargs runs the command as many times as is required, instead of just once.

“Find” command

명령어 목록

find
-exec COMMAND \;

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

<tt>bash$ </tt><tt><strong>find ~/ -name '*.txt'</strong></tt>
<tt>/home/bozo/.kde/share/apps/karm/karmdata.txt
/home/bozo/misc/irmeyc.txt
/home/bozo/test-scripts/1.txt</tt>
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 /[\+\{\;\"\\\=\?~\(\)\&lt;\&gt;\&amp;\*\|\$]/p`
# 이런 고약한 글자를 포함하는 파일들: + { ; " \ = ? ~ ( ) &lt; &gt; &amp; * | $
rm $badname 2&gt;/dev/null    # 에러 메세지는 무시.
done

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

exit 0

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

# 위 스크립트의 다른 방법:
find . -name '*[+{;"\\=?~()&lt;&gt;&amp;*|$ ]*' -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 ) &gt;&gt;logfile
# 시간과 머신 이름
echo --------------------------------------------------------------------- &gt;&gt;logfile
tail -$LINES /var/log/messages | xargs |  fmt -s &gt;&gt;logfile
echo &gt;&gt;logfile
echo &gt;&gt;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 \&gt; 10`
echo 'b=`expr $a \&gt; 10`, 즉...'
echo "If a &gt; 10, b = 0 (거짓)"
echo "b = $b"            # 0  ( 3 ! -gt 10 )
echo

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

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

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절을 참고하세요.