Table of Contents
Problem
カレントディレクトリにyyyy-mm-dd-<description>.md
というファイルが以下のように存在しているとします.
1
2
3
4
5
% find . -type f -printf "%f\n"
2021-02-01-order-pizza.md
2021-01-01-Linux-is-awesome.md
2021-07-28-Ubuntu-so-kind.md
2021-03-03-shougi.md
このときやりたいことは以下,
- このファイル一覧から
yyyy-mm-dd
の形式でsortされた日付リストを取得
1
2
3
4
5
% CMD ./
2021-01-01
2021-02-01
2021-03-01
2021-07-28
- 上記で得られたリストから任意の2つの日付の間のdateのみ取得
1
2
3
% CMD ./ 2021-01-02 2021-02-01
2021-01-01
2021-02-01
Solution 1: ファイル一覧からyyyy-mm-dd
の形式でsortされた日付リストを取得
カレントディレクトリからファイル一覧のみを取得するためにはfind
コマンドを以下のように利用すれば良い:
1
% find . -type f
ここから日付リストを取得する方法として,
- シンプルに
grep
を用いる awk
+match
を用いる
の2つが考えられます.
1
2
3
4
5
## grepを用いる場合
% find . -type f -printf "%f\n" | grep -Eo '^[0-9]{4}-[0-9]{2}-[0-9]{2}'
## awk & matchを用いる場合
% find $1 -type f -printf "%f\n" | awk 'match($0, /^[0-9]{4}-[0-9]{2}-[0-9]{2}/){print substr($0, RSTART, RLENGTH)}'
awk: RLENGTH and RSTART
awk
コマンドで利用可能なmatch
関数は, input文字列を対象に /pattern(regex also available)/
の検索機能を提供してくれます.
1
2
3
4
5
6
## 以下の2つは同じ結果を返す
% echo "unko\nkirby" | awk 'match($0, /un/)'
unko
% echo "unko\nkirby" | grep 'un'
unko
match
関数はmatchした場合, RSTART
and RLENGTH
という変数を設定します.
RSTART |
検索に合致する文字列の開始場所 |
RLENGTH |
検索に合致する文字列の長さ |
この2つの変数とsubstr
関数を組み合わせることでgrep -o
と似た挙動を再現することができます.
1
2
3
% echo '2090-09-01-aaaabbbb\n2001-09-01-kkkkk' | awk 'match($0, /^[0-9]{4}-[0-9]{2}-[0-9]{2}/){print substr($0, RSTART, RLENGTH)}'
2090-09-01
2001-09-01
Solution 2: 上記で得られたリストから任意の2つの日付の間のdateのみ取得
上記のコマンドで日付リストは取得できています. そこからfilterをする方法として awk
の変数定義とgensub
関数を利用する方法があります
1
2
3
4
5
6
7
8
9
10
11
## filter date between 2000-08-01 and 2012-08-31
% echo '2090-09-01\n2010-09-01\n2011-09-01' | awk -v start='2000-08-01'\
-v end='2012-08-31' -F= '{a=gensub("-", "","g", $1);\
gsub("-", "", start); gsub("-", "", end)} (a >= start) && (a <= end)'|sort
2010-09-01
2011-09-01
% echo '2090-09-01\n2010-09-01\n2011-09-01' | awk -F= 'BEGIN{start="2000-08-01"; end="2012-08-31"}\
{a=gensub("-", "","g", $1); gsub("-", "", start); gsub("-", "", end)} (a >= start) && (a <= end)'|sort
2010-09-01
2011-09-01
変数定義
awk
コマンドでは-v
オプションやBEGIN{}
を組み合わせることでawk
コマンド内部で利用可能となる変数
の定義ができます.
1
2
3
4
5
## -vによる変数定義
awk -v varname=value
## BEGIN{}での
awk 'BEGIN { varname=value }'
awk
に与えられた変数を利用して新たな変数を作成する場合はgensub()
関数を用います
1
2
3
4
5
6
7
8
9
10
## syntax
gensub("置換前", "置換後", "変更箇所(基本は数値, gならばすべて)", 対象カラム)
## yyyy-mm-ddをyyyy/mm/ddへ変更
% echo "2020-09-01" | awk '{date=gensub("-", "/", "g", $1)}{print date}'
2020/09/01
## 1つ目の-のみ置換
% echo "2020-09-01" | awk '{date=gensub("-", "/", 1, $1)}{print date}'
2020/09-01
日付の大小関係を評価する
awk
では条件式に基づいた評価が可能ですが, 日付は文字列として認識されるのでそのままでは大小関係を評価することはできません.
幸いにも今回は日付形式がyyyy-mm-dd
なのでyyyymmdd
と数値変換しても大小関係は同じになるのでこの特徴を利用します.
方針としては
start
,end
をyyyy-mm-dd
からyyyymmdd
へ変換する- inputとして読み込まれる
yyyy-mm-dd
をベースにyyyymmdd
形式の変数を新たに定義する - 条件式にあった
yyyy-mm-dd
を返す
その解答が以下:
1
2
% echo '2090-09-01\n2010-09-01\n2011-09-01' | awk -F= 'BEGIN{start="2000-08-01"; end="2012-08-31"}\
{a=gensub("-", "","g", $1); gsub("-", "", start); gsub("-", "", end)} (a >= start) && (a <= end)'|sort
gsub("-", "", 変数)
はglobalにsub(=置換)する関数です. 変換位置を”g”に指定したgensub
の挙動と似ていますが,
これは変数そのものを変化させる関数です. 今回はstart
, end
変数は出力させる予定はないので, 直接gsub
で形式を変換しています.
変換後, (a >= start) && (a <= end)
評価式に合致するレコードのみを出力するという流れになっています
Column: mm/dd/yyyy 形式の場合
mm/dd/yyyy
の場合はそのままmmddyyyy
と変換して大小関係を比較すると厄介なことが起きます. このような場合は,
yyyy
要素mm
要素dd
要素
を個別に取得して, 順番を入れ替えた上でyyyymmdd
と変換することが必要です. これはawk
の配列機能を利用することで実現できます.
詳しくは説明しませんが, 解答例は以下
1
2
3
% find . -type f | awk 'match($0, /[0-9]{4}-[0-9]{2}-[0-9]{2}/){print substr($0, RSTART, RLENGTH)}'\
| awk -v start='2022-08-02' -v end='2022-08-31' -F= '{split($1, a, /-/); split(start, b, /-/);\
split(end, c, /-/)} (a[1]a[2]a[3] >= b[1]b[2]b[3]) && (a[0]a[1]a[2] <= c[0]c[1]c[2])'| sort
Summary
これをシェルスクリプトに落とし込むと
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
## get date list from start to end
## Author: Ryo Nakagami
## Revised: 2023-08-09
set -e
# var
SEARCH_PATH=$1
START=$2
END=$3
# Functions
function args_error {
echo 'fatal: start or end missing, date format is yyyymmdd'
exit 1
}
# Main
if [[ -z $3 ]]; then
args_error
fi
find $SEARCH_PATH -type f -printf "%f\n" \
| grep -Eo '[0-9]{4}-[0-9]{2}-[0-9]{2}'\
| awk -v start=$START -v end=$END\
-F= '{date=gensub("-", "", "g", $0); gsub("-", "", start); gsub("-", "", end)}\
(date >= start) && (date <= end)'\
| sort
References
(注意:GitHub Accountが必要となります)