Photo by Agence Olloweb on Unsplash
はじめに こんにちは、中本です。シェルスクリプト連載 の3日目です。
本記事ではShell Script作成において切っても切れない findコマンド について利用頻度高めのオプションをまとめます。
最後には今回紹介したオプションを全て盛り込んだシェルスクリプトを作成します。
findとはfindは、場所を指定してファイルやディレクトリを検索するコマンドです。
「ファイル名/ディレクトリ名」「更新日時」「種別」など様々な条件を指定してファイルを検索できます。
様々なfindオプション -typeオプション検索する対象のファイル種別を指定するオプション
# 検索対象種別「ファイル」 $ find [検索パス]-type f # 検索対象種別「ディレクトリ」 $ find [検索パス]-type d
$ ls -ltotal 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
上記の挙動で分かるように、以下のように処理に違いがあります。
したがって、検索したファイルを1ファイル単位で圧縮するなどの処理の場合は、-execオプションを利用が推奨されます。
while read line while read lineに関しては、findのオプションではありませんが、findコマンドと相性の良いループ処理です。 検索結果を一行ずつ読み込んで、任意の処理を実行できます。
while_read_line.sh #!//bin/bash ${FIND_DIR} ="[検索パス]" 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 lineecho {変数} |while read line
といった形でも利用可能です。
おまけ 先で紹介したオプションを盛り込んで、
【-mtime】前日以前に更新されたファイル 【-name】ファイル名に「ccc」を含む 【-type】検索種別は「ファイル」 【-maxdepth】検索対象ディレクトリは第2階層目まで 【while read line】各ファイルごとに圧縮する
処理を行うシェルスクリプトを作成してみます。
find.sh #!//bin/bash FIND_DIR="/work/find_test1/test" 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} ` 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} 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
処理イメージ
# スクリプト実行前 # 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_test1total 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.tmpaaa 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_test1total 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.gzccc_20210326.tmp # 特定文字列を「bbb」の直後に挿入されていること確認 $ cat ./ccc_20210326.tmpaaa bbb find.shにより特定行を挿入 ccc ddd eee
想定通りの挙動をするシェルスクリプトになりました。
おわりに findは利用頻度は非常に高いのですが、-mtime/-mminといった対象期間を限定する起点や終点についてよく迷ってしまうので、そんなときに本記事がお役に立てれば幸いかと思います。
また、今回紹介しきれませんでしたが、-atime/-ctime/-newerなど他にも様々なfindオプションがありますので、もっともっとオプションを使いこなして素敵なfindライフを送りましょう。
シェルスクリプト連載 の3日目でした。明日は市川諒さんのdeclare使ってBashで配列と連想配列 です。