シェル芸術とは
- さっき思いついた1
- 読み方(案)
- ShellGeiJutsu
- ShellGei-no-Jutsu
- 意味
- シェル芸の術
- シェルの芸術
- 本当は、とくに意味はない
クワインとは
- クワイン(Quine)
- 自身のソースコードと完全に同じ文字列を出力するプログラム。2
簡単なクワインの例
-
例
bash $BASH_COMMAND
$ echo $BASH_COMMAND echo $BASH_COMMAND $ echo $BASH_COMMAND | bash echo $BASH_COMMAND
以降 Bash 4.4 での作業とする
先行例
s='s=\47%s\47;printf "${s}" "${s}"';printf "${s}" "${s}"
本当にクワインか確認する
$ eval cat q.sh "| bash"{,,,,,}
jot
コマンドが使えるなら
$ eval cat q.sh "$(jot -b '|bash' -s ' ' 30)"
先行例の解説
s='s=\47%s\47;printf "${s}" "${s}"';printf "${s}" "${s}"
↓
s='s=\47%s\47;printf "${s}" "${s}"'
printf "${s}" "${s}"
↓
printf \
's=\47%s\47;printf "${s}" "${s}"' \
's=\47%s\47;printf "${s}" "${s}"'
↓
printf \
's=\47s=\\47%%s\\47;printf "${s}" "${s}"\47;printf "${s}" "${s}"'
(私が勝手に感じる)課題
-
;
を使っている- 実質2行のBash
- ワンライナーとは言い難い(個人の見解
- 視覚的にものたりない
- ヒルベルト曲線とは言わないけど何かしたい
ワンライナーへの挑戦
変数の定義と参照をワンライナーで行うには
$ s='hoge' printf "$s"
- これでは何も出力されない
-
printf "$s"
の$s
が展開されるタイミングではまだ値が入っていない
eval
の活用
$ s='hoge' eval 'printf "$s\n"'
hoge
eval
を使うことで $s
を展開するタイミングを遅らせられる
詳しくは「ヒルベルト曲線 + シェル芸」で検索
先行例を修正
s='s=\47%s\47 eval \47printf "${s}" "${s}"\47' eval 'printf "${s}" "${s}"'
遊びやすくする
Howto本 3 によれば 「空白」 を減らすことが望ましいとのこと(意訳)
空白を減らす
空白をアスキーコードにする
s='s=\47%s\47\40eval\40\47printf\40"${s}"\40"${s}"\47' eval 'printf "${s}" "${s}"'
最後の printf
内の空白を \40
にしても動く。
ただしクワインにはならない。
長くなったので少しだけ短くする
${val@Q}
を活用
s='s=%s\40eval\40$\47printf\40"${s}"\40"${s@Q}"\47' eval $'printf "${s}" "${s@Q}"'
-
${val@Q}
の説明は後述
空白が無ければ遊べる
直角に曲げてみる
s='s
=%s\
40ev
al\4
0$\4
7printf\40"${s//\n/}"\40"${s@Q}"\47
' eval $'printf "${s//\n/}" "${s@Q}"'
-
${s//\n/}
で改行を消しているのがミソ -
クワインではない
- クワインを出力するスクリプトではあるが...
- 改善したい
アスキーアート
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
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
が加算されるクワインシェル芸
n=1 s='n=%d s=%s eval $\47printf "${s}" "$((++n))" "${s@Q}"\47' eval $'printf "${s}" "$((++n))" "${s@Q}"'
レベルが上がるクワイン
実行する度にレベルアップするクワインシェル芸
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}"'
↓ ちょっと改行して見やすく
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}"'
レベルが残るクワイン
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}"'
- 標準出力だけ見ればクワイン
ヒルベルト曲線クワイン
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}"'
- 鳥海さんのヒルベルト曲線をクワイン化
- 厳密にはクワインではない
- 2度目の実行以降がクワイン化されている
- これは、クワインシェル芸を作るシェル芸
おまけ
${parameter@operator}
Bashの機能 (zshにはなかった)
-
${parameter@A}
- 定義時の文字列を出力
-
${parameter@E}
-
echo -e
や$''
と同じ
-
-
${parameter@P}
-
prompt string
を変換する
-
-
${parameter@Q}
- 値をクォートして出力
おまけ
${parameter@operator}
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'
クワインの確認の限界(?)
$ 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
パイプをつなぐ数の限界?
おしまい
-
すでに誰かが言ってそうな気がする ↩