シェル芸クワインの作り方

# 自己紹介

@yasuhiroki
tw: [@duck_yasuhiroki](https://twitter.com/duck_yasuhiroki)

[シェル芸術＜クワイン編＞](https://qiita.com/yasuhiroki/items/7e66ffdca67e834f55a1)

[そのシェルスクリプトもうちょっとシンプルに書けそう Tips集（Golf/シェル芸ではない）](https://qiita.com/yasuhiroki/items/fad876db9e5505fceb03)

---

# シェル芸とは

> マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。

シェル芸の定義バージョン1.1[^1]

[^1]: https://blog.ueda.tech/?page_id=1434

---

# クワインとは

コンピュータプログラムの一種で、自身のソースコードと完全に同じ文字列を出力するプログラムである。[^2]

[^2]: https://ja.wikipedia.org/wiki/クワイン_(プログラミング)

---

# シェル芸クワインとは

シェル芸であり、クワインである

例

```bash
$ echo $BASH_COMMAND
echo $BASH_COMMAND

$ echo $BASH_COMMAND | bash
echo $BASH_COMMAND

$ echo $BASH_COMMAND | bash | bash
echo $BASH_COMMAND
```

---

# シェル芸クワインの実装例

---

# ヒルベルト曲線シェル芸クワイン (前回発表)

https://qiita.com/yasuhiroki/items/7e66ffdca67e834f55a1#ヒルベルト曲線クワイン

```bash
n= h='l=\47L${r}FR${l}F${l}RF${r}L\47 r=\47R${l}FL${r}F${r}LF${l}R\47 %s l= r= eval echo \47$l\47 | { read a; b=${a%%%%F*}; echo "import sys;from turtle import *;speed(0);pensize(2);ms=min(screensize())*0.8;l=2*ms/(2**${#b}-1);up();setpos(-ms,-ms);down();${a}sys.exit()"; } | sed \47s/L/lt(90);/g;s/R/rt(90);/g;s/F/fd(l);/g\47 | python \n' s='n=\47%s\47 h=%s s=%s eval $\47printf "${h}${s}" "${n} eval"{,} "${h@Q}" "${s@Q}"\47' eval $'printf "${h}${s}" "eval ${n}"{,} "${h@Q}" "${s@Q}"'
```

---

# 山手線内回りシェル芸クワイン

```bash
n=0 y='5p2x5LqsIOaciealveeUuiDmlrDmqYsg5rWc5p2+55S6IOeUsOeUuiDlk4Hlt50g5aSn5bSOIOS6lOWPjeeUsCDnm67pu5Ig5oG15q+U5a+/IOa4i+iwtyDljp/lrr8g5Luj44CF5pyoIOaWsOWuvyDmlrDlpKfkuYXkv50g6auY55Sw6aas5aC0IOebrueZvSDmsaDooosg5aSn5aGaIOW3o+m0qCDpp5Lovrwg55Sw56uvIOilv+aXpeaarumHjCDml6Xmmq7ph4wg6bav6LC3IOS4iumHjiDlvqHlvpLnlLog56eL6JGJ5Y6fIOelnueUsAo=' l='convert -pointsize $(($(tput cols)/4)) -font ArialUnicode label:%s pbm:- | pbmtoascii 1>&2\n' s='n=%d y=%s l=%s s=%s eval $\47printf "${l}${s}\n" "$(echo ${y}|base64 --decode|xargs -n1|sed -n $((n%%29+1))p)" "$((n+1))" "${y@Q}" "${l@Q}" "${s@Q}"\47' eval $'printf "${l}${s}\n" "$(echo ${y}|base64 --decode|xargs -n1|sed -n $((n%29+1))p)" "$((n+1))" "${y@Q}" "${l@Q}" "${s@Q}"'
```

---

# 山手線内回りシェル芸クワイン

次は東京ー東京ー

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/c893bb60-8c85-822d-c4de-83c21f64d164.png)

---

# 山手線内回りシェル芸クワイン

東京の次はー有楽町ー

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/90bb5131-8e66-72c9-7403-a3f9e29782f9.png)

---

# 山手線内回りシェル芸クワイン

フォントサイズを小さくすれば鮮明な文字に

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/453bc56f-87f9-19cd-4a3b-c1dd817fb757.png)

---

# 山手線内回りシェル芸クワイン

無事一周

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/c656f166-b490-54c5-81ea-c33f943f6ac7.png)

---

# シェル芸クワインのための必須技術

---

# シェル芸クワインのための必須技術

- `s=1 echo ${s}` では `${s}` の値は出力されない
    - `s=1 eval 'echo ${s}'` なら `${s}` の値は出力される
    - `eval` により `s=1` が評価されてから `echo ${s}` が実行されるため

---

# シェル芸クワインのための必須技術

- `s="%s" eval 'printf "${s}" "hoge"'` は `hoge` が出力される
    1. `s="%s" eval 'printf "${s}" "hoge"'`
    2. `eval 'printf "${s}" "hoge"'`  
        (`s="%s"` が定義済みな状態)
    3. `printf "${s}" "hoge"`
    4. `printf "%s" "hoge"`

---

# シェル芸クワインの基本形

比較的シンプルなクワインシェル芸の例

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/28d26e44-1347-1750-8830-24665ba63fd5.png)

- 全く同じコードが含まれている
    - クォートをエスケープしているか、していないかだけ
- エスケープと `%s` の位置さえ間違えなければカスタムできる

> ↓コピペ用

```bash
s='s=%s eval $\47printf "${s}" "${s@Q}"\47' eval $'printf "${s}" "${s@Q}"'
```
---

# シェル芸クワインの発展形

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/be9ace06-3f54-9c55-5091-e0594bcf3268.png)

- 変数を増やした例
    - `n=%d` により動的に `n` の値を変更できる
    - `$((++n))` にしているが、別に `${RANDOM}` などでもよい
        - 同じコードを2箇所に追加することさえ忘れなければOK
        - ただし空白や改行やクォートが混ざるとエスケープが難しいので注意
        - `%` も混ざると大変

> ↓コピペ用

```bash
n=1 s='n=%d s=%s eval $\47printf "${s}" "$((++n))" "${s@Q}"\47' eval $'printf "${s}" "$((++n))" "${s@Q}"'
```

# シェル芸クワインのデバッグ方法

- `bash -xv` で地道に頑張る
- 慣れてくると目で気付く（末期）

---

# 山手線内回りシェル芸クワインの解説

使用しているコマンド

- `base64` コマンド
    - 駅名を `base64` でエンコードしている
    - 別に不要なのだけど `東京 有楽町 ...` と文字列があるとネタバレになるので隠したかった
- `tput` コマンド
    - ターミナルの横幅を取得する

---

# 山手線内回りシェル芸クワインの解説

使用しているコマンド

- `convert` コマンド
    - ImageMagick に含まれる画像変換コマンド
    - 漢字の文字列を画像に変換
- `pbmtoascii` コマンド
    - 画像をアスキーアートに変換
- 参考）https://twitter.com/eban/status/813261361622884352

```bash
# 漢字を表示できるフォントを探すシェル芸
$ convert -list font | grep Font: | awk '{print $2}' | xargs -I@ echo 'echo @; convert -pointsize 20 -font @ label:漢字OK pbm:- | pbmtoascii' | bash
```

---

# 山手線内回りシェル芸クワインの解説

![image.png](https://qiita-image-store.s3.amazonaws.com/0/27201/eaba4094-a915-0523-7cf0-2b7ccc634464.png)

---

```bash
m=60 n=-1 l='echo "$(yes @ | head -n%d | xargs | tr -d @)^" 1>&2\n' l2='echo -e "$(yes @ | head -n%d | xargs | tr -d @)/$(yes @ | head -n%d | xargs | tr @ " ")\\\\" 1>&2\n' s='m=60 n=%d l=%s s=%s eval $\47printf "${l}${s}" "$((m-n-2))" "$((n+1))" "$((n+1))" "${l@Q}" "${s@Q}"\47' eval $'printf "${l}${s}" "$((m-n-2))" "$((n+1))" "${l2@Q}" "${s@Q}"'
```

---

# おしまい

---