今回お届けするのは……なんだろう、これ。
執筆時参考とした文献
シェルスクリプトで変数に改行コードを含める方法 | 俺的備忘録 〜なんかいろいろ〜
シェルの入出力制御あれこれ
https://qiita.com/tag1216/items/7ce35b7c27d371165e56
ヒアストリングとヒアドキュメント
https://qiita.com/ogiw/items/ac77a3bbb813351099a1
実験環境
Slackware 14.2 x86_64
自分自身を削除するシェルスクリプト
#!/bin/sh
rm ./suicide.sh
これは問題なく動作する。
次に、
#!/bin/sh
rm ./suicide2.sh
echo "hoge"
echo "fuga"
echo "piyo"
ls ./suicide2.sh
結果はこう。
$ ash suicide2.sh
hoge
fuga
piyo
ls: './suicide2.sh' にアクセスできません: そのようなファイルやディレクトリはありません
不思議に思えるが、これも問題なく動作する。
自分自身を一旦削除した上で再生成するシェルスクリプト
#!/bin/sh
# a strange shell script which remove oneself and regenerate oneself.
# Maybe, this is a kind of quine (self-printing program).
GHOST_IN_THE_SHELL=$(cat ./ghost_in_the_shell.sh)
rm ./ghost_in_the_shell.sh
ls ./ghost_in_the_shell.sh
echo "$GHOST_IN_THE_SHELL" >./ghost_in_the_shell.sh
ls ./ghost_in_the_shell.sh
結果はこう。
$ ash ghost_in_the_shell.sh
ls: './ghost_in_the_shell.sh' にアクセスできません: そのようなファイルやディレクトリはありません
./ghost_in_the_shell.sh
コメント行や動作確認用の ls を外すと shebang 含めて 4 行。
#!/bin/sh
GHOST_IN_THE_SHELL=$(cat ./ghost_in_the_shell.sh)
rm ./ghost_in_the_shell.sh
echo "$GHOST_IN_THE_SHELL" >./ghost_in_the_shell.sh
bash ならヒアストリングで echo いらず。
#!/bin/bash
GHOST_IN_THE_SHELL=$(cat ./ghost_in_the_shell.bash)
rm ./ghost_in_the_shell.bash
cat <<< "$GHOST_IN_THE_SHELL" >./ghost_in_the_shell.bash
アルゴリズム
変数にスクリプトをまるごと格納
↓
自分自身を記述したファイルを削除
↓
変数から自分自身を再生成しファイル化
↓
終了
要点
シェルにおいて、変数は文字列を格納する。
格納される文字列には改行が含まれていてもよい。
シェルスクリプトは改行を含む文字列として変数に格納出来る。
利点
上述のアルゴリズムを用いることで、様々なシェルスクリプトに自分自身を出力させることが可能になる(クワインをいくらでも長く出来るようになる?)。
※ 「自分自身を削除」の部分はなくても良さそう。
蛇足
自分自身の削除と再生成の間に sleep を挟むとより不気味に。
追記1
関連する情報をググりました。
今回の実験では ls が失敗していますが、この時点で実体は残っているのか否か。
後ほどプログラムの修正を試みます(rm に替えて echo >./ghost_in_the_shell.sh などと挟むと動作不能になるのかどうか等)。
→動作しました……わけわからん。
→追記2にまとめました。
Tips: 実行中のシェルスクリプトを書きかえるときには Linux: ハードリンクと inode Linuxの文字化けファイルを削除する方法 Linuxでディスクが一杯になった時の対処法 シェルスクリプトは文字通りの逐次実行 実行中のシェルスクリプト自体を修正した場合の動作 Linux とWindows では「ファイルを消す」際の挙動が異なるんだ。気をつけろ 削除したファイルをlsofで復元する inode番号からファイルのパスを得る方法を教えてください。 inode番号でmvを使う 【 stat 】コマンド――ファイルの属性や日付などを表示する:Linux基本コマンドTips(122) - @IT 【 cmp 】コマンド――ファイルを1バイトずつ比較する:LinuxコマンドTips(106) - @IT Linux におけるファイル I/O の基礎ファイル、フォルダと inode に関する参考文献一覧
https://qiita.com/kitsuyui/items/d0048eeaa50293a92a60
https://qiita.com/lnznt/items/6178e1c5f066f22fe9c2
https://qiita.com/nekkoneko/items/91bcdd687357ea5b58eb
https://qiita.com/masarufuruya/items/0c794083fc4ea03cd9f9
https://sleepy-yoshi.hatenablog.com/entry/20090917/p1
https://oshiete.goo.ne.jp/qa/3573184.html
https://lukesilvia.hatenablog.com/entry/20081001/p1
https://mag.osdn.jp/06/11/23/0451251
http://q.hatena.ne.jp/1281884424
https://hacknote.jp/archives/30128/
https://www.atmarkit.co.jp/ait/articles/1706/29/news027.html
https://www.atmarkit.co.jp/ait/articles/1704/27/news027.html
https://itkq.jp/blog/2017/05/10/linux-file-and-io/
発展:テセウスの船?
ファイル末尾で自分自身を source してやるとどうなるか。
間に sleep を入れないと CPU 使用率が酷いことになりそう。
コード
#!/bin/sh
SHIP_OF_THESEUS=$(cat ./ship_of_theseus.sh)
if [ -z $GENERATION ]; then
GENERATION=1
fi
echo $GENERATION
GENERATION=$(expr $GENERATION + 1)
stat --printf="%i\n" ./ship_of_theseus.sh
sleep 10
echo "$SHIP_OF_THESEUS" >./ship_of_theseus.sh
. ./ship_of_theseus.sh
テセウス的ゾンビ
ファイルの inode が同一であり、かつ、echo >./p-zombie.sh しているにも関わらずプログラムが続いてしまうのはいささか不可解。
コード
#!/bin/sh
PHILOSOPHICAL_ZOMBIE=$(cat ./p-zombie.sh)
if [ -z $GENERATION ]; then
echo "PID $$"
GENERATION=1
fi
echo $GENERATION
GENERATION=$(expr $GENERATION + 1)
stat --printf="%i\n" ./p-zombie.sh
echo >./p-zombie.sh
cat ./p-zombie.sh
sleep 10
echo "Hello, Everyone. I'm a zombie. Let's dance together!"
echo "$PHILOSOPHICAL_ZOMBIE" >./p-zombie.sh
. ./p-zombie.sh
番外:時間経過に比例して inode を消費するシェルスクリプト
コード途中で生成される dr_strangelove.sh と最初の dr_strangelove.sh の inode が異なり、最後の echo の行が到達不能コードである場合、このシェルスクリプトは時間の経過に比例して inode を消費するはず。
なお、コードの最終行で Fork Bomb に言及していますが、このコード自体は Fork Bomb ではありません。たぶん。コード
#!/bin/sh
DR_STRANGELOVE=$(cat ./dr_strangelove.sh)
stat --printf="%i\n" ./dr_strangelove.sh
rm ./dr_strangelove.sh
sleep 10
echo "$DR_STRANGELOVE" >./dr_strangelove.sh
stat --printf="%i\n" ./dr_strangelove.sh
. ./dr_strangelove.sh
echo 'or: How I Learned to Stop Worrying and Love the Fork Bomb.'
自己同一性とクワインの比較
人格の同一性 - 心の哲学まとめWiki - アットウィキ 私は本当に同じ私なのか? - Researchmap 【報告】バリー・デイントン教授連続講演 クリーネの再帰定理 - Wikipedia ハッシュ関数 - Wikipedia 情報 - Wikipedia ※ 難しく考えないなら diff をとって同じなら同一で良い気もします。同一性に関する参考文献
哲学的な自己同一性のアレコレ
https://www21.atwiki.jp/p_mind/pages/128.html
https://researchmap.jp/mu2gwsdgy-1774112/?action=multidatabase_action_main_filedownload&download_flag=1&upload_id=91425&metadata_id=39541
https://utcp.c.u-tokyo.ac.jp/blog/2015/08/post-780/
生成されたコードの同一性
https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%8D%E3%81%AE%E5%86%8D%E5%B8%B0%E5%AE%9A%E7%90%86
https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0
https://ja.wikipedia.org/wiki/%E6%83%85%E5%A0%B1
哲学的な同一性の問題と追記1のコードの比較
「昨日の私と今日の私は同じ私なのか?」は「同一のシェルで実行されているか否か」で類比出来るでしょうか。
記憶媒体の同一性を以って自己の同一性とみなすならば、「inode が同一であるか否か」が重要かも。
シェルスクリプトの在り様と人間の意識の在り様がなんとなく似ている気がしたので、あれこれつらつらと考えてみました。
追記2
拙作スクリプト「テセウス的ゾンビ」の挙動から推測するシェル毎のシェルスクリプトの扱いの違い
※ 実行時に自己改変・自己呼び出しする 変態 スクリプトを書く場合、注意する必要がありそうです。例えば Slackware 14.2 のように、sh が bash の POSIX 互換モードである場合、普通に実行権限を付与して実行するだけではテセウス君をゾンビ化出来ません。
bash
仮説:一行実行する毎に実体ファイルにアクセスする
予想:実体ファイルに改変があると即座に反映
※ 出典:シェルスクリプトは文字通りの逐次実行
コードと実行結果
街にゾンビがあふれても bash があれば安心です。
自身への変更を即座に反映されてしまう場合、「自己削除」は組み込みにくい。
#!/bin/sh
PHILOSOPHICAL_ZOMBIE=$(cat ./p-zombie.sh)
if [ -z $GENERATION ]; then
echo "PID $$"
GENERATION=1
fi
tput setaf $GENERATION
echo "GEN $GENERATION"
GENERATION=$(expr $GENERATION + 1)
stat --printf="%i\n" ./p-zombie.sh
echo >./p-zombie.sh
cat ./p-zombie.sh
sleep 10
echo "Hello, Everyone. I'm a zombie. Let's dance together!"
echo "$PHILOSOPHICAL_ZOMBIE" >./p-zombie.sh
. ./p-zombie.sh
本体ではなく source されるファイルを書き換えるようにすることで、類似する動作は実現可能でした。
コードと実行結果その2
#!/bin/sh
PHILOSOPHICAL_ZOMBIE=$(cat ./p-zombie.sh)
if [ -z $GENERATION ]; then
echo "PID $$"
GENERATION=1
fi
tput setaf $GENERATION
echo "GEN $GENERATION"
GENERATION=$(expr $GENERATION + 1)
stat --printf="%i\n" ./p-zombie.sh
echo >./p-zombie.sh
cat ./p-zombie.sh
sleep 10
echo "Hello, Everyone. I'm a zombie. Let's dance together!"
echo "$PHILOSOPHICAL_ZOMBIE" >./p-zombie.sh
source ./p-zombie.sh
ash dash ksh mksh zsh tcsh fish PowerShell
仮説:何らかのかたちで最初にキャッシュしてしまう
予想:読み込み後の実体ファイルの改変は source した場合を除き無視する
こちらの仕様の方が変なものを作りやすそう。 tcsh - コマンド (プログラム) の説明 - Linux コマンド集 一覧表 csh cシェル 簡単なシェルプログラミングまとめ cshスクリプトでよく使われる機能 cshellで環境変数が設定されているか判断する方法がわかりません(><;)... - Yahoo!知恵袋 variable of csh ( UNIX Operation II - AD99 ) array of csh ( UNIX Operation II - AD99 ) text processing - Why is printf better than echo? - Unix & Linux Stack Exchange 変数への改行の入れ方が分かりませんでした。 https://fishshell.com/docs/current/tutorial.html#tut_variables https://fishshell.com/docs/current/tutorial.html#tut_command_substitutions https://github.com/fish-shell/fish-shell/issues/159 https://stackoverflow.com/questions/34166077/multi-line-variables-remove-new-line-character-fish https://github.com/fish-shell/fish-shell/wiki/POSIX-compatibility-thoughts https://fishshell.com/docs/current/commands.html ※ fish を対話的に実行している場合、 fish 用のシェルスクリプトは色々と書き直す必要がありました。 ・変数の設定には set コマンドを使い、= は使わない など、ずいぶん特徴的な書き方をする必要があります。実行時オプションで sh 互換に出来たりしないのかな? 学習コストを横に置くと、表記自体は見通しが良さそう。 ※ 最後に cat するファイルを間違えています。cat ./p-zombie.fish などとすべきでした。 SlackBuilds.org - docker howtosdocker - SlackDocs PowerShell スクリプト | Microsoft Docs PowerShell のコマンド ライン オプション | Microsoft Docs PowerShellの基本(前編) (1/5):Windows PowerShellコマンド&スクリプティング入門 - @IT PowerShellスクリプティングの第一歩(後編) (1/5):Windows PowerShellコマンド&スクリプティング入門 - @IT PowerShell/文法/コマンド置換(バッククオート) - yanor.net/wiki What's the cmd/powershell equivalent of back tick on bash? - Stack Overflow Process コマンドレットによるプロセスの管理 | Microsoft Docs PowerShell/実行しているPowerShellの変数一覧を表示する・Get-Variable - Windowsと暮らす WindowsでPowerShellスクリプトの実行セキュリティポリシーを変更する:Tech TIPS - @IT about_Execution_Policies | Microsoft Docs Powershellでファイルの文字コードを変換 - Qiita 環境: 扱うデータが全て「文字列」というのも楽しいですが、扱うデータ全てが「オブジェクト」というのも色々新鮮でした。オブジェクトのパイプとかリダイレクトとか、とても面白い。 (Docker を使うことで Slackware でも恐らくある程度簡単に PowerShell を試すことが出来ると予想。) 上記の Slackware への読み替え例 ※ /etc/slackpkg/blacklist の末尾に 学習コストはかかりそうですが、興味深いシェルです。 ※ 末尾に改行が 3 行あると、最初のファイルと 2 番目以降のファイルのハッシュ値が一致しました。 ※ Write-Output に改行を含める修正をした際にダブルクォーテーションマークを一つ消し忘れて実行不能になっていました。失礼しました。
コードと実行結果
ash dash ksh mksh zsh
#!/bin/sh
PHILOSOPHICAL_ZOMBIE=$(cat ./p-zombie.sh)
if [ -z $GENERATION ]; then
echo "PID $$"
GENERATION=1
fi
tput setaf $GENERATION
echo "GEN $GENERATION"
GENERATION=$(expr $GENERATION + 1)
stat --printf="%i\n" ./p-zombie.sh
echo >./p-zombie.sh
cat ./p-zombie.sh
sleep 10
echo "Hello, Everyone. I'm a zombie. Let's dance together!"
echo "$PHILOSOPHICAL_ZOMBIE" >./p-zombie.sh
. ./p-zombie.sh
tcsh
参考文献
https://kazmax.zpp.jp/cmd/t/tcsh.1.html
https://uguisu.skr.jp/Windows/csh.html
http://www.coins.tsukuba.ac.jp/~yas/classes/syspro-2003/2003-06-23/csh.html
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1039521865
http://www.not-enough.org/abe/manual/unix2-ad99/variable.html
http://www.not-enough.org/abe/manual/unix2-ad99/array.html
https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echoset PHILOSOPHICAL_ZOMBIE = `cat ./p-zombie.tcsh`
if ! ( $?GENERATION ) echo "PID $$"
if ! ( $?GENERATION ) set GENERATION=1
tput setaf $GENERATION
echo "GEN $GENERATION"
@ GENERATION = $GENERATION + 1
stat ./p-zombie.tcsh
echo >./p-zombie.tcsh
cat ./p-zombie.tcsh
sleep 10
echo "Hello, Everyone. I'm a zombie. Let's dance together!"
printf "$PHILOSOPHICAL_ZOMBIE" >./p-zombie.tcsh
source ./p-zombie.tcsh
$ cat p-zombie.powder | tr "\n" ";" > p-zombie.tcsh
fish
参考文献
環境変数について
コマンド置換について
改行を含む変数の登録や呼び出しについて
POSIX との互換性について
組み込みコマンドについて
help "組み込みコマンド名"
でWebブラウザ上で詳細を閲覧可能。
・コマンド置換に ()
を使う
・改行を含む文字列の呼び出しには printf %s\n
を使う#!/bin/fish
set PHILOSOPHICAL_ZOMBIE (cat ./p-zombie.fish)
if test -z $GENERATION
echo "PID $fish_pid"
set GENERATION 1
end
tput setaf $GENERATION
echo "GEN $GENERATION"
set GENERATION (expr $GENERATION + 1)
stat --printf="%i\n" ./p-zombie.fish
echo >./p-zombie.fish
cat ./p-zombie.fish
sleep 10
echo "Hello, Everyone. I'm a zombie. Let's dance together!"
printf "%s\n" $PHILOSOPHICAL_ZOMBIE >./p-zombie.fish
. ./p-zombie.fish
PowerShell
参考文献
Slackware と Docker
https://slackbuilds.org/repository/14.2/system/docker/
https://docs.slackware.com/howtosdocker
PowerShell
https://docs.microsoft.com/ja-jp/powershell/scripting/overview?view=powershell-6
https://docs.microsoft.com/ja-jp/powershell/scripting/components/console/powershell.exe-command-line-help?view=powershell-6
https://www.atmarkit.co.jp/ait/articles/0708/30/news137.html
https://www.atmarkit.co.jp/ait/articles/0709/20/news125.html
http://yanor.net/wiki/?PowerShell/%E6%96%87%E6%B3%95/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E7%BD%AE%E6%8F%9B%EF%BC%88%E3%83%90%E3%83%83%E3%82%AF%E3%82%AF%E3%82%AA%E3%83%BC%E3%83%88%EF%BC%89
https://stackoverflow.com/questions/434038/whats-the-cmd-powershell-equivalent-of-back-tick-on-bash/434311#434311
https://docs.microsoft.com/ja-jp/powershell/scripting/samples/managing-processes-with-process-cmdlets?view=powershell-6
https://win.just4fun.biz/?PowerShell/%E5%AE%9F%E8%A1%8C%E3%81%97%E3%81%A6%E3%81%84%E3%82%8BPowerShell%E3%81%AE%E5%A4%89%E6%95%B0%E4%B8%80%E8%A6%A7%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E3%83%BBGet-Variable
https://www.atmarkit.co.jp/ait/articles/0805/16/news139.html
https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-6
https://qiita.com/tworks55/items/b7884c4bb6ec84e73e57
Windows 10 x86_64 上の PowerShell 5x
→runc のインストールに手間取りましたが(構築依存のインストール後に一度シェルを開き直す必要があった)、Docker をインストール出来ました。powershell も問題なくインストール出来ます。公式配布のバイナリが Slackware で普通に動きました
インストール - バイナリ アーカイブ | Linux への PowerShell Core のインストール | Microsoft Docs
https://docs.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-6#installation---binary-archives
cd /tmp
curl -L -o /tmp/powershell-6.2.0-linux-x64.tar.gz https://github.com/PowerShell/PowerShell/releases/download/v6.2.0/powershell-6.2.0-linux-x64.tar.gz
mkdir -p /tmp/powershell-6.2.0/opt/microsoft/powershell/6.2.0
tar zxf /tmp/powershell-6.2.0-linux-x64.tar.gz -C /tmp/powershell-6.2.0/opt/microsoft/powershell/6.2.0
chmod +x /tmp/powershell-6.2.0/opt/microsoft/powershell/6.2.0/pwsh
mkdir -p /tmp/powershell-6.2.0/usr/bin
cd /tmp/powershell-6.2.0/usr/bin
ln -s /opt/microsoft/powershell/6.2.0/pwsh ./pwsh
cd /tmp
su -
cd /tmp/powershell-6.2.0/
makepkg --linkadd y --chown y /tmp/powershell-6.2.0-1_MYPKG.txz
installpkg /tmp/powershell-6.2.0-1_MYPKG.txz
cd /root
rm -r /tmp/powershell-6.2.0
mkdir /root/Downloads
cp /tmp/powershell-6.2.0-linux-x64.tar.gz /tmp/powershell-6.2.0-1_MYPKG.txz /root/Downloads/
[0-9]+_MYPKG
と追記し、パッケージ名で "1_MYPKG" とすることで、slackpkg clean-system
等の対象外に指定。$PHILOSOPHICAL_ZOMBIE=$(Get-Content -Encoding UTF8 ./p-zombie.ps1)
$GENERATION=$GENERATION+1
if($GENERATION -eq 1){
Write-Output "PID $PID"
}
Write-Output "GEN $GENERATION"
Get-FileHash ./p-zombie.ps1
Write-Output "" >./p-zombie.ps1
Start-Sleep 10
Get-Content -Encoding UTF8 ./p-zombie.ps1
Write-Output "Hello, Everyone. I'm a zombie. Let's dance together!
"
Write-Output $PHILOSOPHICAL_ZOMBIE | Out-File -FilePath ./p-zombie.ps1 -Encoding utf8
.\/p-zombie.ps1
追記2:考察と展望
今回のスクリプトにより、無性生殖による世代交代の(ごく大雑把な)モデル化が出来ました。
次の機会には有性生殖により世代交代するスクリプトを書きたい。
exit コマンドによりループを開くことでアポトーシスも表現出来ますし、生命現象を比喩するスクリプトにはまだまだ開拓の余地がありそうです。
「シェル芸」の誤用に関するお詫び
ワンライナーへの指向を以ってシェル芸と定めるそうなので、今回のような単に非実用性を志向するだけのコードはシェル芸にはカテゴライズされないようです。
失礼しました。
(シェルアートと自称すれば問題ないかな?)
芸術 - Wikipedia
https://ja.wikipedia.org/wiki/%E8%8A%B8%E8%A1%93
アート - Wikipedia
https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%BC%E3%83%88
「シェル芸術」という概念が @yasuhiroki さんから提唱されています。
シェル芸術 <クワイン編> - Qiita
https://qiita.com/yasuhiroki/items/7e66ffdca67e834f55a1