フューチャー技術ブログ

今さらながらfindパイセンについてまとめてみた

Photo by Agence Olloweb on Unsplash

はじめに

こんにちは、中本です。シェルスクリプト連載の3日目です。

本記事ではShell Script作成において切っても切れない findコマンド について利用頻度高めのオプションをまとめます。

最後には今回紹介したオプションを全て盛り込んだシェルスクリプトを作成します。

目次

  1. findとは
  2. 様々なfindオプション
    1. -typeオプション:検索対象のファイル種別を指定
  3. おまけ
  4. おわりに

findとは

find」は、場所を指定してファイルやディレクトリを検索するコマンドです。

「ファイル名/ディレクトリ名」「更新日時」「種別」など様々な条件を指定してファイルを検索できます。

様々なfindオプション

-typeオプション

検索する対象のファイル種別を指定するオプション

#検索対象種別「ファイル」
$ find [検索パス]-type f

#検索対象種別「ディレクトリ」
$ find [検索パス]-type d
$ ls -l
total 0
drwxrwxr-x 2 finduser finduser 6 Mar 29 09:00 dir1
drwxrwxr-x 2 finduser finduser 6 Mar 29 09:00 dir2
drwxrwxr-x 2 finduser finduser 6 Mar 29 09:00 dir3
-rwxrwxr-x 1 finduser finduser 0 Mar 29 09:00 file01.tmp
-rwxrwxr-x 1 finduser finduser 0 Mar 29 09:00 file02.tmp
-rwxrwxr-x 1 finduser finduser 0 Mar 29 09:00 file03.tmp

$ find ./
./
./dir1
./dir2
./dir3
./file01.tmp
./file02.tmp
./file03.tmp

$ find ./ -type f
./file01.tmp
./file02.tmp
./file03.tmp

$ find ./ -type d
./
./dir1
./dir2
./dir3

‐nameオプション

検索する対象の検索文字列を指定するオプション
ワイルドカードを使用することで、部分一致のファイルやディレクトリの検索が可能となります。

#検索対象種別「ファイル」
$ find [検索パス]-name "[検索文字列]"
#ワイルドカード無しだと完全一致検索
$ find . -type f -name "file01"
$


$ find . -type f -name "*.tmp"
./file01.tmp
./file02.tmp
./file03.tmp

なお、「-path」も同様に検索文字列を指定するオプション
-name」と異なり、「/」を含む文字列検索が可能です。

$ ls -l ./dir1/
total 0
-rw-r--r-- 1 root root 0 Mar 29 02:24 file01.tmp

#「-name」だと「/」を含むと怒られる
$ find . -type f -name "*dir1/file01.tmp"
"find: warning: Unix filenames usually don't contain slashes (though pathnames do). That means that '-name ‘*dir1/file01.tmp’' will probably evaluate to false all the time on this system. You might find the '-wholename' test more useful, or perhaps '-samefile'. Alternatively, if you are using GNU grep, you could use 'find ... -print0 | grep -FzZ ‘*dir1/file01.tmp’'."

$ find . -type f -path "*dir1/file01.tmp"
./dir1/file01.tmp

-mtime/-mminオプション

ファイルやディレクトリのタイムスタンプから判定して、検索対象期間を指定するオプション


#検索対象日に更新されたファイルディレクトリ
$ find [検索パス] -mtime 日数

#現在~検索対象日の期間にに更新されたファイル、ディレクトリ
$ find [検索パス] -mtime -日数

#検索対象日以前に更新されたファイル、ディレクトリ
$ find [検索パス] -mtime +日数

上記のように「+」や「‐」を付与することで検索対象期間をより特定期間に絞ることが可能となります(※対象期間については後述)。
日数は今日が「0」で、昨日が「1」と換算します。

$ ls -l ./
-rw-r--r-- 1 finduser finduser 0 Mar 25 01:00 test01.txt
-rw-r--r-- 1 finduser finduser 0 Mar 26 01:00 test02.txt
-rw-r--r-- 1 finduser finduser 0 Mar 27 01:00 test03.txt
-rw-r--r-- 1 finduser finduser 0 Mar 28 01:00 test04.txt
-rw-r--r-- 1 finduser finduser 0 Mar 29 01:00 test05.txt


$ find . -mtime +1
./test01.txt
./test02.txt
./test03.txt

$ find . -mtime 1
./test04.txt

$ find . -mtime -1
./test05.txt

また、オプション無しの状態ではコマンド実行時点を起点として、日数計算を行いますが、「-daystart」オプションを付与することで当日24:00を起点として日数計算を行います。
(ジョブ処理等で、コマンド実行時間によって処理にばらつきを生じさせたくない際などに利用推奨)

他にも、「-mtime」ではなく、「‐mmin」も存在し、分単位で指定することも可能です。

-mtime」「-mmin」はfindを利用する上で高頻度で利用されるオプションですが、対象となる期間がややこしいので、以下のように整理してみました。

注意点としては、「-daystart」オプションは日の始まりである0:00ではなく、日の終わりの24:00を起点としていること注意が必要です。
そのため「-mtime -0」は必ず未来日検索となるため、基本的にファイル検索結果は存在しません。

-depthオプション

検索対象ディレクトリの階層を指定するオプション

本オプションを指定しない場合、子ディレクトリ全てに対して検索を実行します。

ファイル数が膨大にあり、検索対象のディレクトリ階層を絞りたい時などに有効です。

#検索する最大深度の階層
$ find [検索パス]-maxdepth [階層]

#検索する最小深度の階層
$ find [検索パス]-mindepth [階層]
$ tree ./find_test1
./find_test1/
| 20210328.tmp
∟ find_test2
| 20210328.tmp
∟ find_test3
∟ 20210328.tmp

#階層指定なし(すべての子ディレクトリ含む)
$ find /work/find_test1-type f -name "*.tmp"
/work/find_test1/find_test2/find_test3/20210328.tmp
/work/find_test1/find_test2/20210328.tmp
/work/find_test1/20210328.tmp

#最大1階層
$ find /work/find_test1 -maxdepth 1 -type f -name "*.tmp"
/work/find_test1/20210328.tmp

#最大2階層
$ find /work/find_test1 -maxdepth 2 -type f -name "*.tmp"
/work/find_test1/find_test2/20210328.tmp
/work/find_test1/20210328.tmp

#最大3階層
$ find /work/find_test1 -maxdepth 3 -type f -name "*.tmp"
/work/find_test1/find_test2/find_test3/20210328.tmp
/work/find_test1/find_test2/20210328.tmp
/work/find_test1/20210328.tmp

#最小1階層(1階層目は検索しない)
$ find /work/find_test1 -mindepth 1 -type f -name "*.tmp"
/work/find_test1/find_test2/find_test3/20210328.tmp
/work/find_test1/find_test2/20210328.tmp
/work/find_test1/20210328.tmp

#最小2階層(1階層、2階層目は検索しない)
$ find /work/find_test1 -mindepth 2 -type f -name "*.tmp"
/work/find_test1/find_test2/find_test3/20210328.tmp
/work/find_test1/find_test2/20210328.tmp

#最小2階層(1階層~3階層目は検索しない)
$ find /work/find_test1 -mindepth 3 -type f -name "*.tmp"
/work/find_test1/find_test2/find_test3/20210328.tmp

‐execオプション

コマンド実行結果を引数として次の処理に引き渡す場合などに利用されます。
同様の動きを持つコマンドとして「xargs」があります。

# findで検索した結果のみをlsに引き渡す
# 「{}」は引数の位置、行末には「\;」が必須
$ find . -type f -name "*.txt" -exec ls -l {} \;
-rw-r--r-- 1 finduser finduser 0 Mar 25 01:00 ./test01.txt
-rw-r--r-- 1 finduser finduser 0 Mar 26 01:00 ./test02.txt
-rw-r--r-- 1 finduser finduser 0 Mar 27 01:00 ./test03.txt
-rw-r--r-- 1 finduser finduser 0 Mar 28 01:00 ./test04.txt
-rw-r--r-- 1 finduser finduser 0 Mar 29 01:00 ./test05.txt

# 「xargs」をパイプで繋ぐことで検索結果を引き渡すことが可能
$ find . -type f -name "*.txt" |xargs ls -l
-rw-r--r-- 1 finduser finduser 0 Mar 25 01:00 ./test01.txt
-rw-r--r-- 1 finduser finduser 0 Mar 26 01:00 ./test02.txt
-rw-r--r-- 1 finduser finduser 0 Mar 27 01:00 ./test03.txt
-rw-r--r-- 1 finduser finduser 0 Mar 28 01:00 ./test04.txt
-rw-r--r-- 1 finduser finduser 0 Mar 29 01:00 ./test05.txt

一見すると、xargs-execも同様の処理結果を返すように見えるのですが、

$ find . -type f -name "*.txt" -exec echo "ファイル名: {}" \;
ファイル名: ./test01.txt
ファイル名: ./test02.txt
ファイル名: ./test03.txt
ファイル名: ./test04.txt
ファイル名: ./test05.txt

$ find . -type f -name "*.txt" |xargs echo "ファイル名: "
ファイル名: ./test01.txt ./test02.txt ./test03.txt ./test04.txt ./test05.txt

上記の挙動で分かるように、以下のように処理に違いがあります。

処理 説明
-execオプション 実行結果を1行ずつ引き渡して処理する
xargsコマンド 実行結果をまとめて引き渡して処理する

したがって、検索したファイルを1ファイル単位で圧縮するなどの処理の場合は、
-execオプションを利用が推奨されます。

while read line

while read lineに関しては、findのオプションではありませんが、findコマンドと相性の良いループ処理です。
検索結果を一行ずつ読み込んで、任意の処理を実行できます。

while_read_line.sh
#!//bin/bash

${FIND_DIR}="[検索パス]"

# パイプで繋いで、「line」という変数に一行ずつ格納する
# 変数は「line」ではなく、任意名でも可
find ${FIND_DIR} -type f -name "*.txt" |while read line
do

echo "ファイル名: $line"
ls -l $line

done

exit
$ bash ./while_read_line.sh
ファイル名: ./test01.txt
-rw-r--r-- 1 finduser finduser 0 Mar 25 01:00 ./test01.txt
ファイル名: ./test02.txt
-rw-r--r-- 1 finduser finduser 0 Mar 26 01:00 ./test02.txt
ファイル名: ./test03.txt
-rw-r--r-- 1 finduser finduser 0 Mar 27 01:00 ./test03.txt
ファイル名: ./test04.txt
-rw-r--r-- 1 finduser finduser 0 Mar 28 01:00 ./test04.txt
ファイル名: ./test05.txt
-rw-r--r-- 1 finduser finduser 0 Mar 29 01:00 ./test05.txt

while read line はもちろんfindのみではなく、

cat {ファイル名} |while read line
echo {変数} |while read line

といった形でも利用可能です。

おまけ

先で紹介したオプションを盛り込んで、

【-mtime】前日以前に更新されたファイル
【-name】ファイル名に「ccc」を含む
【-type】検索種別は「ファイル」
【-maxdepth】検索対象ディレクトリは第2階層目まで
【while read line】各ファイルごとに圧縮する

処理を行うシェルスクリプトを作成してみます。

find.sh
#!//bin/bash

FIND_DIR="/work/find_test1/test"

# 検索対象は2階層目まで
# 検索種別はファイル
# 「ccc」の文字列を含むファイル
# 1日以前のファイル
# 「while read line」をパイプで引き渡す
find /work/find_test1/ -maxdepth 2 -type f -name "*ccc*.tmp" -mtime +1 |while read FILE_PATH
do

# ファイルディレクトリとファイル名をそれぞれ取得
FILE_DIR=`dirname ${FILE_PATH}`
FILE_NAME=`basename ${FILE_PATH}`

# 各ファイルの「bbb」という文字列の直後に任意文字列を挿入
# UNIXは同一ファイルにリダイレクトできないため、tmpファイルにリダイレクト
sed -e "/^bbb$/a find.shにより特定行を挿入" ${FILE_DIR}/${FILE_NAME} > ${FILE_DIR}/${FILE_NAME}.tmp
mv ${FILE_DIR}/${FILE_NAME}.tmp ${FILE_DIR}/${FILE_NAME}

chmod 775 ${FILE_DIR}/${FILE_NAME}
chown finduser:finduser ${FILE_DIR}/${FILE_NAME}

# 検索されたファイルを圧縮
# フルパスでの圧縮を避けるために「-C」オプションを付与
tar -C ${FILE_DIR} -cvzf ${FILE_DIR}/${FILE_NAME}.tar.gz ${FILE_NAME}

# 圧縮処理に問題がなければファイル削除
RET_CD=$?
if [ ${RET_CD} -eq 0 ];then
rm -f ${FILE_DIR}/${FILE_NAME}
fi

done

exit
# 処理概要
1 検索対象種別は「ファイル」を対象として検索する
2 「bbb」という文字列を含むファイルのみ対象として検索する
3 前日以前のファイルを対象として検索する
4 指定ディレクトリから子ディレクトリ2階層までを対象として検索する
5 各ファイル内容の「bbb」という特定文字列の直後に任意行を挿入する
6 ファイル単位で元ファイルと同ディレクトリ内に圧縮ファイルを作成する
7 圧縮が成功していれば、ファイルは削除する

処理イメージ

# スクリプト実行前
# 3階層分同じファイルをコピー
$ tree find_test1
find_test1
| aaa_20210320.tmp
| aaa_20210321.tmp
| aaa_20210322.tmp
| bbb_20210323.tmp
| bbb_20210324.tmp
| bbb_20210325.tmp
| ccc_20210326.tmp
| ccc_20210327.tmp
| ccc_20210328.tmp
∟ find_test2
| aaa_20210320.tmp
| aaa_20210321.tmp
| aaa_20210322.tmp
| bbb_20210323.tmp
| bbb_20210324.tmp
| bbb_20210325.tmp
| ccc_20210326.tmp
| ccc_20210327.tmp
| ccc_20210328.tmp
∟ find_test3
| aaa_20210320.tmp
| aaa_20210321.tmp
| aaa_20210322.tmp
| bbb_20210323.tmp
| bbb_20210324.tmp
| bbb_20210325.tmp
| ccc_20210326.tmp
| ccc_20210327.tmp
∟ ccc_20210328.tmp

$ ls -l ./find_test1
total 36
-rwxrwxr-x 1 finduser finduser 20 Mar 20 10:00 aaa_20210320.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 21 10:00 aaa_20210321.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 22 10:00 aaa_20210322.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 23 10:00 bbb_20210323.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 24 10:00 bbb_20210324.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 25 10:00 bbb_20210325.tmp
-rwxrwxr-x 1 finduser finduser 197 Mar 26 10:00 ccc_20210326.tmp
-rwxrwxr-x 1 finduser finduser 197 Mar 27 10:00 ccc_20210327.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 28 10:00 ccc_20210328.tmp

# ファイル中身確認
$ cat ./find_test1/ccc_20210326.tmp
aaa
bbb
ccc
ddd
eee

#シェルスクリプトを実行
$ bash ./find.sh
$

#ファイル階層を再確認
#第1階層~第2階層に圧縮ファイルが作られていることを確認
$ tree find_test1
find_test1
| aaa_20210320.tmp
| aaa_20210321.tmp
| aaa_20210322.tmp
| bbb_20210323.tmp
| bbb_20210324.tmp
| bbb_20210325.tmp
| ccc_20210326.tmp.tar.gz
| ccc_20210327.tmp.tar.gz
| ccc_20210328.tmp
∟ find_test2
| aaa_20210320.tmp
| aaa_20210321.tmp
| aaa_20210322.tmp
| bbb_20210323.tmp
| bbb_20210324.tmp
| bbb_20210325.tmp
| ccc_20210326.tmp.tar.gz
| ccc_20210327.tmp.tar.gz
| ccc_20210328.tmp
∟ find_test3
| aaa_20210320.tmp
| aaa_20210321.tmp
| aaa_20210322.tmp
| bbb_20210323.tmp
| bbb_20210324.tmp
| bbb_20210325.tmp
| ccc_20210326.tmp
| ccc_20210327.tmp
∟ ccc_20210328.tmp


$ ls -l ./find_test1
total 36
-rwxrwxr-x 1 finduser finduser 20 Mar 20 10:00 aaa_20210320.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 21 10:00 aaa_20210321.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 22 10:00 aaa_20210322.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 23 10:00 bbb_20210323.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 24 10:00 bbb_20210324.tmp
-rwxrwxr-x 1 finduser finduser 20 Mar 25 10:00 bbb_20210325.tmp
-rw-rw-r-- 1 finduser finduser 197 Mar 29 12:27 ccc_20210326.tmp.tar.gz
-rw-rw-r-- 1 finduser finduser 197 Mar 29 12:27 ccc_20210327.tmp.tar.gz
-rwxrwxr-x 1 finduser finduser 20 Mar 28 10:00 ccc_20210328.tmp


# 圧縮ファイルを展開して内容を確認
$ sudo tar xvzf ./find_test1/ccc_20210326.tmp.tar.gz
ccc_20210326.tmp

# 特定文字列を「bbb」の直後に挿入されていること確認
$ cat ./ccc_20210326.tmp
aaa
bbb
find.shにより特定行を挿入
ccc
ddd
eee

想定通りの挙動をするシェルスクリプトになりました。

おわりに

findは利用頻度は非常に高いのですが、-mtime/-mminといった対象期間を限定する起点や終点についてよく迷ってしまうので、そんなときに本記事がお役に立てれば幸いかと思います。

また、今回紹介しきれませんでしたが、-atime/-ctime/-newerなど他にも様々なfindオプションがありますので、もっともっとオプションを使いこなして素敵なfindライフを送りましょう。

シェルスクリプト連載の3日目でした。明日は市川諒さんのdeclare使ってBashで配列と連想配列です。