Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
11
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Organization

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

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

by yasuhiroki
1 / 28

シェル芸術とは

  • さっき思いついた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}"

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


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

$ 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 を展開するタイミングを遅らせられる
詳しくは「ヒルベルト曲線 + シェル芸」で検索 :mag:


先行例を修正

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/} で改行を消しているのがミソ
  • :sweat_smile: クワインではない
    • クワインを出力するスクリプトではあるが...
    • 改善したい

アスキーアート

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}"'
  • 鳥海さんのヒルベルト曲線をクワイン化
    • :warning: 厳密にはクワインではない
    • 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

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


おしまい


Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
11
Help us understand the problem. What are the problem?