次ログ

次ログ

プログラミング系の記事多め。GoやJavaFXで自作のツールを公開したり、イラストを配布してます。

第42回 シェル芸勉強会振り返り

まえがき

2019/06/15に「jus共催 第42回BLACK HOLEシェル芸勉強会」へ参加してきたのでその振り返り。 今回の名前は前回のブレース展開と違って普通の名前だった。

いわくここでいうBLACK HOLEは「アトランティスの謎 42th」を指すものだったらしい。 僕はアトランティスの謎というゲームを知らなかったけれど、ニコ動の何かの動画で 無限に落下するシーンを見たことはあった。

シェル芸の様子はこちら。

togetter.com

問題

Q1 正の整数の組(x, y, z)について、x + xy + xyz = 1234, x < y < zを満たす組み合わせを全て選んでください

競技プログラミング的な問題。普通に3重ループで実装したら計算量が 12343 になってとんでもないことになる。 例えば以下のような。(僕の回答)

for x in {1..1234}; do
  for y in {1..1234}; do
    for z in {1..1234}; do
      n=$((x + x*y + x*y*z))
      if [ $n -eq 1234 ]; then
        echo "x=$x y=$y z=$z"
      fi
    done
  done
done

一応この回答でも全然終了しないけれどそのうち計算は終了する。 けれど、それではこの問題を用意した意図に応えられていない。

x + xy + xyz == 1234 and ( x < y < z ) というこの問題、ぱっと問題を見た時ではわからなかったけれど、これは普通の数学の問題だった。

x + xy + xyz == 1234を式変形するとx(1 + y + yz) == 1234と等価になる。

これを更に式変形するとx(1 + y(1 + z))になる。

これはx * N == 1234と言い換えられるが、このNが何通り考えられるかを考える。 この1234を素因数分解すると、次のようになる。

% factor 1234
1234: 2 617

このことからxとNの組み合わせは次の通り考えられる。

  • x == 1 のとき N == 1234
  • x == 2 のとき N == 617
  • x == 617 のとき N == 2
  • x == 1234 のとき N == 1

しかし、前提条件として(x < y < z)が与えられているため、最終的に次のようになる。

  • x == 1 のとき N == 1234
  • x == 2 のとき N == 617

この時点で総計算量を比較してみる。

  • 1234 x 1234 x 1234 = 1879080904 (18億7908万)
  • 2 x 1234 x 1234 = 3045512 (304万)

304万オーダーならまぁ普通に計算できる計算量なので、僕の最初に考えたシェルは次のように修正できる。

for x in {1..2}; do
  for y in {1..1234}; do
    for z in {1..1234}; do
      n=$((x + x*y + x*y*z))
      if [ $n -eq 1234 ]; then
        echo "x=$x y=$y z=$z"
      fi
    done
  done
done

awkを使うともっと短くかける。

echo {1,2}" "{1..1234}" "{1..1234}"\n" | awk '$1 < $2 && $2 < $3 && $1 + $1 * $2 + $1 * $2 * $3 == 1234'

Q2 次のファイルの文章内の絵文字を全て💩に変えてください

$ cat oji
あれ(^_^;さのチャン、朝と夜間違えたのかな❗❓⁉俺はまだ起きてますよ〜😃 ちょっと電話できるかナ( ̄ー ̄?)⁉天気悪いと気分もよくないよね😱じゃあ今日は会社休んで俺とデートしヨウ💕ナンチャッテ🎵(笑)😘

僕のはじめの回答はこんなかんじ。あらゆる絵文字に対応できる方法が思いつかなくて、明らかに手抜きな回答になった。

cat /ShellGeiData/vol.42/oji | sed -E  "s@$(cat /ShellGeiData/vol.42/oji | grep -o . | sort | head -n 9 | tr '\n' '|')z@💩@g"

なにをやっているかというと、sed拡張正規表現の文字列を作っている。なんとなく sortして戦闘の文字列を確認したらいい感じに絵文字が先頭に偏っていたのでそれを9 文字決め打ちで取り出している。

% cat ShellGeiData/vol.42/oji | grep -o . | sort | head -n 20
❗
❓
⁉
⁉
😃
😱
💕
🎵
😘
 
(
;
?
^
^
_
、
 ̄
 ̄

これを正規表現のORでつなぐ。

% cat ShellGeiData/vol.42/oji | grep -o . | sort | head -n 9 | tr '\n' '|'
❗|❓|⁉|⁉|😃|😱|💕|🎵|😘|

すると正規表現の最後にORだけが残ってしまう。 めんどくさかったのでもとの文字列に出現していなかったzを付け足した。

% echo "s@$(cat ShellGeiData/vol.42/oji | grep -o . | sort | head -n 9 | tr '\n' '|')z@💩@g" 
s@❗|❓|⁉|⁉|😃|😱|💕|🎵|😘|z@💩@g

これをsed -Eに渡してやれば、絵文字をすべて💩に置換できる。 かなり雑だし問題しかない。 正しい回答はnkfawklength関数を使う方法。

上田先生のブログより引用。

$ grep -o . oji | LANG=C awk '{printf length($1)==4?"💩":$1}' | awk 4

Q3 (要約)素数のときに任意の値を出力してください

2: きつねうどん
3: ブラウンソース定食
4:
5: 鉄皿ブラウンソースハンバーグセット
6:
7: きつねうどん ミニプレミアムおろしポン酢牛めしセット
8:
9:
10:
11: 鉄皿ブラウンソースライス
12:
13: 担々うどん(プレミアム牛肉使用)
14:
15:
16:
17: とろたまうどん ミニポン酢ポン酢牛めしセット
18:
19: 豚カルビ丼
20:

僕の回答(ただしゴミが残ってるし、間違ってる)

% paste -d : <(seq 20) <(seq 20 | while read i; do factor $i | cut -d : -f 2- | awk -F " " '{print NF}'; done | sed -E 's@^1$@'"$(ojichat)"'@g')
1:0
2:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
3:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
4:2
5:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
6:2
7:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
8:3
9:2
10:2
11:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
12:3
13:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
14:2
15:2
16:4
17:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
18:3
19:吏花ちゃん、愛しいなぁモウ😘🎵(^з<)こんなに可愛く💕😄(^з<)なっちゃったら天使みたいでオジサン困っちゃウヨ(・_・;(^_^;
20:3

まず、ループしてfactorに引数としてわたして素因数分解する。

% seq 20 | while read i; do factor $i;done
1:
2: 2
3: 3
4: 2 2
5: 5
6: 2 3
7: 7
8: 2 2 2
9: 3 3
10: 2 5
11: 11
12: 2 2 3
13: 13
14: 2 7
15: 3 5
16: 2 2 2 2
17: 17
18: 2 3 3
19: 19
20: 2 2 5

この時、素数のものは:で区切られた右側の文字は1つしかないはず、と考えた。 よって、:で区切った2フィールド目の文字列のみを取得し、さらに空白区切るのフィールドの数を数えれば、素因数の数が取得できる。

% seq 20 | while read i; do factor $i | cut -d : -f 2- | awk -F " " '{print NF}'; done
0
1
1
2
1
2
1
3
2
2
1
3
1
2
2
4
1
3
1
3

あとは1だけをsedで置換すれば一応僕の回答にはなる。間違っているけれど。

% seq 20 | while read i; do factor $i | cut -d : -f 2- | awk -F " " '{print NF}'; done | sed -E 's@^1$@'"$(ojichat)"'@g'
0
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
2
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
2
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
3
2
2
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
3
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
2
2
4
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
3
えみこ、愛しいなぁもウ😃♥ (^o^)可愛すぎてボクお仕事に集中できなくなっちゃいそうだよ(◎ _◎;)どうしてくれるンダ😃✋
3

何が間違っているか、というと。

  1. 余計な数字が残っている
  2. ojichatの実行結果が毎回同じになっている。

この回答のだしかたが良くなかったのもあるけれど、この回答に会えて修正を入れるなら次のようにすればこの問題の解になる。

% paste -d : <(seq 20) <(seq 20 | while read i; do factor $i | cut -d : -f 2- | awk -F " " '{print NF}'; done | sed -E 's@^1$@ojichat@ge; s/^[0-9]+$//g')
1:
2:えいはチャン、可愛らしいネ😍😃♥ 可愛すぎてオジサンお仕事に集中できなくなっちゃいそうだよ(・_・;(T_T)(◎ _◎;)^^;どうしてくれるんダ😚(笑)❗
3:おはよー!チュッ(^з<)早く会いたいナ(^_^)😍❗
4:
5:センリチャン、お早う(^_^)😍😆センリチャンと一緒に今度ランチ、したいなァ(^o^)💗(^_^)風邪ひかないようにね😚(^_^)😆😘
6:
7:風邪ひかないようにね💕💗オイラはももきちゃん一筋だよ🤑✋
8:
9:
10:
11:美佐穂チャン、オッハー(^_^)(^o^)美佐穂チャンにとって素敵な1日になりますようニ😄
12:
13:今日も大変だったんだネ(-_-;)よく頑張ったね💕えらいえライ😄😃☀ 
14:
15:
16:
17:みゆきちゃん、可愛らしいね(^з<)(^_^)こんなに可愛く(^o^)😃♥ なっちゃったらお姫様みたいでオジサン困っちゃウヨ(・_・;(-_-;)😱( ̄Д ̄;;
18:
19:帆夏チャン、そっちも快晴なのかな😜⁉️水曜日は仕事〜❓( ̄ー ̄?)😜⁉️よく頑張ったね🎵(^_^)えらいえらイ(笑)😘
20:

修正したのはここ。

sed -E 's@^1$@ojichat@ge; s/^[0-9]+$//g'

いきなりojichatの実行結果をsedの置換式に埋め込むのではなく、ojichat自体を埋め込んでeで実行するようにした。 そのあと数字だけ残っている行を消す置換をすることで完成。

Q4 数字を打たずに3を出力してください

難読化シェル芸。僕の回答は下記。

echo $(( $(echo a | grep x)$? + $(echo a | grep x)$? + $(echo a | grep x)$? ))

grepで検索にマッチするものが存在しなかった時、終了コードは1になる。 それを$?で取得してbashの算術演算で加算している。

ただ他の方の回答ではより難読化しているものがあった。 面白かったのは以下。

% echo $(( $$/$$ + $$/$$ + $$/$$ )) 
3

$$はプロセスIDである。プロセスIDをプロセスIDで割ると当然1になる。あとはそれを加算している。

あとは僕が思いついたしょーもない回答。

$ unko.tower | wc | sed -E 's/|/ /g' | awk '{print $NF}'
3

なんなのかというとunko.towerの文字の数を数えたらたまたま一番最後の文字が3だったのでそれを取得している。 sedですべての文字を空白で区切り、awk$NFで一番最後の文字を取得した。

Q5 アルファベットを打たずにlsを実行してください

これも難読化。 パス補完の機能を使えばいいんだろうというのはわかったが、「アルファベットを使わずに」というのはわからなかった。 回答としてはこうなるらしい。

% echo /???/??
/bin/cp /bin/dd /bin/df /bin/ed /bin/ip /bin/ln /bin/ls /bin/mt /bin/mv /bin/nc /bin/ps /bin/rm /bin/sh /bin/ss /bin/su /dev/fd /etc/hp /etc/i3 /etc/pm /sys/fs


% __=(/???/??); ${__[7]}
oji

これはなるほど〜となった。 パス補完を配列として変数に追加して、決め打ちで配列のインデックスを指定して取得する、という。 lsが何番目に位置するかは環境によって変わるので、そこは適宜読み替える必要がある。

Q6 飛行機アイコンのみを左右反転させる

まさか僕が作ったtextimgというコマンドが問題の中で使われると思っていなくて驚いた。 なお問題は解けなかった。

問題の回答にのためにいろんな方がtextimgをインストールしようとして苦戦していたのが印象に残った。 インストールに時間がかかっていたようで、Go自体のインストールに時間がかかっていたのだと思われる。

あとは一応単体で動作する実行可能バイナリも配布していたが、絵文字とかの描画を可能にするにはさらに環境変数とかフォントを整える必要があるので おそらくそのあたりで躓いたのかなぁと考えている。

一応整備するべき環境変数についてはREADMEには書いておいたが、いきなりそれをやれというのはやはり難しいので 手軽に試せるDockerfileも用意しておこうと思った。 (Dockerを使えるようにするのが難しいとかはまた別の話)

Q7 素数の桁が変わる直前のところで改行を入れてください

$ seq 1000000 | factor | awk 'NF==2{print $2}' | tr \\n ' ' 
ここからパイプをつなげて、。つまり、桁数ごとに素数を1行に並べて出力してください。なお、awk、perl、rubyなどのプログラミングできるコマンドやbashのforやwhileなどの制御構文を用いて出力できた場合は、それらを使わずに出力してみてください。sedは可とします。

次のように確かめるとデバッグしやすいです。

$ seq 1000000 | factor | awk 'NF==2{print $2}' | (解答) | awk '{print $1,$NF}'
2 7
11 97
101 997
1009 9973
10007 99991
100003 999983

僕の回答は次の通り。数が決め打ちなので汚い。

$ seq 1000000 | factor | awk 'NF==2{print $2}' | tr \\n ' ' | sed -E 's/ ([0-9]{2}) ([0-9]{3})/ \1\n \2/g; s/ ([0-9]{3}) ([0-9]{4})/ \1\n \2/g; s/ ([0-9]{4}) ([0-9]{5})/ \1\n \2/g; s/ ([0-9]{5}) ([0-9]{6})/ \1\n \2/g' | awk '{print $1, $NF}'
2 97
101 997
1009 9973
10007 99991
100003 999983

ようは数字の連続する長さが切り替わるタイミングで改行を入れる、というアプローチ。 sedでいい感じにやる方法が思いつかなかった。

他の方の回答でgrepを使うのがすごくきれいでよかった。

seq 1000000 | factor | awk 'NF==2{print $2}' | tr \\n ' ' | grep -Eo -e'(([0-9]{'{1..6}'} )*)'

Q8 これをなるべく忠実に作ってみてください。

このあたりの画像系のシェル芸についていけてなくて遅れを感じている。

LT会

今回のLT会は名言の宝庫だった。 名言だと思ったもの。

  • くだしンス
  • ふつうの難読化
  • これはdateコマンドです
  • 難読化シェル芸のアンダーバー派
  • 好きなVimVim
  • Tab補完は軟派
  • 娘のChangeLog

まとめ

ということで今回の問題の僕の正答率は 3 / 8 。今回は難しかった。 今回は難読化、画像、数学とバリエーション豊かだったと感じた。

前回のsort系は得意なほうだった。 逆に今回は未知の問題ばかりだったので苦戦したが 解きほぐしてみればなるほどな、というものばかりだった。

Linux環境のRPGツクールMVで日本語入力できなかった原因を特定した

ちょっと前からLinux環境のRPGツクールMV(Steam版)で日本語入力ができなくなっていた。 昨日気分転換にプライベートPCのOSをManjaroLinuxからLinuxMint Cinnamonに変更して、 ツクールも入れ直して動作確認したらやっぱり動かない。

そもそもなんでこれ動かないんだろう数年前は動いていた気がするのになぁ・・・ と思い、本腰いれて原因を調査した。

ここではどういう過程で調査したのかを順に記録する。

前提情報

まず、僕は日本語入力のIMEの仕組みとかを何も知らない。 Linux環境で日本語入力を可能にするためのfcitxだのibus-mozcだのも Webの知識をコピペして設定して動かしている。 よって何もそのあたりの細かい仕様について知らない。

知らないなりに色々仮説を立てて調査したので トンチンカンなこと言っている可能性は大いにあります。

調査

そもそも日本語入力できるのか

ツクールMVの文章入力の画面で全角半角変換のキーを入力しても反応しない。 アルファベットはそのまま普通に入力できた。

しかしながら、エディタやブラウザなどで日本語を入力してから コピペで貼り付けたら日本語を入力できた。 よって日本語を入力できる環境自体はあると考えた。

キーボードレイアウトをUS配列から日本語配列に変更

僕はHHKBのUS版を使っているので、キーボードレイアウトもUSに変更している。 日本語変換もfcitx + ibusで右altと左altで日本語英語を切り替えるようにしている。 この設定が何か問題になっているのでは、と考えた。

例えば、Altキーで日本語に変換しようとしてメニューバーの方にフォーカスを奪われて いたりするかもしれない、と考えたためである。

試しで日本語配列に変更し、素直に全角半角キーで変更するように設定を戻してみたが、 結果は変わらなかった。

類似情報がないか調べる

だいたいこういうのは他の人も詰まっているだろう、と思ってGoogle検索や Steamのフォーラムで色々調べてみた。

「SteamはGoogleIMEでは日本語入力できないけれど、MS IMEなら入力できる」 みたいな情報も見つかりはしたが、ツクールには逆に全くそれっぽい情報が見つからなかった。

起動オプションを疑う

Steamからゲームを起動する時、ゲーム起動時にオプションを渡すことができる。 上級者向けの機能で、僕は全く使っていない。名前だけ知っていた。

起動時のオプションとかでIMEオフみたいなのがもしかしたらあるかも、と思い プロセスの起動オプションを調べてみることにした。

% ps aux | grep -i rpg
jiro4989  5530  0.0  0.0   4644   272 ?        S    13:10   0:00 /bin/sh -c '/home/jiro4989/.steam/steam/steamapps/common/RPG Maker MV/RPG Maker MV.sh'
jiro4989  5532  0.0  0.0   4644   268 ?        S    13:10   0:00 /bin/sh /home/jiro4989/.steam/steam/steamapps/common/RPG Maker MV/RPG Maker MV.sh
jiro4989  5537 12.0  2.0 4020776 245376 ?      Sl   13:10   0:02 /home/jiro4989/.steam/steam/steamapps/common/RPG Maker MV/RPG Maker MV
jiro4989  5550  0.0  0.2 287016 31356 ?        S    13:10   0:00 /home/jiro4989/.steam/steam/steamapps/common/RPG Maker MV/libexec/QtWebEngineProcess --type=zygote --no-sandbox --lang=C

すると、「QtWebEngineProcess」というプロセスが目に止まった。 ツクールMVはQtで作られている可能性があると考えた。

エディタ側のライブラリを調べる

IMEを実装したことないので想像で話すが、 いろんな言語に対応する必要のあるIMEなんて僕ならとても実装したくない。 既存でライブラリがあるなら絶対使う、と思ったのでライブラリ側の名前で情報を探す。

最初に想像したのはNW.js。 ツクールMVのゲームはNW.jsで動く。

ただし今回問題になっているのはツクールMVの生成されたゲームではなくエディターの方なので NW.jsは調べても意味ないだろうなと考えた。

前述のQtらしきプロセスのこともあったので、ツクールMVとQtについて調べたら やはりQtで作られていることがわかった。

RPGツクールMV - Wikipedia

Qtといえばクロスプラットホームで動作するGUIツールキットなので、 Linux, Mac, Windowsで動くのも納得。 日本語入力もQt側でよしなにやっていると考えたのでQtの日本語入力について今度は調べることにした。

Qtのライブラリを調べる

とりあえず雑に「Qt 日本語入力」で調べたところ、まっさきに以下の記事がヒットした。

自作QtアプリやQtCreatorで日本語入力ができない問題の解決法

この記事の中の以下の記載が目に止まった。

libfcitxplatforminputcontextplugin.soがないためらしい。

fcitx と書いてあるので、もしかしたらこれと近い名前のInputMethodプラグインをツクールMVも使っているのでは、と考えた。 以下のコマンドで調査した。

% pwd
/home/jiro4989/.steam/steam/steamapps/common/RPG Maker MV

% find . -name "*.so" | grep plugin | grep -i -e ibus -e fcitx -e anthy
./plugins/platforminputcontexts/libibusplatforminputcontextplugin.so

「libibusplatforminputcontextplugin.so」というすごくそれっぽい名称のファイルが見つかった。 他に同様のファイルがないかを念の為確認。

% ls -1 plugins/platforminputcontexts
libcomposeplatforminputcontextplugin.so
libibusplatforminputcontextplugin.so

これだけ見るとibus以外のInputMethodに対応していないように見える。 僕の環境ではfcitx-mozcを使用しているので、これだと僕の環境で日本語入力できないのも 納得できた。

ということで次は日本語入力をfcitx-mozcからibus-mozcに変更して検証することにした。

日本語入力をfcitx-mozcをibus-mozcに変更

僕のPCの環境はAnsibleである程度構築を自動化している。 fcitx-ibusの環境構築もある程度自動化していた。

今回の調査に際して、以下のように変更を加えた。

https://github.com/jiro4989/setup/commit/75b07d0a3e70de8d50c2ee32d2ac2fbc1c499029

他の設定は以下の記事を参考にした。

LinuxMint 19: 日本語入力の設定をする

日本語入力できるようになった

ということで、紆余曲折を経て無事以下のようにLinux環境で日本語入力できる状態になりました。

f:id:jiroron666:20190616135328p:plain

まとめ

Linux環境でツクールMVで日本語入力できなかったのを解決する過程を説明しました。 僕と同じ問題に直面している人の助けになれば幸いです。

MV顔グラ差分素材【女の子】【ゴスロリ】

数ヶ月ぶりに描いた。 数ヶ月の間に描きたくなったときにしか描かないので 毎回思い出す作業から始まる。

ダウンロード

ここに記載の連番とzipファイル名は一致します。

actor027.zip

https://github.com/jiro4989/dist-illust/releases

イメージ

actor027

f:id:jiroron666:20190610215517p:plain

f:id:jiroron666:20190610215539p:plain

利用規約

https://github.com/jiro4989/dist-illust/blob/master/docs/LICENSE.adoc

Manjaro Linux (Arch Linux)でWacomのFinger touchデバイスをxsetwacomで無効化できなかったのを解消したメモ

つい最近、OSをUbuntuからManjaroLinux i3に変更しました。 変更前に使っていたWacomのFinger touchの無効化エイリアスが機能しなくなりました。 結果としては解消したので備忘録メモをば。

移行前に使っていたエイリアス

移行前は以下のエイリアス~/.bashrc にセットして手動で呼び出して無効化してい ました。

alias wacomoff='xsetwacom set "Wacom Intuos Pro M Finger touch" Touch off'

しかしながら、Arch Linuxに移行してこのエイリアスを呼び出しても無効化できなくなっていました。

xsetwacomでデバイスを調べる

Linux Wacom」とかでググったら出てくる xsetwacom コマンドを使って調べます。 以下のコマンドを実行して確認しました。

% xsetwacom list                            
Wacom Intuos Pro M Pen stylus       id: 10  type: STYLUS    
Wacom Intuos Pro M Pad pad          id: 11  type: PAD       
Wacom Intuos Pro M Pen eraser       id: 28  type: ERASER    
Wacom Intuos Pro M Pen cursor       id: 29  type: CURSOR    

前述のエイリアスに指定していた「Finger touch」というデバイスが認識されていないように見えます。 しかしながら、この時点では指でタブレットを触れても反応しています。

xinputでデバイスを調べる

xsetwacomはwacomのデバイスを操作できますが、 指で操作できているのでデバイスは認識されていると考えました。 ということでWacomに限らず、接続されているデバイスすべてを操作、参照できるコマンドがないか調べました。

結果としてxinputというコマンドで問題を解消しました。

こちらでも前述のxsetwacomと同様にデバイス一覧を出力できます。 以下のコマンドを実行して調べました。

% xinput list | grep Wacom
⎜   ↳ Wacom Intuos Pro M Pen stylus             id=10   [slave  pointer  (2)]
⎜   ↳ Wacom Intuos Pro M Pad pad                id=11   [slave  pointer  (2)]
⎜   ↳ Wacom Intuos Pro M Finger                 id=12   [slave  pointer  (2)]
⎜   ↳ Wacom Intuos Pro M Pen eraser             id=28   [slave  pointer  (2)]
⎜   ↳ Wacom Intuos Pro M Pen cursor             id=29   [slave  pointer  (2)]

すると、xsetwacomでは見つからなかった「Wacom Intuos Pro M Finger」が見つかりました。 xsetwacomでは細かい値の変更ができるはずですが、無効化にするだけならこれを disableにすれば十分だと考えました。

以下のコマンドを実行して、指でのタッチを無効化することができました。

xinput disable "Wacom Intuos Pro M Finger"

根本解決にはなっていないですが、とりあえずこれで指タッチだけは無効化の目的は達成できました。

以上です。

AnbilePlaybookでプライベートの開発環境構築を一撃で終わらせられるようにした

普段自分が使ってる自宅PCの開発環境構築をコマンド一撃で終わらせられるようにした。

リポジトリはこちら。

setup : https://github.com/jiro4989/setup

なぜやったか

僕は不定期にPCのOSを変更したくなります。 今までUbuntuCentOS、LinuxMint、Debian、ManjaroLinuxなど色々変更し、 今ではManjaroLinux i3に落ち着いています。

数ヶ月に一度環境を構築し直しているわけですが、 最初の1回しかやらないことも多くて何をインストールしたのか忘れてしまいます。

Vimの環境構築とか何やってたのか覚えてなかったりするので どうせLinux系OSを使うならセットアップを自動化しよう、と考えました。

シェルではだめなのか

AnsiblePlaybookを使っていますが、最初はシェルで書いていました。 curl URL | shで環境構築が終わるような感じです。これはこれで便利でした。

しかしながらOSをごとに使うコマンドが違ったり、 このOSにはこのコマンドがないので追加でインストールが必要、みたいな ことが度々あって、それを分岐させるのが大変でした。

ロール(タスクのまとまり)で分ける、というアプローチが取りづらかったんです。

Ansibleに変更して、最初にAnsibleをインストールする手間が発生してしまいましたが、 それ以外はとても快適につかえています。

参考になったリンク

Ansibleはまともに動かせるようになるまでに時間がかかる。 覚えることも多いです。 ということでまともにかけるようになるまでに参考にした記事を記載。 僕がわざわざ書くよりも、今はWebに情報が豊富になっているので。

はてなブックマークの自分のansibleタグのページです。

https://b.hatena.ne.jp/jiroron666/search?q=ansible

特に参考になったのはこの記事。

Ansibleをはじめる人に。 - Qiita

最後に

Ansibleでローカル開発環境構築自動化するのいいよ、というだけの話でした。

Ansibleをおすすめする記事で必ず見かける「べき統制」「設定ファイルベース」 という点は、こうして自分でずっとメンテし続けるとたしかにメリットを体感します。

あの記述どうやったっけ、ってのをさっとコピペして使い回せますし、 何回も実行できますからね。

こうして自動化するスクリプトを作っておくことのメリットは 1つは自動化による環境構築コストの低下ですが、 もう1つは構築漏れがなくなることだと思います。

結構、OS入れ直して実行してみたら失敗することありました。 が、都度修正すればいいですし、大半は動くので修正量は少ないです。

個人開発機ですし完全な自動化はできてなくても、 設定しないといけない物事について、設定ファイルとしてまとまっていることが大事です。

なので、Ansibleでなくてもよいので 自分の開発機の環境構築スクリプトとかは用意しとくと いいと思います。

シェル芸bot環境につぶやいてきたジョークシェル芸をひたすら書き連ねる

今までシェル芸botで実行してきたシェルのうち ボクが気に入ったものや、メモしておきたいものをひたすらここに残します。

ついでにどういうことをやっているのかといった解説もします。

LISP風の条件分岐を行う関数

[ifという関数を定義して]が見つかるまでをシェルのコマンドとみなす。 結果的に内部でifしてるんですけれどね。一応true、falseで実行されるコマンドが変化する。

[if(){
f=2
a=()
b=()
c=()
i=0
for o in "$@";do
case "$o" in
[)f=1;;
])f=2;let i++;;
*)
if [ $f = 1 ];then
case $i in
0)a+=($o);;
1)b+=($o);;
2)c+=($o);;
esac
fi
;;
esac
done
if $("${a[@]}");then
eval ${b[@]}
else
eval ${c[@]}
fi
}
[if [ true ] [ echo 1 ] [ echo 2 ] ]
#シェル芸

喘ぎ声生成

いわゆるみさくらなんこつ的な喘ぎ声っぽいなにかを生成するシェル芸。 下品すぎる。

p(){
paste -d "" - - - - - -
}
h(){
head -n 99
}
s(){
shuf -re $@
}
(s あ ぁ ん ♥ \"|p|grep -vE '^("|♥)'|h;
s ら め へ ぇ \"|p|grep -vEe '""|らら|めめ'|grep -E "^ら"|h;
s ん ほ お ぉ \"|p|grep -E "^ん"|h;
s い く ぅ \"|p|grep -E '^い'|grep -v '""'|h;)|shuf
らへめ"ら"
い"くぅ"く
ん"ぉんぉお
ぁぁぁあ♥ん
らぇへめ"ぇ
い"いくくく
らへへ"めら
ん""ん"ぁ
んおお"んお
いくいぅ"く
らへ"へめへ
らぇぇめぇぇ

「あなたは赤い部屋が好きですか」1文字ずつ増やして出力

かなり昔に作成されたホラーフラッシュの赤い部屋の最後の方で表示される 少しずつ文字が見えていくというのを再現したシェル(もとのフラッシュだと文字と文字の切れ目から出現していたが)。

本家の赤い部屋フラッシュはもう見れない。 公開が停止されている、という意味ではなく、 ブラウザがフラッシュプラグインをサポートしていないため。 Youtubeで我慢する。

これを当時見たときは本気で怖がっていたような記憶。

echo あなたは赤い部屋が好きですか? | grep -o . | awk '{s=s""$1; print s}'
あ
あな
あなた
あなたは
あなたは赤
あなたは赤い
あなたは赤い部
あなたは赤い部屋
あなたは赤い部屋が
あなたは赤い部屋が好
あなたは赤い部屋が好き
あなたは赤い部屋が好きで
あなたは赤い部屋が好きです
あなたは赤い部屋が好きですか
あなたは赤い部屋が好きですか?

技術的な話をすると、grep -o .は文字列を1列にする。 -oオプションはマッチした文字だけを出力する。 .正規表現での「任意の何らかの文字」にマッチする。 よって、あらゆる文字が1つマッチしたら出力し、次のマッチを待つようになる。

以下の例を示す。

[0-9]は数値にのみにマッチする正規表現。 結果も数値のみ出力されている。

% echo あ12い34う56え78お | grep -Eo "[0-9]" 
1
2
3
4
5
6
7
8

対して.ではすべての文字が1文字ずつ出力されている。

% echo あ12い34う56え78お | grep -o "." 
あ
1
2
い
3
4
う
5
6
え
7
8
お

最後はawkで1文字ずつ連結して出力する。

awkでは文字列の連結は+とかは不要。 文字列や変数をカンマで区切ったらスペース区切り出力され、 カンマをつけずに文字列や変数を続けてprintすると 文字列が連結される。

awk '{s=s""$1; print s}'

なので、こう書いても同じ結果になる。

awk '{s=s $1; print s}'

うんこで囲う

シェル芸bot環境に存在するいくつかのコマンドと組み合わせている。 手前味噌だけれど、alignという自作のコマンドも使用している。

(
  echo;
  (
    unko.tower -s うんこ神 5 \
    | align left -p " " \
    | owari kan -gi; echo
  ) \
  | align center -p1 -n 60 \
  | sed -r 's/111/うんこ/g;s/11/うん/g;s/1/う/g';echo
) \
| align center -p2 -n 72 \
| sed 's/222/ウンコ/g;s/22/ウン/g;s/2/ウ/g' \
| sed 'y/NHK/うんこ/' \
| imgout #シェル芸

f:id:jiroron666:20190605000638p:plain

unko.towerはsuper_unkoリポジトリのコマンドの1つ。 -sオプションではうんこの中にテキストを埋め込める。 alignはテキストの位置を揃える。 owariはギコネコが看板を背負っているAAを出力する。 -giでは標準入力を受け取って、看板内に中央寄せして埋め込んで出力する。

$ unko.tower -s うんこ | align left | owari kan -gi

| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|       人          |
|     (うんこ)      |
|    (うんこうん)    |
|   (こうんこうんこ)  |
|       制作・著作       |
|   ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄  |
|        N H K        |
|____________|
       ∧∧ ||        
      ( ゚д゚)||       
       /   づΦ        

一番深い階層でギコネコを出力するが、それ全体をサブシェルでラッピングし、 サブシェルをパイプしてalignに渡すことで結果全体を一揃えしている。

同じようなことを繰り返し、空白文字をうんこに置換。 sedのyコマンドでは1文字ずつ対応するように置換する。

相関図

これは2ツイート連携で実現している。280文字以内には収まった。

やっていることは結局前述のうんこで囲むのと同じことを組み合わせているだけ。

#!/bin/bash

(
echo
echo 'うんこシェル芸人とショル力の相関'|align center -n 70 -p ' '
(
echo 'y (シェル芸力)'
(
seq 10|while read i;do printf "%${i}s\n" '/';done|tac|sed 's/ / /g' | align left -p ' ' | sed -r 's/^/|/g'
echo
) | align left -p '~~' \
  | sed '11s/$/ x (うんこと呟く頻度)/g'
) \
| align left -n 40 -p ' ' \
| align center -p ' ' -n 70
echo '※1 サンプリング数:255名'|align right -n 70 -p ' '
echo
) | align center -p '-' -n 80

f:id:jiroron666:20190605000806p:plain

以下抜粋。 seq〜printfで空白埋めされた線を描画している。左上から右下に下がるように描画される。 線は斜め上に上がってほしかったので、whileの結果全体をtacで反転されている。 taccatの逆で、入力データの最終行から最初の行に向かって出力するコマンド。

$ seq 10 \
| while read i; do
    printf "%${i}s\n" '/'
  done \
| tac \
| sed 's/ / /g' \
| align left -p ' ' \
| sed -r 's/^/|/g'

|         /
|        / 
|       /  
|      /   
|     /    
|    /     
|   /      
|  /       
| /        
|/         

難読化うんこ

超絶読みづらくしたunko.tower。解説するのがしんどい。

+() { ! :;bc<<<obase=$?$#\;ibase=$(($?<<$?))\;${@:${?}:${?}}; };
$(echo -e "\x`! :;+ $?$?$?``! :;+ $?$?`\x`! :;+ $?$?$#``! :;+ $?$#$?`\x`! :;+ $?$?$#``! :;+ $?$#$#`") `! :`$((($?<<$?)+$?))$(echo -e "\x`! :;+ $?$?$?``! :;+ $?$?`")/     / ・__・ / <(unko.tower) #シェル芸


     人
   (   )
  ( ・__・ )
 (       )

+()はただの関数定義。関数名を+としている。 中でやっているのはbcコマンドに<<<(ヒアストリング)でbc用の式を渡している。

+の関数内の先頭の! :;$?を1にするための処理。 $(($?<<$?))bashの算術演算式で、左シフトを表現している。 よって$(($?<<$?))$((1<<1))になり、1の1左シフトで2になる。

あとのecho -eのところは、16進数で表現したsedコマンド。 ただし直接sedとしたくなかったので、echo -eで16進表現(\xFFみたいな)を文字に変換している。

あみだ線生成

あみだくじを生成する。 アルゴリズムが適当なので総当りで文字を生成して、余計なものが混じってる行を出力しないようにしているので 計算量は増えがち。 あみだくじの線の数を増やしたら出力されないほうが多くなる。

make_row() {
  seq $1 \
    | xargs -I@ bash -c 'printf "$(shuf -n1 -e ├ ┤ │)"' \
    | grep -vEe "├([^┤]|$)" -e "(^|[^├])┤"
  return $?
}

row=5
col=6

n=0
while [ $n -lt $row ]; do
  ! make_row $col
  n=$((n+$?))
done
├┤├┤││
││││├┤
││││├┤
├┤│├┤│
│├┤├┤│

やってることとしては、make_row関数であみだくじを生成し、その戻り値で あみだくじが生成されたかどうかを判定し加算し続けるというもの。 指定の数になったらwhileを抜ける。

grepは指定の文字が1つも見つからなかったときに終了コードが1になる。 1つでも文字が見つかった場合は終了コードが0になる。

今回のケースではその性質を利用し、あみだくじが出力されたとき、 「1行あみだくじの行を生成した」ということとして 生成した行数1を戻り値として返すということをやっている。

呼び出し元で!を使って0を1に反転しているので、 grepで行が見つかった際に1加算している。 (不正なくじが生成されていない)

終了コードの使い方としてはよろしくないアプローチだとは思う。

虹色のうんこ

super_unkoのunko.printpnmという自作のコマンドで出力したうんこのテキストを虹色に変換している。 PNMについてはWikipediaが参考になる。

a=("255 0 0" "255 165 0" "255 255 0" "0 128 0" "0 255 255" "0 0 255" "128 0 128")
i=0
while read -r line
do
  i=$((++i))
  sed -r 's/255 255 255/'"${a[$((i%7))]}"'/g' <<< "$line"
done <<< "$(unko.printpnm)" \
| convert - -scale 200x200 /images/t.png
#シェル芸

f:id:jiroron666:20190605070953p:plain

whileに対してヒアドキュメントを渡すところから処理が始まる。 パイプでwhileに渡していないことには理由があって、iというループカウンタを加算したいから。

パイプ後で実行されるコマンドは別シェルとして起動されるため、 その中で変数を定義したり値を変更しても、while外の変数には影響を与えない。

ループカウンタiは0〜6の値をループさせたいのでパイプは使えない、ということで ヒアドキュメントで渡している。

ループカウンタは虹色のRGBを定義した配列から色を取り出すときに使用している。 sedでもとの色の指定の箇所をループの都度sedの式を配列の色をもとに生成して置換している。

convert -ではパイプの結果を入力データとして利用することを指定している。 それをPNGとして保存する。

ちなみにwhileにテキストを渡す方法にはいくつかアプローチがある。 ファイルをいきなり渡したりコマンドの実行結果を渡したりパイプで渡したり いろいろ方法がある。 いくつかやり方を知っていると芸の幅が広がる。以下はその一例。

パイプせずにファイルをwhileに渡す

while read -r line; do
  echo $line
done < text.txt

パイプしてファイルを渡す

cat text.txt | while read -r line; do
  echo $line
done

パイプせずに複数行のテキストを渡す

while read -r line; do
  echo $line
done << EOS
1
22
333
EOS

パイプせずにコマンドの実行結果を渡す

while read -r line; do
  echo $line
done <<< "$(seq 3)"

色のついたテキストを画像に変換する

手前味噌で恐縮だが、textimgという自作のコマンドを使う。 これは渡されたテキストのエスケープシーケンスを解析してRGB値に変換して画像出力するコマンド。 シェル芸bot環境で画像を描画したくて作った。

bigunko.show | textimg -o /images/t.png

f:id:jiroron666:20190605071052p:plain

シェル芸bot環境では以下のように短くかける。

bigunko.show | textimg -s

grepの結果やscreenfetchなどの結果も画像に反映できる。

screenfetch | textimg -s

f:id:jiroron666:20190605071117p:plain

ちなみに上にスクロールできるオプションがある。

殺意のうんこ

前述のbigunko.showの色を変更して赤黒い配色に変更するシェル。

bigunko.show \
| sed 's/100/0/g;s/94/16/g;s/58/1/g;s/243/0/g' \
| sed 's/\x1b\[0m$//g;:a;s/ /う/;s/ /ん/;s/ /こ/;/ $/ba;s/[うんこ]/\x1b[31m&/g' \
| textimg -s
#シェル芸

f:id:jiroron666:20190605071227p:plain

bigunko.showの結果をlessで確認してみるとESC[48;5;243mとかって文字が大量に表示される。

これは端末上で背景色の変更を表現するエスケープシーケンス。 ESC[4nから始まると背景色、ESC[3nから始まると文字色が変わる。 このシェルでやっているsedはこのエスケープシーケンスの数値を別の色の数値に置換する。

置換が終わったら全角空白文字をうんこ文字に置換する。 文字色を変更するためESC[31を文字の置換に指定する。

色のエスケープシーケンスについて

前述のESC[48;とかって文字を端末上で再現する場合は\x1b[48あるいは\e[48と書く。 色だけ試したい場合は以下のシェルを実行してみるととりあえず色は見れる。

for i in 3 4; do
  for j in {0..7}; do
    echo -ne "\e[${i}${j}m  test  \e[m"
  done
  echo
done

f:id:jiroron666:20190605071503p:plain

ESC[3nは文字の色を変更する。 ESC[4nは文字の背景色を変更する。

nの値は0~8になる。0~7については色が固定だが、8を指定するとさらに別の色を指定できるようになる。

0~7の色についてはQiitaの記事 とかを見ればすぐに見つかる。

で、8の指定は特殊。 8を指定した場合は2通りの色の指定の仕方がある。 書式は以下の通り。

分類 ESC ESC 色指定
文字色 ESC[38 (固定) 5 (固定) 0 ~ 255m ESC[38;5;128m
背景色 ESC[48 (固定) 5 (固定) 0 ~ 255m ESC[48;5;128m

0〜255の色の一覧は下記の通り。

f:id:jiroron666:20190605071850p:plain

もう1つはRGB指定する方法。

きもいオジサンのLINEメッセージ

先日(2019/05/31だったかな)ojichatというコマンドがシェル芸bot環境に追加された。 これはLINEやTwitterなどのSNSで女性に対して下心丸出しのおっさんが送るメッセージを再現するコマンド。 以下のように実行する。

ojichat

ヤエ、そっちも雨なのかな( ̄ー ̄?)⁉出張で広島に行ってきたよ😍💕😄観光でも、行きたいなぁ💗💗モチロン、ヤエとネ

ojichat 花子

花子チャン、お疲れ様〜😃(^o^)😃✋今日は長崎28度だよ😱💦^^;💔暑いよ^^;💔ヤケドしないように気をつけないトネ(^_^)😃☀ (笑)

文章は完全にランダムに生成されるので、特定のパターンがほしいときはheadする。 以下は10回ojichatを実行する。

yes ojichat | head | bash

これを利用して特定の文言が出現するパターンを取得して文章を書き換えて独自の文章を作ったり。

メンヘラおじさん。

m=$(seq 100 | xargs -I@ printf "なんで")
yes ojichat \
| head -n 100 \
| bash \
| grep こうよ \
| head -n 1 \
| sed -E 's/こうよ.*/こうよ・・・。行かないの?なんで・・・'$m'/g'

#シェル芸
マキちゃん、オハヨウ〜😚😃😃♥ ちょっと電話できるカナ✋❓⁉⁉そろそろご飯行こうよ・・・。行かないの?なんで・・・なんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなんでなん

別の文章と合わせたりして色々遊べる。

きもいオジサンのメッセージをうんこに埋め込む

殺意のうんこシェル芸と同じようなことをやっている。

bigunko.showに含まれる文字を埋め込むには少し工夫が必要。 bigunko.showの全角スペースの前後にはエスケープシーケンスがついているため 「全角文字の連続」みたいな正規表現はかけない。

今回の僕のアプローチは「sedの置換式文字列をループで回し、bigunko.showが書かれた テキストを都度上書きして1文字ずつ全角空白を置換する」というようにした。

前述のシェルを分解すると以下のような3つのシェルに分解できる。

bigunko.show \
| tr '\n' 'Q' > a.txt

for __ in {1..20} do
  for cmd in $(ojichat | grep -o . | sed -E "s,.,s/ /&/ ,g" | tr -d "\n"); do
    sed -i "$cmd" a.txt
  done
done

cat a.txt|tr Q \\n \
| sed -E 's@\x1b\[48;5;243m@\x1b[41m@g;s@.*@\x1b[30m&\x1b[m@g'\
| textimg -s

#シェル芸

まずbigunko.showをテキストファイルに出力する。 このとき改行文字を元の文章に出現しない何でも良い文字に置換して1行のデータに変換している。 sedのループで置換するときに1行のデータのほうが処理しやすいため。

bigunko.show \
| tr '\n' 'Q' > a.txt

sedで文字を置換する処理。 sed -E "s,.,s/ /&/ ,g"の箇所でsedの式を生成している。

for __ in {1..20} do
  for cmd in $(ojichat | grep -o . | sed -E "s,.,s/ /&/ ,g" | tr -d "\n"); do
    sed -i "$cmd" a.txt
  done
done

sedの結果を一旦出力すると次のようになる。

% ojichat | grep -o . | sed -E 's,.,s/ /&/ ,g' | tr -d \\n
s/ /は/ s/ /つ/ s/ /か/ s/ /チ/ s/ /ャ/ s/ /ン/ s/ /、/ s/ /ヤ/ s/ /ッ/ s/ /ホ/ s/ /ー/ s/ /(/ s/ /^/ s/ /o/ s/ /^/ s/ /)/ s/ /😃/ s/ /😃/ s/ /♥/ s/ / / s/ /😄/ s/ /何/ s/ /し/ s/ /て/ s/ /る/ s/ /の/ s/ /か/ s/ /い/ s/ /❓/ s/ /早/ s/ /く/ s/ /会/ s/ /い/ s/ /た/ s/ /イ/ s/ /ナ/ s/ /😄/ s/ /💕/

sed&はマッチした文字列をそのまま埋め込む特殊な値。 後方参照みたいなもの。よく使う。

この結果をcmdにセットし、sed -iの式として利用している。 -iオプションでファイルを置換した結果で上書きできる。 分解すると以下のようなsedとして実行されている。

sed -i "s/ /は/" a.txt
sed -i "s/ /つ/" a.txt

最後に結合。最初の処理で1行のデータにしていたものをもとに戻す。 sedの式では、bigunko.showの背景色を赤色のエスケープシーケンスに置換している。

cat a.txt|tr Q \\n \
| sed -E 's@\x1b\[48;5;243m@\x1b[41m@g;s@.*@\x1b[30m&\x1b[m@g'\
| textimg -s

あとこのシェルだけでなくシェル芸botではよくやってしまうが、 sedで複数の式を一気に書いている。

sed -E 's@\x1b\[48;5;243m@\x1b[41m@g;s@.*@\x1b[30m&\x1b[m@g'\

圧縮されていて読みにくいが、複数のsコマンドをここでは実行している。 ;が式の区切り文字である。 実はあまり知らない人多いと思うが、sedは複数の式を同時にかけるし、何なら複数行に書くこともできる。

echo あいう | sed 's/い/i/; s/i/Z/'

あzう

これも結局は以下のシェルと同じである。

% echo あいう | sed 's/い/i/      
s/i/Z/
'

あZう

割とあまり一般的でない特殊なシェルを書いたような気がする。(シェル芸では一般的 なテクニックかもしれないが、普通のサーバオペレーションではまずやらないようなシ ェルの使い方であることは間違いない)

以上です。

Nimで文字幅を扱うためのライブラリ(eastasianwidth)を作った

Nimで文字幅を扱うためのライブラリ(eastasianwidth)を作りました。 なぜ作ったのか、と何ができるのか、について記載します。

EastAsianWidthとは

東アジアの文字幅の問題 - Wikipediaという名称で知られている 文字幅についてのヒントのことです。

いわゆる半角文字は半角1文字分、全角文字は半角2文字分の幅になるということを定義化したものです。 その他にも文字の分類について定義しています。

なぜ作ったのか

CLIツールを作る上で全角文字の幅を考慮した実装の必要があったためです。 Goだとサードパーティのライブラリでgo-runewidthがそれを実現できるのですが Nimにはこれに相当するものがありませんでした。

なので、Node.js用のライブラリeastasianwidthを参考に Nimにも実装してみた次第です。

何が問題になるのか

例えばプログラムでテキストを使用して罫線などを表現するケースを考えます。 テーブルを表現する罫線を引き、セル内にテキストを書くとき、 文字幅を考慮しないでプログラムから扱おうとすると罫線の位置がずれてしまいます。

問題になるケースの実装を示します。

from eastasianwidth import stringWidth
from sequtils import mapIt
from strutils import repeat
from unicode import runeLen

let texts = ["test code", "テストコード"]

echo """
string.len pattern
------------------
"""

# 文字の長さ(byteサイズ)のみで実装
let maxByteLen = texts.mapIt(it.len).max
for text in texts:
  let diff = maxByteLen - text.len
  let pad = " ".repeat(diff)
  echo "| " & text & pad & " |"

echo """

string.runeLen pattern
----------------------
"""

# rune文字の長さでの実装
let maxRuneLen = texts.mapIt(it.runeLen).max
for text in texts:
  let diff = maxRuneLen - text.runeLen
  let pad = " ".repeat(diff)
  echo "| " & text & pad & " |"

echo """

string.stringWidth pattern
----------------------
"""

# eastasianwidthを使用した、表示上の幅を考慮した実装
let maxStringWidth = texts.mapIt(it.stringWidth).max
for text in texts:
  let diff = maxStringWidth - text.stringWidth
  let pad = " ".repeat(diff)
  echo "| " & text & pad & " |"

このコードを実行したときの結果は以下のとおりです。

f:id:jiroron666:20190525065525p:plain
実行結果

最後の実行結果以外は文字幅について考慮していないため、 日本語が混在すると罫線の位置がずれていることがわかります。

絵文字の例

絵文字は少し特殊です。 EastAsianWidthでは絵文字はNeutralに属しており、1文字分として扱われます。

しかしながら、ほとんどのソフトは絵文字を2文字分として扱っています。 なので、絵文字はNeutralであるけれど、文字幅としては2文字を返すようにする必要がありました。 絵文字コード範囲だけ特別扱いするようにして、2文字幅を返すように実装しました。

以下に文字幅のテストコードを転記します。

import eastasianwidth

doAssert "☀☁☂☃".stringWidth == 8
doAssert "🧀".stringWidth == 2

ライブラリの使い方

インストール

インストールには以下のコマンドを実行します。

nimble install eastasianwidth

プロジェクトとして使用する場合は、 Nimbleファイルに記述する以下の設定を記述します。

requires "eastasianwidth >= 1.1.0"

使い方は前述の問題になる例を参考にしてください。

また、rectというツールで 実際に僕は今回作成したeastasianwidthライブラリを使用しています。 このrectについてはいずれ記事にしようと思います。

まとめ

自作のライブラリeastasianwidthについて説明しました。

仕組みは結構単純なので実装にはそこまで苦労しませんでした。 他の言語に移植するのも容易だと思います。

今後別言語で同様の問題に遭遇したときは移植しようかなぁと思います。