Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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

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


おしまい


yasuhiroki
CircleCI, GitHub Actions, Jenkins, Vim, Ruby, Ruby on Rails, ShellScript,
https://yasuhiroki.github.io
a10lab
三日坊主防止アプリ「みんチャレ」の運用会社エーテンラボ株式会社です
https://a10lab.com/
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした