The Nominomicon の非公式翻訳を作った (Rust パーサー)

The Nominomicon の非公式翻訳を作った。

GitLab Pages でホスティングしてます。 https://nogiro.gitlab.io/nominomicon-unofficial-japanese-translation/

意訳・超訳が多いと思うので、指摘などあれば GitLab に issue を作ってもらえるとありがたいです。 8 章は内容はあったけど SUMMARY からリンクされておらずビルドされないので、翻訳せずそのままにしています。

New GitLab Issue: https://gitlab.com/nogiro/nominomicon-unofficial-japanese-translation/-/issues/new

英語へたくそなので中々大変だったけど、翻訳作業そのものが nom の理解の助けになって良かった。

npm の dotenv と cargo-make の --env-file オプションで、パースのされ方が異なったので調査

npm の dotenv パッケージと cargo-make の --env-file オプションで、パース方法に違いがあったので調べた結果をまとめました。

調査に利用したソースは https://gitlab.com/nogiro/dotenv-inspection に配置しています。

違いがあったのは ' (シングルクォート) と # (シャープ) まわりです。

この記事で比較するのは以下の 2 個です。


上記リポジトリーを clone すると、以下の .env ファイルを用意してあります。

DOTENV_INSPECTION_NOT_QUOTED=foo#bar
DOTENV_INSPECTION_SINGLE_QUOTED='foo#bar'
DOTENV_INSPECTION_DOUBLE_QUOTED="foo#bar"

npm installcargo install cargo-make の後で、npm run envcargo make --env-file .env env を実行するとそれぞれの違いが確認できます。

npm run --silent dev

DOTENV_INSPECTION_NOT_QUOTED=foo
DOTENV_INSPECTION_SINGLE_QUOTED=foo#bar
DOTENV_INSPECTION_DOUBLE_QUOTED=foo#bar

クォートされていない場合は、# 以降がコメントとして削除されていることが確認できます。

cargo make --quiet --env-file .env env

DOTENV_INSPECTION_SINGLE_QUOTED='foo#bar'
DOTENV_INSPECTION_NOT_QUOTED=foo#bar
DOTENV_INSPECTION_DOUBLE_QUOTED=foo#bar

シングルクォートでクォーテーションした場合に、シングルクォートが残っていることが確認できます。(また、逆順にソートされるようです。)

まとめ

npm の dotenv と cargo-make を同時に利用するプロジェクトでは、# を含む値はダブルクォートで囲むと良いようです。

(リポジトリーでは envmnt (cargo-make が利用してる crate) と Rust の dotenv crate、python-dotenv も比較しています。)

Dwarf Fortress プレイ中にググった英単語 (自分用メモ、随時追記)

  • throne: 玉座
  • pedestal: 台
  • brook: 小川
  • mussel: ムール貝
  • drowsy: 眠い
  • jade: ヒスイ
  • gneiss: 花崗岩
  • confinement: 監禁
    • 小動物を捕まえたら (?) 出た
  • shear: 剪定
    • 羊とかの毛刈りだろう
  • grackle: ムクドリ
  • galena: 方鉛鉱
  • eradicate (eradicating): 絶滅させる
  • heirloom: 家宝
  • worship: 崇拝
  • pewter: ピューター
  • pig iron: 銑鉄
  • electrum: 「金と銀の合金」らしい
    electrumとは・意味・使い方・読み方・例文 - 英ナビ!辞書 英和辞典
    • 仮想通貨ではない
  • hematite: 赤鉄鉱
  • quartzite: 珪岩
  • calf: 子牛
  • cow: 雌牛
  • bull: 雄牛
  • tallow: 獣脂
  • petition: 請願、嘆願、申請、請願書
  • persimmon: 柿
  • sedimentary: 沈殿物の、沈殿作用による、沈積によって生じた (Wiki)
  • cartilage: 軟骨
  • bituminous coal: 瀝青炭
  • lignite: 褐炭
  • limonite: 褐鉄鉱
  • billon: ビロン、貨幣用合金
    • どういうこと……?
      • ビロン(billon)は、合金の一種で、貴金属(主に銀だが、金の場合もある)と主成分となる金属(銅など)を混ぜたものである。硬貨、メダル、代用貨幣などの製造に用いられる。

        ビロン - Wikipedia

  • quicklime: 生石灰
  • pearlash: 真珠灰 (不純な炭酸カリウム)
  • gypsum: 石膏
  • tallow: ヘット、牛脂
  • sarcophagus: サルコファガス
  • stew: シチュー
  • orthoclase: 正長石
  • interrogate: 尋問する
  • convict: 有罪判決を下す
  • witness: 目撃者
  • expell: 追放する

nginx リバースプロキシの後ろの plantuml を VM 管理から docker-compose に変更する

目的

仮想マシンで提供していた plantuml-server を Docker コンテナーに変更します。

plantuml-server は↓を tomcat で稼働させていました。 github.com

前提

nginx を docker-compose でリバースプロキシとして利用している

https://example.com/plantuml/ で plantuml サーバーを提供しています。

/plantuml にアクセスすると nginx が仮想マシン (192.0.2.170) に転送するようにしています。1

    location /plantuml/ {
        # etc...
        proxy_pass http://192.0.2.170:8080;
    }

修正内容

サブディレクトリー対応するよう plantuml/Dockerfile を作成

Docker Hub に plantuml-server があります。 が、サブディレクトリー (/plantuml) に対応していないため、そのままでは利用できません。 https://example.com にアクセスすると plantuml server が見られる状態を https://example.com/plantuml に変えたいです。

Jetty は war の名前を変えると対応できるので、以下の Dockerfile を書きます。

FROM plantuml/plantuml-server:jetty

ARG BASE_URL=plantuml
RUN mv /var/lib/jetty/webapps/ROOT.war /var/lib/jetty/webapps/$BASE_URL.war

(ROOT.war は特殊なファイル名のようです。https://example.com/ROOT ではなく https://example.com へのアクセスで公開されるアプリケーションになります。)

docker-compose.yml への追加

docker-compose.yml からの相対パスplantuml/Dockerfile の位置に、先程の Dockerfile を配置します。

docker-compose.yml に plantuml サービスを追加します。

plantuml:
  build: ./plantuml/
  restart: always

また、nginx サービスに plantuml へのネットワーク接続を追加します。

 nginx:
   image: nginx:1.14.2-alpine
   volumes:
     - ./files/nginx/etc/letsencrypt:/etc/letsencrypt
     - ./nginx/conf.d:/etc/nginx/conf.d
   ports:
     - 192.0.2.146:443:443
   restart: always
+  links:
+    - plantuml

docker-compose を再起動します。

docker-compose build && (docker-compose down; docker-compose up -d)

nginx 設定の修正・適用

nginx/conf.d/default.conf を修正します。

-        proxy_pass http://192.0.2.170:8080;
+        proxy_pass http://plantuml:8080;

nginx プロセスの設定を更新します。

docker-compose exec nginx nginx -s reload

疑問点

plantuml/plantuml-server:jetty の Dockerfile では ARG でサブディレクトリーが指定されています。 docker-compose で args を指定できるのは build (image ではなく) のときなので使いませんでした。

docker-compose.yml で完結せずに Dockerfile を作らないといけないのは収まりが悪いですが、実はどうにかできるのでしょうか。


  1. 192.0.2.0/24 は例示用 IPv4 アドレス https://tools.ietf.org/html/rfc5737

apt-cacher-ng を回避して Docker をインストールする

要約

apt-cacher-ng 環境下では download.docker.com へ接続できず Docker をインストールできないので

echo 'Acquire::HTTP::Proxy::download.docker.com "DIRECT";' > /etc/apt/apt.conf.d/70docker

して直接ダウンロードするようにしましょう。

なぜ書くか

毎回毎回忘れていて Twitter で 「docker from:nogiro_iota」を検索して↓を探すハメになるので自分用メモとして書いています。


本題

パッケージマネージャー apt には、パッケージのダウンロードをプロキシしてキャッシュしてくれる仕組みの apt-cacher-ng があります。
apt-cacher-ng 環境下で Docker をインストールすると download.docker.com へ接続できずに失敗します。

以下の公式のインストール手順にしたがって進めていると
docs.docker.com
INSTALL DOCKER ENGINE - COMMUNITY セクションの apt update が失敗します。(add-apt-repository の次)

$ sudo apt update
Err:1 https://download.docker.com/linux/debian buster InRelease
  Reading from proxy failed - read (115: Operation now in progress) [IP: 192.0.2.0 3142]
Hit:2 http://deb.debian.org/debian buster InRelease
Hit:3 http://deb.debian.org/debian buster-updates InRelease
Hit:4 http://security.debian.org/debian-security buster/updates InRelease
Reading package lists... Done
Building dependency tree
Reading state information... Done
All packages are up to date.
W: Failed to fetch https://download.docker.com/linux/debian/dists/buster/InRelease  Reading from proxy failed - read (115: Operation now in progress) [IP: 192.0.2.0 3142]
W: Some index files failed to download. They have been ignored, or old ones used instead.

当時調べたところ、apt の設定で apt-cacher-ng を回避する方法が出てきました。
github.com

ので設定します。(Twitter からコピーすると http:// がついてめんどかったのですよね。)

$ echo 'Acquire::HTTP::Proxy::download.docker.com "DIRECT";' > /etc/apt/apt.conf.d/70docker

apt update が成功します。

$ sudo apt update
Hit:1 http://deb.debian.org/debian buster InRelease
Get:2 http://deb.debian.org/debian buster-updates InRelease [49.3 kB]
Get:3 https://download.docker.com/linux/debian buster InRelease [44.4 kB]
Get:4 https://download.docker.com/linux/debian buster/stable amd64 Packages [10.3 kB]
Hit:5 http://security.debian.org/debian-security buster/updates InRelease
Fetched 104 kB in 1s (79.5 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
All packages are up to date

後書き

この記事を書いているときに調べたところ、HTTPS だと失敗するようです。
wiki.debian.org

apt-cacher-ng 側の設定で回避できそうなのでストレスフリーになりそうです。

第41回シェル芸勉強会 - Python ワンライナー版解答

第 41 回シェル芸勉強会の問題の Python ワンライナーでの解答です。
真面目なほうは 第41回シェル芸勉強会に参加しました。 - nogiro_iotaのメモ を参照してください。

けっこう改訂しています。(解説も追加する予定です。しました。)
改訂履歴を最下段に載せています。

まとめ

python3 -c 'import sys; [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:hoge)]'hoge をいじれば標準入力を好き勝手ソートできる。

全体に共通する解説

第 41 回シェル芸勉強会はソート回とのことでした。
Python でのソートは sort()sorted() の 2 種類です。この記事中の回答ではすべてsorted()を使用しています。sorted()は引数として iterable なオブジェクト (sys.stdin やリスト) を受け取って、ソート済みのリストを返すためワンライナーで扱い易いためです。(sort() はリスト型クラスのメソッドで、呼ぶとリスト内部をソートして返り値は None になります。)

printf デバッグ

Python は短絡評価してくれて print() は None を返すので、値を見たい部分で print(hoge) or hoge すると printf デバッグできます。

以下は A4-2 を例にしています。

$ python3 -c '[print(x.strip()) for x in sorted(open("ShellGeiData/vol.41/sjis", "r", encoding="cp932"), key=lambda x:print(int(x.split()[0])) or int(x.split()[0]))]'
123
31
20
2242
20 ほじぱんふんじこみ
31 こきたてひーひー
123 ずんごるももう
2242 うえってきたかるとらまん

問題と Python ワンライナー版解答

sorted() の key でソート基準を決める関数を渡せるのでけっこう簡単でした。

Q1

 次のファイルについて、2列目をキーにしてエクセルの横列の記号(A, B, ..., Z, AA, AB, ...のやつ)順に並べ替えてください。

$ cat excel
114514 B
1192296 AA
593195 CEZ
4120 TZ
999 QQQ
A1
$ cat ShellGeiData/vol.41/excel | python3 -c 'import sys; [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:("%%%"+x.split()[1])[-4:])]'
114514 B
593195 AA
4120 TZ
1192296 CEZ
999 QQQ

解説

解き方自体は前記事と同じです。
列番号は x.split()[1] で得られるので、前部分に"%%%"をつけて後ろ 4 文字 ([-4:]) を返すようにします。

Q2

 次のファイルのレコードを干支順にソートしてください。

$ cat eto_yomi
申 さる
子 ね
寅 とら
卯 う
巳 み
辰 たつ
丑 うし
酉 とり
戌 いぬ
亥 い
午 うま
未 ひつじ

ただし、次のファイルを補助に使って良いこととします。

$ cat eto
子丑寅卯辰巳午未申酉戌亥
A2
$ cat ShellGeiData/vol.41/eto_yomi | python3 -c 'import sys; [print(x[:-1]) for x in sorted(sys.stdin, key = lambda x:open("ShellGeiData/vol.41/eto", "r").read().index(x.split()[0]))]'
子 ね
丑 うし
寅 とら
卯 う
辰 たつ
巳 み
午 うま
未 ひつじ
申 さる
酉 とり
戌 いぬ
亥 い

解説

補助ファイルを文字列として持てば、その index でソートすればキレイにできます。

ただ上の回答だと、eto_yomi を 1 行読むたびに eto を読み直しているのでファイルが大きくなると遅くなりそうですね。。。
以下のように、先に変数に入れておけば良いですね。

cat ShellGeiData/vol.41/eto_yomi | python3 -c 'import sys; eto = open("ShellGeiData/vol.41/eto", "r").read(); [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:eto.index(x.split()[0]))]'

Q3

 次のファイルのレコードを数字(第一フィールドの計算結果)が小さい順に並べてください。

$ cat kim_calc
1+2+4 金正日
4*3 金正男
3-1-5 金日成
495/3 金正恩
0x1F 金正哲
A3
$ cat ShellGeiData/vol.41/kim_calc | python3 -c 'import sys;[print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval(x.split()[0]))]'
3-1-5 金日成
1+2+4 金正日
4*3 金正男
0x1F 金正哲
495/3 金正恩

解説

eval() がつよい。

Q4

 次のファイルはシフトJISのテキストですが、これを1) 辞書順、2) 数字の小さい順、にソートしてください。出力もシフトJISとします。

$ cat sjis | nkf -g
Shift_JIS
$ cat sjis | nkf -wLux
123 ずんごるももう
31 こきたてひーひー
9 ほじぱんふんじこみ
2242 たまもとやろう
A4-1
$ python3 -c 'import codecs;[print(x.strip()) for x in sorted(codecs.open("ShellGeiData/vol.41/sjis", "r", "cp932"))]'
123 ずんごるももう
20 ほじぱんふんじこみ
2242 うえってきたかるとらまん
31 こきたてひーひー

解説

PythonSJIS のファイルを読み込むには codecs.open() を使うか open()encoding を渡します。

A4-2
$ python3 -c '[print(x.strip()) for x in sorted(open("ShellGeiData/vol.41/sjis", "r", encoding="cp932"), key=lambda x:int(x.split()[0]))]'
20 ほじぱんふんじこみ
31 こきたてひーひー
123 ずんごるももう
2242 うえってきたかるとらまん

解説

Python では全角の数字を int にキャストできるようです。すごい。

Q5

 サイズの小さい順にソートしてください。

$ cat size 
2GB
1.2GB
40000MB
1000000000kB
0.4GB
410MB
A5
$ cat ShellGeiData/vol.41/size | python3 -c 'import sys; d = {"k":1,"m":2,"g":3}; [print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])))]'
0.4GB
410MB
1.2GB
2GB
40000MB
1000000000kB

解説

SI 接頭辞をディクショナリで定義して (d = {"k":1,"m":2,"g":3})、その値の数 "*1000" をつけたものを eval() しています。

想像しにくいように思うので、 eval() で計算する前の文字列を出力したものを以下に貼ります。(改行していますが cat()])))]' までコピペで動きます。)

$ cat ShellGeiData/vol.41/size | python3 -c 'import sys; d = {"k":1,"m":2,"g":3}; [print(x.strip()) for x in sorted(sys.stdin, key=lambda x:
print("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])) or 
 eval("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])))]'
2*1000*1000*1000
1.2*1000*1000*1000
40000*1000*1000
1000000000*1000
0.4*1000*1000*1000
410*1000*1000
0.4GB
410MB
1.2GB
2GB
40000MB
1000000000kB

途中で出てくる for j in [x[:-2].lower()] はリスト内包表記内で変数を使う方法です。

Q6 (省略)

bash 内部コマンドは Python ワンライナーから呼べないため省略します。

Q7

 次のローマ数字をソートしてください。

$ cat roman
IV
XI
LXXXIX
IX
XLIII
XX
VIII
A7
$ cat ShellGeiData/vol.41/roman | python3 -c 'import sys;d={"I":1,"V":5,"X":10,"L":50};[print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:eval("+".join([[a[j]+"*-1" if j < len(a) - 1 and a[j] < a[j+1] else a[j] for j in range(len(a))] for a in [[str(d[i]) for i in x[:-1]]]][0])))]'
IV
VIII
IX
XI
XX
XLIII
LXXXIX

解説

A5 と大きく変わっていません。

IVXL などの減算則の部分に "*-1" をつける処理 ([a[j]+"*-1" if j < len(a) - 1 and a[j] < a[j+1] else a[j] for j in range(len(a))]) が主な違いです。

Q8

 次のファイルを辞書順にソートしてください。ただし、濁点がついているものが先に来るようにしてください。できる人はワンライナー中で「かきくけこがぎぐげご」の文字を使わないでください。

$ cat gagigugego 
かき氷
ぎ・おなら吸い込み隊
きつねうどん
ぐりこもりなが事件
きききりん
がきの使い
くその役にも立たない
げんしりょく発電
ごりらいも
こじんてきにはクソ
がきの使い
かき氷
ぎ・おなら吸い込み隊
きききりん
きつねうどん
ぐりこもりなが事件
くその役にも立たない
げんしりょく発電
ごりらいも
こじんてきにはクソ
A8
$ cat ShellGeiData/vol.41/gagigugego | python3 -c 'import sys; from unicodedata import normalize; c=u"\U00003099"; [print(x[:-1]) for x in sorted(sys.stdin, key=lambda x:["".join(c in a and a.insert(a.index(c), u"\U00000000") or a) for a in [[i for i in normalize("NFD",x)]]])]'
がきの使い
かき氷
ぎ・おなら吸い込み隊
きききりん
きつねうどん
ぐりこもりなが事件
くその役にも立たない
げんしりょく発電
ごりらいも
こじんてきにはクソ

解説

Python だと unicodedata.normalize()Unicode 正規化できます。

そのままソートすると期待どおりにソートされないので、濁点 (u"\U00003099") の前に小さな値 (u"\U00000000") を追加してソートしています。

改訂

  • A8. a[a.index(c):]+[u"\U00000000"] + a[:a.index(c)] -> a.insert(a.index(c), u"\U00000000")
  • A2. [j for j in [i for i in open("ShellGeiData/vol.41/eto", "r")][0]] -> [i for i in open("ShellGeiData/vol.41/eto", "r")][0]
  • A1.lambda x:("%%%"+x[1])[:-4] -> lambda x:("%%%"+x[1])[-4:]

eban on Twitter: "最後は[-4:]じゃないでしょうか?… "

  • 全体. sorted([i for i in sys.stdin], key= -> sorted(sys.stdin, key=
  • まとめを追加
  • A2. [i for i in open("ShellGeiData/vol.41/eto", "r")][0] -> open("ShellGeiData/vol.41/eto", "r").read()
  • A5. SI接頭辞をディクショナリで定義して汎用性を上げました
    • 前.
      cat ShellGeiData/vol.41/size | python3 -c 'import sys;[print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval(x[:-2].replace("k","*1000").replace("M","*1000"*2).replace("G","*1000"*3)))]'
    • 後.
      cat ShellGeiData/vol.41/size | python3 -c 'import sys; d = {"k":1,"m":2,"g":3}; [print(x.strip()) for x in sorted(sys.stdin, key=lambda x:eval("".join([j.replace(i,"*1000" * d[i]) if i in j else "" for j in [x[:-2].lower()] for i in d.keys()])))]'
  • A7. 減算則が適用されるのが、最初にある場合のみになっていたのを修正しました。 (LXXXIX が 89 ではなく 91 に計算されていた。)
    • 前.
      eval("+".join([[a[0]+"*-1"]+a[1:] if a[0] < a[1] else a for a in [[str(d[i]) for i in x[:-1]]]][0])) or x
    • 後.
      eval("+".join([[a[j]+"*-1" if j < len(a) - 1 and a[j] < a[j+1] else a[j] for j in range(len(a))] for a in [[str(d[i]) for i in x[:-1]]]][0]))
  • 解説を追加

第41回シェル芸勉強会に参加しました。

第41回シェル芸勉強会の参加レポートです。
だいぶ久々に参加させていただいたように思います。

午前の部

ついに文字コードのお話が最終回になりました。今回は主に Unicode の結合文字列の話でした。

結合文字列をざっくり言うと、 Unicode 上では同じ「ポ」を表すにも以下の 2 通りの方法があるということです。

  • ポ: U+30DD
  • ホ + ゜(半濁点): U+30DB U+309A

また、上の単一のコードでひとつの文字になる表現を合成済み文字、下の複数コードの組み合わせで表現するものを結合文字列と呼びます。

同一文書中に符号化方式が混在していると不便なので統一することを Unicode 正規化といい、合成済み文字に統一した形式を NFC、結合文字列に分解した形式を NFD と言います。
CLIUnicode 正規化するには uconv が便利 (uconv -x NFDuconv -x NFC) ということを知りました。

$ echo -n ポ | uconv -x NFC | iconv -t ucs-2be | xxd -ps
30dd
$ echo -n ポ | uconv -x NFD | iconv -t ucs-2be | xxd -ps
30db309a

余談ですが結合文字列といえば Mac OS X がめんどくさい印象です。Windows から OS X に濁点 (・半濁点) がファイル名に入ったファイルを送って、編集して送り返してもらうと濁点が分割されます。
メールなどであれば、おそらくファイル名を変更してやり取りするのでまだ良いですが、DropBox などのファイル同期を取るソフトウェアで OS が混在すると同名のファイルが 2 つできるという悪夢が発生します。
しかも保存しないと増えないのでタイミングがよくわからなくなって原因究明が大変です。

問題と私の回答

私の回答のうち、 Twitter のものはその場での回答です。
pre タグで囲まれている部分は記事執筆の際に WSL で再度実行しています。

Q1

 次のファイルについて、2列目をキーにしてエクセルの横列の記号(A, B, ..., Z, AA, AB, ...のやつ)順に並べ替えてください。

$ cat excel
114514 B
1192296 AA
593195 CEZ
4120 TZ
999 QQQ
A1


$ cat ShellGeiData/vol.41/excel | xargs printf '%s %4s\n' | tr \  % | sed 's#%%# %#' | sort -k2 | tr -d %
114514 B
593195 AA
4120 TZ
1192296 CEZ
999 QQQ

解説

列番号の桁数を合わせてソートしたらできました。

他の方の回答を見ていると、 awk の length 関数で列番号の桁数を求める解法が多かったみたいですね。

Q2

 次のファイルのレコードを干支順にソートしてください。

$ cat eto_yomi
申 さる
子 ね
寅 とら
卯 う
巳 み
辰 たつ
丑 うし
酉 とり
戌 いぬ
亥 い
午 うま
未 ひつじ

ただし、次のファイルを補助に使って良いこととします。

$ cat eto
子丑寅卯辰巳午未申酉戌亥
A2.1


$ cat ShellGeiData/vol.41/eto | grep -o . | sed 's#.#grep & ShellGeiData/vol.41/eto_yomi#e'
子 ね
丑 うし
寅 とら
卯 う
辰 たつ
巳 み
午 うま
未 ひつじ
申 さる
酉 とり
戌 いぬ
亥 い

解説

sed の e を外すとなにをしたかわかるかと思います。

$ cat ShellGeiData/vol.41/eto | grep -o . | sed 's#.#grep & ShellGeiData/vol.41/eto_yomi#'
grep 子 ShellGeiData/vol.41/eto_yomi
grep 丑 ShellGeiData/vol.41/eto_yomi
grep 寅 ShellGeiData/vol.41/eto_yomi
grep 卯 ShellGeiData/vol.41/eto_yomi
grep 辰 ShellGeiData/vol.41/eto_yomi
grep 巳 ShellGeiData/vol.41/eto_yomi
grep 午 ShellGeiData/vol.41/eto_yomi
grep 未 ShellGeiData/vol.41/eto_yomi
grep 申 ShellGeiData/vol.41/eto_yomi
grep 酉 ShellGeiData/vol.41/eto_yomi
grep 戌 ShellGeiData/vol.41/eto_yomi
grep 亥 ShellGeiData/vol.41/eto_yomi
A2.2


$ cat ShellGeiData/vol.41/eto_yomi | awk '{print "/"$1"/s_.*_"$0"_"}' | sed -f - <(sed 's_._&\n_g' ShellGeiData/vol.41/eto)
子 ね
丑 うし
寅 とら
卯 う
辰 たつ
巳 み
午 うま
未 ひつじ
申 さる
酉 とり
戌 いぬ
亥 い

解説

grepを使わないことを目標にしました。

awksed のパターンを作って、eto を縦にしたものに適用しています。

感想

以下のようにしたら grep -o . みたいなことしてる部分も消せますね。

$ cat ShellGeiData/vol.41/eto_yomi | awk '{print "s_"$1"_"$0" _"}' | sed -f - ShellGeiData/vol.41/eto | xargs -n 2
子 ね
丑 うし
寅 とら
卯 う
辰 たつ
巳 み
午 うま
未 ひつじ
申 さる
酉 とり
戌 いぬ
亥 い

A2.1 の sed 部分を xargs でやるのが方がいました。そっちのほうがスマートですね。

Q3

 次のファイルのレコードを数字(第一フィールドの計算結果)が小さい順に並べてください。

$ cat kim_calc
1+2+4 金正日
4*3 金正男
3-1-5 金日成
495/3 金正恩
0x1F 金正哲
A3


$ cat ShellGeiData/vol.41/kim_calc | sed 's_\(.*\) \(.*\)_echo $((\1)) \1 \2_e' | sort -n | cut -d\  -f2-3
3-1-5 金日成
1+2+4 金正日
4*3 金正男
0x1F 金正哲
495/3 金正恩

解説

まずbcで計算しようとしたところ0x1Fが変換できませんでした。

$ cat ShellGeiData/vol.41/kim_calc | cut -d\  -f1 | bc
7
12
-3
165
(standard_in) 5: syntax error

bash で式評価すれば 0x 形式を変換できたのでそうしました。

感想

printf でも 0x の変換はできますが計算はできないので、今回の問題では手順が増えますね。

$ printf %d\\n 0x1F
31

Q4

 次のファイルはシフトJISのテキストですが、これを1) 辞書順、2) 数字の小さい順、にソートしてください。出力もシフトJISとします。

$ cat sjis | nkf -g
Shift_JIS
$ cat sjis | nkf -wLux
123 ずんごるももう
31 こきたてひーひー
9 ほじぱんふんじこみ
2242 たまもとやろう
A4-1


A4-2


$ cat ShellGeiData/vol.41/sjis | iconv -f cp932 | sed 'y/01234/01234/' | sort -n | sed 'y/01234/01234/'
20 ほじぱんふんじこみ
31 こきたてひーひー
123 ずんごるももう
2242 うえってきたかるとらまん

解説

cp932 は
Microsoftコードページ932 - Wikipedia のことで、 Windows の Shift JIS 拡張コードです。
Shift JIS と言ってるときは、ほとんどの場合「Windows で作られた」「Windows で表示したい」ことを指してるので cp932 を指定するのが無難だと思っています。

Q5

 サイズの小さい順にソートしてください。

$ cat size 
2GB
1.2GB
40000MB
1000000000kB
0.4GB
410MB
A5


$ cat ShellGeiData/vol.41/size | sed p | sed '2~2s_.*_echo & | tr -d B | tr k K | numfmt --from=si_e' | paste - - | sort -k2 -n | cut -f1
0.4GB
410MB
1.2GB
2GB
40000MB
1000000000kB
感想

以下のツイートを見てnumfmtを知ったのでそのまま使ったら解けました。


Q6

sleepと内部コマンドだけを使って次の数を小さい順にソートしてください。

$ cat nums
5.4
0.34
2.3
0.9
6
A6


$ (while read a; do (sleep $a; echo $a) & done) < ShellGeiData/vol.41/nums
$ 0.34
0.9
2.3
5.4
6

解説

スリープソートです。スリープソートはバケットとしてタイムスロットを使うバケットソートの一種と見なせるそうです。
バケットソート - Wikipedia

非同期で動くので表示がずれるのが気になる場合はwaitをかませば良いです。() でサブシェル内で実行すればジョブ開始のメッセージも出ないのでより見やすくなります。

$ (while read a; do (sleep $a; echo $a) & done && wait) < ShellGeiData/vol.41/nums
0.34
0.9
2.3
5.4
6
感想

sleepソートを見た瞬間スリープソートだと思いました。

Q7

 次のローマ数字をソートしてください。

$ cat roman
IV
XI
LXXXIX
IX
XLIII
XX
VIII
A7


$ cat ShellGeiData/vol.41/roman | sed -E 'p;s_I([XV])_(-1)\1_;s_X([LC])_(-10)\1_;s_I_(1)_g;s_V_(5)_g;s_X_(10)_g;s_L_(50)_g' | sed 's_)(_)+(_g; 2~2s_.*_echo "&" | bc_e' | paste - - | sort -k2 -n | cut -f1
IV
VIII
IX
XI
XX
XLIII
LXXXIX

解説

アラビア数字への変換は以下の手順で行いました。

  1. 減算則の部分 (IVXL など) を先に負の値に置き換える
  2. 他の記号を対応する正の値に置き換える
  3. 総和を求める式に変形する
  4. 計算する

上記手順を偶数列で行って、paste - - で行を揃えてソートする。という流れですね。

今見ると seds_I([XV])_(-1)\1_;s_X([LC])_(-10)\1_ 部分を s_I[XV]_(-1)*&_;s_X[LC]_(-1)*&_; に置き換えたいですね。つまり、負の値そのものに置き換えるのではなく -1 をかけるのでもできます。
動作としては、以下の途中結果がわかりやすいかと思います。

$ cat ShellGeiData/vol.41/roman | sed 'p;s_I[XV]_(-1)*&_;s_X[LC]_(-1)*&_;s_I_(1)_g;s_V_(5)_g;s_X_(10)_g;s_L_(50)_g' | sed 's_)(_)+(_g; 2~2s_.*_echo "&" | bc_'
IV
echo "(-1)*(1)+(5)" | bc
XI
echo "(10)+(1)" | bc
LXXXIX
echo "(50)+(10)+(10)+(10)+(-1)*(1)+(10)" | bc
IX
echo "(-1)*(1)+(10)" | bc
XLIII
echo "(-1)*(10)+(50)+(1)+(1)+(1)" | bc
XX
echo "(10)+(10)" | bc
VIII
echo "(5)+(1)+(1)+(1)" | bc
感想

手元では動いていたのですがシェル芸 bot では動いていませんでした。
Ubuntu の sh が dash だったのが動かなかった理由だったようです。

numconv がすごい便利そうでした。珍しく AUR になかったのでその場では試せませんでした。 (AUR: Arch Linux の半公式ユーザーリポジトリ。ソースからビルドしたり、.debを展開してインストールするエキセントリックなパッケージがあったりする。)

numconv だと以下のように解けます。

$ cat ShellGeiData/vol.41/roman | sed 'p; s_.*_echo & | numconv_e' | paste - - | sort -k2n | cut -f1
IV
VIII
IX
XI
XX
XLIII
LXXXIX

Q8

 次のファイルを辞書順にソートしてください。ただし、濁点がついているものが先に来るようにしてください。できる人はワンライナー中で「かきくけこがぎぐげご」の文字を使わないでください。

$ cat gagigugego 
かき氷
ぎ・おなら吸い込み隊
きつねうどん
ぐりこもりなが事件
きききりん
がきの使い
くその役にも立たない
げんしりょく発電
ごりらいも
こじんてきにはクソ
がきの使い
かき氷
ぎ・おなら吸い込み隊
きききりん
きつねうどん
ぐりこもりなが事件
くその役にも立たない
げんしりょく発電
ごりらいも
こじんてきにはクソ
A8


$ cat ShellGeiData/vol.41/gagigugego | uconv -x NFD | iconv -t utf16be | xxd -ps | tr -d \\n | sed 's_000a_&\n_g' | sed 's_...._& _g' | sed 's_3099 _-&_g' | sort -n | sed 's_-3099_3099_g' | xxd -r -p | iconv -f utf16be | uconv -x NFC
がきの使い
かき氷
ぎ・おなら吸い込み隊
きききりん
きつねうどん
ぐりこもりなが事件
くその役にも立たない
げんしりょく発電
ごりらいも
こじんてきにはクソ

解説

手順は以下です。

  1. NFD にする (結合文字列に分解する)
  2. UTF-16 のビッグエンディアンに変換する
  3. 16 進表現に変換する
  4. 濁点 (UTF-16 では 0x3099) をソートで上位に並ぶように置き換える (3099 -> -3099)
  5. ソートする
  6. 濁点を戻したり (-3099 -> 3099) など、いろいろ逆手順で戻す
    • uconv -x NFD | iconv -t utf16be | xxd -ps | ... | sed 's_3099 _-&_g' -> sed 's_-3099_3099_g' | xxd -r -p | iconv -f utf16be | uconv -x NFC

その場では -3099 ではなく 13099 に置き換えていましたが、U+0xxx の文字が 2 文字目にあると期待どおりにソートされないので -3099 のほうが汎用的です。

感想


ってツイートを悠長に打ってる間に答え
が出るという不思議体験を味わいました。

U+3099(゛) が先にソートされる理由がよくわかりませんが・・・・・・

総括

久々の参加だったのですが、今回は比較的簡単で良かったです。(スリープソート以外は「これを知ってないと解けない」問題がないので。)
簡単でしたがそれでも疲れるのと、相変わらず全然知らないこと (uconvnumfmtnumconv など) が出てくるのがすごいです。

あと、もとの表現を残したまま計算したいときに sed 'p;s/.*/echo & | hoge/e' | paste - - のパターンが使えますね。A5・A7で使用しています。