日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

マイナンバーチェッカー用シェルawkスクリプト

先日は、仕様に基づいたマイナンバーを列挙するシェルのワンライナーを書いた。危険シェル芸ではないつもりだが、パソコンに高負荷をかけるため要注意なワンライナーではある。

papiro.hatenablog.jp

これとは別に、12桁の数字がマイナンバーの仕様に合っているかを確認するためのシェルスクリプトを作ってみる。

今回も下記のチェックデジット計算についてのページを参考にする。

qiita.com

実行環境

今回もMacというかOSX Yosemiteで実行している。awkやsedはOS標準のものでもGNU版でも可。LinuxやFreeBSDでも動作すると思われる。

最初のシェルスクリプト

ということで最初に考えたシェルスクリプトがこちら。ファイル名をmynumber_checker.shをしておいた。

真・マイナンバーシェル芸に比べて処理が複雑になっているが、これは入力される数値が12桁以外の場合を考慮した結果だ。

mynumber_checker.sh

#!/bin/sh
# マイナンバーチェッカー mynumber_checker.sh
#
# 標準入力から12桁の数値テキストデータを入力。
# 複数行可。フィルタとして動作する。
# 数値以外のデータ入力は想定していない。
#
# チェックデジットの計算を基に、下記のようにOKかNGを判定して出力する。
# 12桁以外の数値の場合は、行頭にNG: をつけて出力する。
# OK: 123456789018
# OK: 023456789013
# NG: 123456789019
# NG: 1
# NG: 1234567890123

# 空行を削除
sed '/^$/d'                                                                       |
# 一桁ずつ空白区切りのフィールドに分解
sed 's/./& /g'                                                                    |
# 桁数(つまりフィールド数)を第一フィールドに出力
# チェックデジットの計算をして第二フィールドに出力
awk '{print NF,($1*6+$2*5+$3*4+$4*3+$5*2+$6*7+$7*6+$8*5+$9*4+$10*3+$11*2)%11,$0}' |
awk '$2<=1{$2=0;print $0}$2>1{$2=(11-$2);print $0}'                                          |
# 1:桁数 2.チェックデジット 3:1桁目の数値 4:2桁目の数値 .........
# 12桁でない場合は行頭にNG:を出力
awk '$1!=12{print "NG:",$0}$1==12{print}'                                         |
# 計算したチェックデジットと12桁目が不一致ならNG:を出力。一致すればOK:を出力。
awk '/^NG:/{print}!/^NG:/{if($2!=$14){print "NG:",$0}else{print "OK:",$0}}'       |
# 1:OK/NG 2:桁数 3.チェックデジット 4:1桁目の数値 5:2桁目の数値 .........
# 分解されたフィールドを連結する
awk '{printf $1" ";for(i=4;i<=$2+3;i++)printf $i;print ""}'
# 1:OK/NG 2:マイナンバーの数値

見ての通りawkの連結で処理をしている・・・が、こんなやり方でもいいじゃない!?一つのawkスクリプトにしてしまってもいいのだが・・・

Twitterリプライいただいた後に

と思っていたら、@ebanさんのこのつぶやきを参考にして考えたら、結局awk一発になった。シェルスクリプトというよりawk芸だコレ。1桁ずつ空白で区切る必要も無くなった。

出来たシェルスクリプト・・・というかawkワンライナーがこちら。空行削除はも無くてもいいか・・・

mynumber_checker.sh

#!/bin/sh
awk '{c=($1*6+$2*5+$3*4+$4*3+$5*2+$6*7+$7*6+$8*5+$9*4+$10*3+$11*2)%11}NF!=12||$12!=(c<=1?0:(11-c)){print "NG: "$0}$12==(c<=1?0:(11-c)){print "OK: "$0}' FS=

もうawkスクリプトでもええがな。awkワンライナーが見にくいならこちらでも良い。

mynumber_checker.awk

#!/usr/bin/env awk -f
BEGIN {
  FS=""
}
{ 
  c=($1*6+$2*5+$3*4+$4*3+$5*2+$6*7+$7*6+$8*5+$9*4+$10*3+$11*2)%11
}
NF!=12 || $12!=(c<=1?0:(11-c)) {
  print "NG: "$0
}
$12==(c<=1?0:(11-c)) {
  print "OK: "$0
}

実行例

ではデータを準備して実行してみる。こんな感じで行頭にOK:NG:を付けて表示する。シェルスクリプトとawkスクリプト、どちらも結果は同じだ。

$ chmod +x mynumber_checker.sh
$ chmod +x mynumber_checker.awk

$ echo 123456789018 | ./mynumber_checker.sh
OK: 123456789018

$ echo 123456789019 | ./mynumber_checker.awk
NG: 123456789019

$ cat numbers 
1
11
111
1111
11111
1234567890123
123456789010
123456789011
123456789012
123456789013
123456789014
123456789015
123456789016
123456789017
123456789018
123456789019
023456789013

$ cat numbers | ./mynumber_checker.sh
NG: 1
NG: 11
NG: 111
NG: 1111
NG: 11111
NG: 1234567890123
NG: 123456789010
NG: 123456789011
NG: 123456789012
NG: 123456789013
NG: 123456789014
NG: 123456789015
NG: 123456789016
NG: 123456789017
OK: 123456789018
NG: 123456789019
OK: 023456789013

MacというかOSXの場合はgseqコマンドを使おう

$ gseq -w 0 999999999999 | head -n 30 | ./mynumber_checker.awk
OK: 000000000000
NG: 000000000001
NG: 000000000002
NG: 000000000003
NG: 000000000004
NG: 000000000005
NG: 000000000006
NG: 000000000007
NG: 000000000008
NG: 000000000009
NG: 000000000010
NG: 000000000011
NG: 000000000012
NG: 000000000013
NG: 000000000014
NG: 000000000015
NG: 000000000016
NG: 000000000017
NG: 000000000018
OK: 000000000019
NG: 000000000020
NG: 000000000021
NG: 000000000022
NG: 000000000023
NG: 000000000024
NG: 000000000025
NG: 000000000026
OK: 000000000027
NG: 000000000028
NG: 000000000029