RACCOON TECH BLOG

株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。

シェル芸のすすめ~awk, xargs, date~

こんにちは、技術戦略部開発第3チームのかしざきです。

エンジニアであれば、ログの調査、環境構築、データの整形などLinuxコマンドを書く機会はたくさんあるかと思います。
コマンドは所謂「枯れた技術」ですが、「枯れた技術」であるが故に話題に上ることは少なく、集中的に学ぶ機会は少ないように感じます。
結果として、毎回コマンドを使ってやりたいことを上手く実現できずにモヤモヤしている方も多いのではないでしょうか(私のことです)。

シェル芸本との出会い

そんな中、先日最寄りの書店にて、ある書籍に出会いました。
それがこちら  
1日1問、半年以内に習得 シェル・ワンライナー160本ノック

翔泳社の主催するITエンジニア本大賞2022の技術書部門ベスト10にも入った技術書のようです。
冒頭に書いたようなモヤモヤを払拭すべく、この書籍を購入して(※1)少しずつトレーニングしてみることにしました。
書籍の副題に「1日1問、半年以内に習得」とありますが、シェル・ワンライナーの問題が160問、各章の練習問題と合わせて200問程度の問題が用意されています。
各問題には丁寧な解説が付いており、質量ともに充実した内容です。
私はこの記事を書いている時点で第2章の途中までしか進められていませんが、少しずつコマンドを書く力を鍛えられている感覚があるので、この先も読み進めていきたいと思います。

問題にチャレンジ

これからこの書籍で取り上げられていた問題を少しアレンジした問題と、その解答・解説を書いてみます。
普段私が開発に携わっているPaidの加盟企業向けAPIのアクセスログを題材にした問題と、日付処理に関する問題の2本立てです。
よろしければチャレンジしてみてください。

なお、実行環境については、この書籍でも利用しているUbuntu 20.04を利用しています。
私はWSL上で実行していますが、Raspberry Piなどにインストールしても、PCに直接インストールしても問題ないと思います。
また、awkはGNU Awk(gawk)を利用しています。

問題1

Paidの加盟企業向けAPIのアクセスログは下記のようなフォーマットで残されます(※2)。

[レスポンス日時][エンドポイントのホスト部][アクセス先のIPアドレス][エンドポイントのホスト部を除いたURL][リクエスト日時][リクエストパラメータの形式が正しいか(true/false)][加盟企業を識別する番号(company_code)]

下記のログファイルの中からcompany_code毎のログの行数を求めてみましょう。

paid-access.log

2022-03-17 00:05:21.071 api.paid.jp 10.39.3.10 /y/coop/Register/b2bMemberId/ver1.0/p.json 2022/03/17 00:05:21.042 true 1576
2022-03-17 00:05:21.082 paid.jp 10.39.3.2 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:21.060 true 3931
2022-03-17 00:05:21.175 api.paid.jp 10.39.3.10 /y/coop/Register/b2bMemberId/ver1.0/p.json 2022/03/17 00:05:21.148 true 1576
2022-03-17 00:05:21.276 api.paid.jp 10.39.3.10 /y/coop/Register/b2bMemberId/ver1.0/p.json 2022/03/17 00:05:21.248 true 1576
2022-03-17 00:05:21.375 api.paid.jp 10.39.3.10 /y/coop/Register/b2bMemberId/ver1.0/p.json 2022/03/17 00:05:21.348 true 1576
2022-03-17 00:05:21.476 api.paid.jp 10.39.3.10 /y/coop/Register/b2bMemberId/ver1.0/p.json 2022/03/17 00:05:21.448 true 1576
2022-03-17 00:05:22.479 paid.jp 10.39.3.10 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:22.457 true 3931
2022-03-17 00:05:22.979 api.paid.jp 10.39.3.10 /y/coop/Register/b2bMemberId/ver1.0/p.json 2022/03/17 00:05:22.950 true 4047
2022-03-17 00:05:23.365 paid.jp 10.39.3.1 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:23.343 true 3931
2022-03-17 00:05:26.074 paid.jp 10.39.3.2 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:26.052 true 3931
2022-03-17 00:05:28.339 paid.jp 10.39.3.1 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:28.316 true 3931
2022-03-17 00:05:31.022 paid.jp 10.39.3.2 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:31.000 true 3931
2022-03-17 00:05:33.358 paid.jp 10.39.3.1 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:33.335 true 3931
2022-03-17 00:05:36.097 paid.jp 10.39.3.2 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:36.075 true 3931
2022-03-17 00:05:36.137 api.paid.jp 10.39.3.10 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:36.115 true 1576
2022-03-17 00:05:36.550 api.paid.jp 10.39.3.10 /y/coop/member/creditCheck/ver1.0/p.json 2022/03/17 00:05:36.527 true 1576
2022-03-17 00:05:36.653 api.paid.jp 10.39.3.10 /y/coop/Register/b2bMemberId/ver1.0/p.json 2022/03/17 00:05:36.626 true 1576
2022-03-17 00:05:38.308 paid.jp 10.39.3.1 /y/coop/member/check/ver1.0/p.json 2022/03/17 00:05:38.286 true 3931

解答例

$ cat paid-access.log | awk '{print $9}' | sort | uniq -c
    8 1576
    9 3931
    1 4047

これは比較的簡単だったかと思います。
awk '{print $9}'の部分でcompany_codeのみが出力されます。
これをパイプで渡してsortで並び替え、最後にuniqで重複排除します。-cオプションをつけると重複した件数が出力されます。

ちょっと不格好なので件数の降順に並べてフォーマットを整えてみるとこんな感じでしょうか。

$ cat paid-access.log | awk '{print $9}' | sort | uniq -c | sort -r | awk '{print "company_code: "$2" 件数: "$1}'
company_code: 3931 件数: 9
company_code: 1576 件数: 8
company_code: 4047 件数: 1

sort -rで降順に並べ直して、awk '{print "company_code: "$2" 件数: "$1}'でフォーマットを整えています。
以上、ログの集計を行う問題でした。
もう1問、違うタイプの問題を用意しました。

問題2

皆さんは『プレミアムフライデー』を覚えていますか?
毎月最終金曜日に民間企業や公共機関が社内外向けのイベントやセールなどを行っている(行っていた?)アレです。
皆さんもプレミアムフライデーが何日にやってくるのか気になりますよね??
それでは、2022年3月17日以降、2022年中のプレミアムフライデーは何月何日なのか求めてみましょう。

解答例1

$ seq 0 300 | xargs -I{} date '+%F %a' -d '2022-03-17 {}day' | grep '^2022' | grep '金' | tac | uniq -w7 | tac

まずseq 0 300で0~300の数値を順々に次の標準入力に渡しています。
2022年の残り日数が300日弱あると考えて300としていますが、2022年末までカバーできれば他の日数を渡しても良いと思います。
xargs-Iオプションを付けることで標準入力を{}に保持します。
{}ではなく@など他の文字でも動きますが、引数を表すということで通例として{}が用いられることが多いようです。
これをdateコマンドに渡してやることで日付及び曜日を出力し、以降のgrepで2022年の金曜日のみに絞り込みます。
uniq -w7によって最初の7文字、つまり年月の部分までで重複排除できます。
ただし、そのまま重複排除すると最初のものだけ、つまり月初の金曜日の日付のみが残ってしまうため、先にtacで逆順にしてから重複排除することでプレミアムフライデーを残しています。
最後にもう一度tacで順序を戻して完了です。

解答例2

当記事のレビューを副部長の羽山に依頼したところ、別解をいただきました。
上述の解答例1とは全く別の発想で面白かったので以下に掲載します。

$ seq 1 10 |while read e; do t=$(date +%Y-%m-%d -d "2022-03-01 + ${e}months - 1day"); date -d "$t - $(( ( $(date -d $t +%u) + 2 ) % 7 ))days"; done

seq 1 10 |while read e; doの部分で早速違いがあります。
プレミアムフライデーは月に1回なので3月から12月までの月数でループを回しています。
t=$(date +%Y-%m-%d -d "2022-03-01 + ${e}months - 1day")で対象月の月末の日付をyyyy-mm-dd形式で$tに代入しています。
例えば最初のループではt=2022-03-31となります。
date -d "$t - $(( ( $(date -d $t +%u) + 2 ) % 7 ))days"の部分は、まず$(date -d $t +%u)から考えます。
この部分から曜日を1~7(月曜日=1)で取得できるので、最後の金曜日を得るには+2したものに対して7の剰余を引けばOKです。
$(( ( $(date -d $t +%u) + 2 ) % 7 ))の部分で+2して7の剰余を求めています。
その結果を$tから引いているのがdate -d "$t - $(( ( $(date -d $t +%u) + 2 ) % 7 ))days"ということになります。
私は最初はピンと来なかったのですが、具体例で考えてみるとそうなることに気が付きます。
例えば月曜日なら%uの結果は1で、それに+2すると=3、そして7の剰余は3となるので、月曜日から3日引いて金曜日となります。
土曜日なら%uの結果は6で、それに+2すると=8、そして7の剰余は1となるので、土曜日から1日引いて金曜日となります。

あくまで好みとのことでしたが、xargsは問題によっては応用が利きにくいため、どんなパターンでも使えるwhile readを使っているとのことでした。

今回は1年未満の短い期間を対象とした問題でしたが、例えば対象期間が10年になった場合、解答例1では日付の数だけdateを呼び出すためそれなりに重い処理になってしまいますし、ソートを行うため全ての演算が終わるまで結果が表示されません。
一方、解答例2ではdateの呼び出しが月の数になるため処理が軽くなる上に1行ごとに結果が出力されます。

終わりに

以上、私が読み進めているシェル芸本の紹介と、シェル芸問題2問でした。
この書籍の前書きにも

お遊びのような問題のほうが実用的な問題よりも重要です。

と書かれていますが、楽しみながらトレーニングを積んでいくことにより、開発業務のあらゆる場面で問題解決できる技術が身に着いていきそうです。

さて、ラクーングループでは一緒に働く仲間を募集中です。
採用情報はこちらです。
もしご興味をお持ちの方がいらっしゃれば、ぜひエントリーをお願いします。

※1 弊社では、自身のスキルアップのために書籍や機器の購入を支援する、技術サポート制度という制度があります。
今回の書籍も技術サポート制度を利用して購入しました。
※2 実際のPaidの加盟企業向けAPIを問題用に加工したログを扱っています。

一緒にラクーンのサービスを作りませんか? 採用情報を詳しく見る

関連記事

運営会社:株式会社ラクーンホールディングス(c)2000 RACCOON HOLDINGS, Inc