ディレクトリの中身のファイルをリスト化して操作できると色々と便利です。 例としてディレクトリ内のファイルの行数をCSV出力してみながら、bashの解説をしていきます。
Contents
- 目的
- 1. ディレクトリ内のファイルとフォルダを出力する
- 2. ディレクトリとファイルを判定する
- 3. パスからファイル名を取得する
- 4. ファイルの行数を変数に代入する
- 5. ログファイルのファイル名とそのファイルの行数のCSVを出力する
目的
Apacheのログファイルのファイル名とそのファイルの行数のCSVを出力するbashを考えてみる。
▼ディレクトリの中身はこんな感じ。 Apacheのログとそのバックアップのディレクトリがあるとする。 ログは1週間ごとに切り分けてあり、古いファイルは圧縮している。
/var/log/httpd/
├── access_log
├── access_log-20210228.gz
├── access_log-20210307.gz
├── access_log-20210314.gz
├── access_log-20210321.gz
├── error_log
├── error_log-20210228.gz
├── error_log-20210307.gz
├── error_log-20210314.gz
├── error_log-20210314.gz
├── error_log-20210321.gz
└── backup # ディレクトリ
├── 202012
├── 202101
├── 202102
└── 202103
以降で順に説明しながら目的を実行するスクリプトを書いていく。
1. ディレクトリ内のファイルとフォルダを出力する
#!/bin/bash
DIR=/var/log/httpd
# ディレクトリ内のファイルのリストを操作
for path in $DIR/*; do
# パスを出力する
echo $path
done
実行結果
/var/log/httpd/access_log
/var/log/httpd/access_log-20210228.gz
/var/log/httpd/access_log-20210307.gz
/var/log/httpd/access_log-20210314.gz
/var/log/httpd/access_log-20210321.gz
/var/log/httpd/error_log
/var/log/httpd/error_log-20210228.gz
/var/log/httpd/error_log-20210307.gz
/var/log/httpd/error_log-20210314.gz
/var/log/httpd/error_log-20210321.gz
/var/log/httpd/backup
for path in /path/to/dir/*; do
と書くとディレクトリ内の一層目のファイルとフォルダの
リストをfor文で扱うことができる。
2. ディレクトリとファイルを判定する
if [ -d "$path" ]; then
でディレクトリか判定できる。
#!/bin/bash
DIR=/var/log/httpd
for path in $DIR/*; do
if [ -d "$path" ]; then
# ディレクトリの場合スキップする
continue
fi
echo $path
done
▼ファイルのみ出力する
実行結果
/var/log/httpd/access_log
/var/log/httpd/access_log-20210228.gz
/var/log/httpd/access_log-20210307.gz
/var/log/httpd/access_log-20210314.gz
/var/log/httpd/access_log-20210321.gz
/var/log/httpd/error_log
/var/log/httpd/error_log-20210228.gz
/var/log/httpd/error_log-20210307.gz
/var/log/httpd/error_log-20210314.gz
/var/log/httpd/error_log-20210321.gz
早めに例外で切り上げるため-d
でディレクトリか判定しました。
他にもファイルやシンボリックリンクなど判定できます。
-e
: パスが存在するか-d
: ディレクトリが存在するか-f
: ファイルが存在するか-L
: リンクが存在するか-s
: 空ファイルではないか-w
: ファイルが書込可能か-x
: ファイルが実行可能か 参考: bashでファイルやディレクトリの存在確認方法まとめ
3. パスからファイル名を取得する
FILE_NAME=${path##*/}
でパスからファイル名を取得出来る。
#!/bin/bash
DIR=/var/log/httpd
for path in $DIR/*; do
if [ -d "$path" ]; then
# ディレクトリの場合スキップする
continue
fi
# echo $path
FILE_NAME=${path##*/}
echo $FILE_NAME
done
実行結果
access_log
access_log-20210228.gz
access_log-20210307.gz
access_log-20210314.gz
access_log-20210321.gz
error_log
error_log-20210228.gz
error_log-20210307.gz
error_log-20210314.gz
error_log-20210321.gz
パターンマッチより${変数##パターン}
とすると先頭から最長一致した部分を取り除く。
つまり、${path##*/}
ではパスの一番後ろの/
までが取り除かれるため、ファイル名が残る。
パターンマッチの仕様
${変数#パターン} # 先頭から最短一致した部分を取り除く ${変数##パターン} # 先頭から最長一致した部分を取り除く ${変数%パターン} # 末尾から最短一致した部分を取り除く ${変数%%パターン} # 末尾から最長一致した部分を取り除く
4. ファイルの行数を変数に代入する
wc -l
でファイルの行数がわかる。
この結果を変数に代入して扱えるようする。
#!/bin/bash
file=/var/log/httpd/access_log
LINE=$(wc -l < $file)
echo $LINE
実行結果
173543
また、行数を取得する際に条件付けもしてみる。
4.1. grepで条件付けした行数を変数に代入する
#!/bin/bash
# grepで条件付け
file=/var/log/httpd/access_log
LINE=$(cat $file | grep POST | wc -l)
echo "POST:"$LINE
実行結果
POST:26
4.2. 一時ファイルに保存した行数を変数に代入する
#!/bin/bash
# 一時ファイルに保存する
# GETを含む and 200を含まない
cat /var/log/httpd/access_log \
| grep GET \
| grep -v "200" \
> tmp.txt
LINE=$(wc -l < "tmp.txt")
echo "GET and not in 200:"$LINE
実行結果
GET and not in 200:76
4.3. 圧縮ファイルの行数を変数に代入する
#!/bin/bash
# 圧縮ファイル
file=/var/log/httpd/access_log-20210228.gz
LINE=$(zcat $file | wc -l)
echo $LINE
実行結果
413180
5. ログファイルのファイル名とそのファイルの行数のCSVを出力する
目的の処理を実装する。
#!/bin/bash
DIR=/var/log/httpd
echo "filename,line"
for path in $DIR/*; do
if [ -d "$path" ]; then
# ディレクトリの場合スキップする
continue
fi
# echo $path
FILE_NAME=${path##*/}
# 拡張子
ext=${FILE_NAME##*.}
if [ "$ext" == "gz" ]; then
# 圧縮ファイル
LINE=$(zcat $path | wc -l)
else
LINE=$(cat $path | wc -l)
fi
echo $FILE_NAME","$LINE
done
実行結果
filename,line
access_log,178389
access_log-20210228.gz,413180
access_log-20210307.gz,411489
access_log-20210314.gz,413381
access_log-20210321.gz,422814
error_log,16686
error_log-20210228.gz,41686
error_log-20210307.gz,44184
error_log-20210314.gz,44154
error_log-20210321.gz,43002
いい感じですね。