しぐまろぐ

勉強したことや読んだ本について書きます。

シェル・ワンライナー160本ノック 問題7

問題7:消費税

家計簿のテキストファイルの3列目に金額(税抜)があるため、消費税を加えて全て足し合わせる。1列目に日付、2列目に商品名があり軽減税率が適用される商品は最初に*がついている。2019年10月以前の商品と、2列目の最初に*がついている商品の税率は8%、それ以外は10%とする。

解答

答えは全然わからなかったけどなんとか足掻いたものを載せておきます。

# 2列目が*から始まる行だけ抽出できる。
$ cat kakeibo.txt | awk '{print $2}' | grep '^\*'
*キャベツ二郎
*ねるねるねるねる

# 3列目に消費税率10%を賭けた金額を足し合わせた途中経過を出力していき、最後に合計金額を出せる。
$ cat kakeibo.txt | awk 'BEGIN{sum=0}{print sum+=int($3*1.1)}END{print "合計", sum}'
11000
11143
25443
39743
39776
54076
合計 54076

正答

順を追って少しずつ進めていく。

ステップ1

まず各行に消費税率の列を追加する。素直に全行1.1とする。

$ cat kakeibo.txt | awk '{tax=1.1; print $0, tax}'
  • $0 : 行全体を表す。
ステップ2

消費税率の列を、条件によって1.1と1.08を表示し分ける。

$ cat kakeibo.txt | awk '{tax = ($1<"20191001" || $2~"^*") ? 1.08 : 1.1; print $0, tax}'
  • ~ : 正規表現との比較を表す。私が解答の1つ目でやっていることがこれで実現できる。ただ1つ気になったのだが、正規表現との比較なら参考サイトのように=~になると思うんだけど、ここでは~だけだし、=~にするとシンタックスエラーになる。

→ 2023.01.09追記:bash正規表現が「=~」であり、awk正規表現は「~」とのことです。カツニャリ (id:papiro)さんにコメントでご教示いただきました。

補足

公式のgithubのissue(参考サイト欄参照)にある通り、Macだと消費税が全て1.08で表示されてしまうため、GNU AWKをインストールする必要がある。基本的にコメントの通り進めていけば良いのだが、インストール後にターミナルに表示された以下の指示通り、bashrcへのPATHの追加も必要なので忘れずに。

GNU "awk" has been installed as "gawk".
If you need to use it as "awk", you can add a "gnubin" directory
to your PATH from your ~/.bashrc and/or ~/.zshrc like:

    PATH="/opt/homebrew/opt/gawk/libexec/gnubin:$PATH"
ステップ3

3列目と4列目の掛け算を行い、結果を出力する。

$ cat kakeibo.txt | awk '{tax = ($1<"20191001" || $2~"^*") ? 1.08 : 1.1; print $0, tax}' | awk '{print int($3 * $4)}'
ステップ4

最後に全ての金額を合計して出力する。

$ cat kakeibo.txt | awk '{tax = ($1<"20191001" || $2~"^*") ? 1.08 : 1.1; print $0, tax}' | awk '{print int($3 * $4)}' | numsum
  • numsum : 入力された数値の合計値を求めてくれる素敵なコマンド。

なお、macにはnumsumがないので、私は代替として以下のように書いてみた。

$ cat kakeibo.txt | awk '{tax = ($1<"20191001" || $2~"^*") ? 1.08 : 1.1; print $0, tax}' | awk '{print int($3 * $4)}' | awk 'BEGIN{sum=0}{sum+=$1}END{print sum}'

別解を見て気づいたんですが、最後は「awk '{sum+=$1}END{print sum}'」でもいいですね(定義していない変数の初期値は0なので)。

補足(2023.01.09追記)

macでも以下を実行するとnumsumを実行できるとのことです。

$ brew install num-utils

別解

別解1

デフォルトのtaxを指定し、日付と軽減税率のチェックを別々に行っている。

$ cat kakeibo.txt | awk '{tax=1.1}$1<"20191001"{tax=1.08}/  \*/{tax=1.08}{print $3*tax}' | sed 's/\..*//' | awk '{a+=$1}END{print a}'
  • / \* 「 \*」 : 空白の後に「*」がある行にマッチする。
  • / \* : 空白の後に「*」がある行にマッチする。
  • sed 's/\..*// : 「.」以下を置き換えることで小数点以下の切り捨てを行う

おそらく「awk '{初期状態}条件{処理} / 条件{処理}'」という形式になっている。複数の条件がある場合にスラッシュで分けている?
→ 2023.01.12追記:正しくは、「「awk '{初期状態}条件{処理} 条件{処理}'」という形式。「/ \*/」は2つ目の条件で、「/正規表現/」の形式。

別解2(2023.01.09追記)

@pyopyopyoさんにコメントで教えていただいた解答です。コメントの方は複数行になっていてみやすいですが、こちらはワンライナーにしてintでの切り捨ても追加してみました。

$ cat kakeibo.txt | awk 'BEGIN{SUM=0}{if ($1<20191001 || $2~"^*") {SUM += int($3 * 1.08)} else {SUM += int($3 * 1.1)}}END {print(SUM)}'