シェル芸術 <クワイン編>

# シェル芸術とは

- さっき思いついた[^1]
- 読み方(案)
    - ShellGeiJutsu
    - ShellGei-no-Jutsu
- 意味
    - シェル芸の術
    - シェルの芸術
    - 本当は、とくに意味はない

[^1]: すでに誰かが言ってそうな気がする

---

# クワインとは

- クワイン（Quine）
    - 自身のソースコードと完全に同じ文字列を出力するプログラム。[^2]
    
[^2]: https://ja.wikipedia.org/wiki/クワイン_(プログラミング)

---

# 簡単なクワインの例

- 例
    - `bash $BASH_COMMAND`  

    ```bash
    $ echo $BASH_COMMAND
    echo $BASH_COMMAND

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

---

# 以降 Bash 4.4 での作業とする

---

# 先行例

```bash
s='s=\47%s\47;printf "${s}" "${s}"';printf "${s}" "${s}"
```

> http://cs.lmu.edu/~ray/notes/quineprograms/

---

# 本当にクワインか確認する

```bash
$ eval cat q.sh "| bash"{,,,,,}
```

`jot` コマンドが使えるなら

```bash
$ eval cat q.sh "$(jot -b '|bash' -s ' ' 30)"
```

---

# 先行例の解説

```bash
s='s=\47%s\47;printf "${s}" "${s}"';printf "${s}" "${s}"
```

↓

```bash
s='s=\47%s\47;printf "${s}" "${s}"'
printf "${s}" "${s}"
```

↓

```bash
printf \
's=\47%s\47;printf "${s}" "${s}"' \
's=\47%s\47;printf "${s}" "${s}"'
```

↓

```bash
printf \
's=\47s=\\47%%s\\47;printf "${s}" "${s}"\47;printf "${s}" "${s}"'
```
---

# （私が勝手に感じる）課題

- `;` を使っている
    - 実質２行のBash
    - ワンライナーとは言い難い（個人の見解
- 視覚的にものたりない
    - ヒルベルト曲線とは言わないけど何かしたい

---

# ワンライナーへの挑戦

変数の定義と参照をワンライナーで行うには

```bash
$ s='hoge' printf "$s"
```

- これでは何も出力されない
- `printf "$s"` の `$s` が展開されるタイミングではまだ値が入っていない

---

# `eval` の活用

```bash
$ s='hoge' eval 'printf "$s\n"'
hoge
```

`eval` を使うことで `$s` を展開するタイミングを遅らせられる
詳しくは「ヒルベルト曲線 + シェル芸」で検索 :mag: 

---

# 先行例を修正

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

---

# 遊びやすくする

Howto本 [^3] によれば 「空白」 を減らすことが望ましいとのこと（意訳）

---

# 空白を減らす

空白をアスキーコードにする

```bash
s='s=\47%s\47\40eval\40\47printf\40"${s}"\40"${s}"\47' eval 'printf "${s}" "${s}"'
```

最後の `printf` 内の空白を `\40` にしても動く。
ただしクワインにはならない。

---

# 長くなったので少しだけ短くする

`${val@Q}` を活用

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

- `${val@Q}` の説明は後述

---

# 空白が無ければ遊べる

直角に曲げてみる

```bash
s='s
=%s\
40ev
al\4
0$\4
7printf\40"${s//\n/}"\40"${s@Q}"\47
' eval $'printf "${s//\n/}" "${s@Q}"'
```

- `${s//\n/}` で改行を消しているのがミソ
- :sweat_smile: クワインではない
    - クワインを出力するスクリプトではあるが...
    - 改善したい

---

# アスキーアート

```bash
s='s
=   %
s\40
e   v
al\40

  $
 \ 4
7   p
rintf
\   4

 0"${
s
 //[\
     n
\40]/}"

\    4
0    "
${s@Q}
"    \
4    7' eval $'printf "${s//[\40\n]/}" "${s@Q}"'
```

---

# アスキーアート 2

```bash
s='     s    =%s\ 4   0
e  v   a l  \     4   0
$\47  p   r  intf \40"$
{   s //[\4     0 \   n
]}"\  4   0 "${s  @   Q

}"\47' eval $'printf "${s//[\40\n]}" "${s@Q}"'
```

---

# 状態を持つクワイン

実行する度に `n` が加算されるクワインシェル芸

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

---

# レベルが上がるクワイン

実行する度にレベルアップするクワインシェル芸

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

↓ ちょっと改行して見やすく

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

---

# レベルが残るクワイン

```bash
n=1 l='echo レベル%d 1>&2\n' s='n=%d l=%s s=%s eval $\47printf "${l}${s}" "${n}" "$((n+1))" "${l@Q}" "${s@Q}"\47' eval $'printf "${l}${s}" "${n}" "$((n+1))" "${l@Q}" "${s@Q}"'
```

- 標準出力だけ見ればクワイン

---

# ヒルベルト曲線クワイン

```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}"'
```

- 鳥海さんのヒルベルト曲線をクワイン化
  - :warning: 厳密にはクワインではない
  - 2度目の実行以降がクワイン化されている
      - これは、クワインシェル芸を作るシェル芸

---

# おまけ

## ${parameter@operator}

Bashの機能 (zshにはなかった）

- `${parameter@A}`
    - 定義時の文字列を出力
- `${parameter@E}`
    - `echo -e` や `$''` と同じ
- `${parameter@P}`
    - `prompt string` を変換する
- `${parameter@Q}`
    - 値をクォートして出力

---

# おまけ

## ${parameter@operator}

```bash
val="hoge\tfuga"
echo "${val}"
echo -e "${val}"
echo "${val@A}"
echo "${val@E}"
echo "${val@P}"
echo "${val@Q}"
```

```
hoge\tfuga
hoge    fuga
val='hoge\tfuga'
hoge    fuga
hoge22:52:26fuga
'hoge\tfuga'
```

---

# クワインの確認の限界（？）

```bash
$ eval cat q.sh "$(jot -b '|bash' -s ' ' 244)"
echo $BASH_COMMAND

$ eval cat q.sh "$(jot -b '|bash' -s ' ' 245)"
bash: 1 行: echo: 書き込みエラー: Bad file descriptor
```

パイプをつなぐ数の限界？

---

# おしまい

---

[^3]: [あなたの知らない超絶技巧プログラミングの世界](https://www.amazon.co.jp/あなたの知らない超絶技巧プログラミングの世界-遠藤-侑介/dp/4774176435)