今回お届けするのは……なんだろう、これ。
執筆時参考とした文献
シェルスクリプトで変数に改行コードを含める方法 | 俺的備忘録 〜なんかいろいろ〜
シェルの入出力制御あれこれ
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にまとめました。
ファイル、フォルダと inode に関する参考文献一覧
Tips: 実行中のシェルスクリプトを書きかえるときには
https://qiita.com/kitsuyui/items/d0048eeaa50293a92a60
Linux: ハードリンクと inode
https://qiita.com/lnznt/items/6178e1c5f066f22fe9c2
Linuxの文字化けファイルを削除する方法
https://qiita.com/nekkoneko/items/91bcdd687357ea5b58eb
Linuxでディスクが一杯になった時の対処法
https://qiita.com/masarufuruya/items/0c794083fc4ea03cd9f9
シェルスクリプトは文字通りの逐次実行
https://sleepy-yoshi.hatenablog.com/entry/20090917/p1
実行中のシェルスクリプト自体を修正した場合の動作
https://oshiete.goo.ne.jp/qa/3573184.html
Linux とWindows では「ファイルを消す」際の挙動が異なるんだ。気をつけろ
https://lukesilvia.hatenablog.com/entry/20081001/p1
削除したファイルをlsofで復元する
https://mag.osdn.jp/06/11/23/0451251
inode番号からファイルのパスを得る方法を教えてください。
http://q.hatena.ne.jp/1281884424
inode番号でmvを使う
https://hacknote.jp/archives/30128/
【 stat 】コマンド――ファイルの属性や日付などを表示する:Linux基本コマンドTips(122) - @IT
https://www.atmarkit.co.jp/ait/articles/1706/29/news027.html
【 cmp 】コマンド――ファイルを1バイトずつ比較する:LinuxコマンドTips(106) - @IT
https://www.atmarkit.co.jp/ait/articles/1704/27/news027.html
Linux におけるファイル I/O の基礎
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 を消費するはず。
コード
#!/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.'
なお、コードの最終行で Fork Bomb に言及していますが、このコード自体は Fork Bomb ではありません。たぶん。
自己同一性とクワインの比較
同一性に関する参考文献
哲学的な自己同一性のアレコレ
人格の同一性 - 心の哲学まとめWiki - アットウィキ
https://www21.atwiki.jp/p_mind/pages/128.html
私は本当に同じ私なのか? - Researchmap
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/
生成されたコードの同一性
クリーネの再帰定理 - Wikipedia
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
ハッシュ関数 - Wikipedia
https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E9%96%A2%E6%95%B0
情報 - Wikipedia
https://ja.wikipedia.org/wiki/%E6%83%85%E5%A0%B1
※ 難しく考えないなら diff をとって同じなら同一で良い気もします。
哲学的な同一性の問題と追記1のコードの比較
「昨日の私と今日の私は同じ私なのか?」は「同一のシェルで実行されているか否か」で類比出来るでしょうか。
記憶媒体の同一性を以って自己の同一性とみなすならば、「inode が同一であるか否か」が重要かも。
シェルスクリプトの在り様と人間の意識の在り様がなんとなく似ている気がしたので、あれこれつらつらと考えてみました。
追記2
拙作スクリプト「テセウス的ゾンビ」の挙動から推測するシェル毎のシェルスクリプトの扱いの違い
※ 実行時に自己改変・自己呼び出しする 変態 スクリプトを書く場合、注意する必要がありそうです。例えば Slackware 14.2 のように、sh が bash の POSIX 互換モードである場合、普通に実行権限を付与して実行するだけではテセウス君をゾンビ化出来ません。
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 した場合を除き無視する
コードと実行結果
こちらの仕様の方が変なものを作りやすそう。
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
参考文献
tcsh - コマンド (プログラム) の説明 - Linux コマンド集 一覧表
https://kazmax.zpp.jp/cmd/t/tcsh.1.html
csh cシェル 簡単なシェルプログラミングまとめ
https://uguisu.skr.jp/Windows/csh.html
cshスクリプトでよく使われる機能
http://www.coins.tsukuba.ac.jp/~yas/classes/syspro-2003/2003-06-23/csh.html
cshellで環境変数が設定されているか判断する方法がわかりません(><;)... - Yahoo!知恵袋
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1039521865
variable of csh ( UNIX Operation II - AD99 )
http://www.not-enough.org/abe/manual/unix2-ad99/variable.html
array of csh ( UNIX Operation II - AD99 )
http://www.not-enough.org/abe/manual/unix2-ad99/array.html
text processing - Why is printf better than echo? - Unix & Linux Stack Exchange
https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo
変数への改行の入れ方が分かりませんでした。
set 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
参考文献
環境変数について
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
POSIX との互換性について
https://github.com/fish-shell/fish-shell/wiki/POSIX-compatibility-thoughts
組み込みコマンドについて
https://fishshell.com/docs/current/commands.html
※ fish を対話的に実行している場合、help "組み込みコマンド名"
でWebブラウザ上で詳細を閲覧可能。
fish 用のシェルスクリプトは色々と書き直す必要がありました。
・変数の設定には set コマンドを使い、= は使わない
・コマンド置換に ()
を使う
・改行を含む文字列の呼び出しには printf %s\n
を使う
など、ずいぶん特徴的な書き方をする必要があります。実行時オプションで sh 互換に出来たりしないのかな?
学習コストを横に置くと、表記自体は見通しが良さそう。
#!/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
※ 最後に cat するファイルを間違えています。cat ./p-zombie.fish などとすべきでした。
PowerShell
参考文献
howtosdocker - SlackDocs
https://docs.slackware.com/howtosdocker
PowerShell
PowerShell スクリプト | Microsoft Docs
https://docs.microsoft.com/ja-jp/powershell/scripting/overview?view=powershell-6
PowerShell のコマンド ライン オプション | Microsoft Docs
https://docs.microsoft.com/ja-jp/powershell/scripting/components/console/powershell.exe-command-line-help?view=powershell-6
PowerShellの基本(前編) (1/5):Windows PowerShellコマンド&スクリプティング入門 - @IT
https://www.atmarkit.co.jp/ait/articles/0708/30/news137.html
PowerShellスクリプティングの第一歩(後編) (1/5):Windows PowerShellコマンド&スクリプティング入門 - @IT
https://www.atmarkit.co.jp/ait/articles/0709/20/news125.html
PowerShell/文法/コマンド置換(バッククオート) - yanor.net/wiki
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
What's the cmd/powershell equivalent of back tick on bash? - Stack Overflow
https://stackoverflow.com/questions/434038/whats-the-cmd-powershell-equivalent-of-back-tick-on-bash/434311#434311
Process コマンドレットによるプロセスの管理 | Microsoft Docs
https://docs.microsoft.com/ja-jp/powershell/scripting/samples/managing-processes-with-process-cmdlets?view=powershell-6
PowerShell/実行しているPowerShellの変数一覧を表示する・Get-Variable - Windowsと暮らす
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
WindowsでPowerShellスクリプトの実行セキュリティポリシーを変更する:Tech TIPS - @IT
https://www.atmarkit.co.jp/ait/articles/0805/16/news139.html
about_Execution_Policies | Microsoft Docs
https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-6
Powershellでファイルの文字コードを変換 - Qiita
https://qiita.com/tworks55/items/b7884c4bb6ec84e73e57
環境:
Windows 10 x86_64 上の PowerShell 5x
扱うデータが全て「文字列」というのも楽しいですが、扱うデータ全てが「オブジェクト」というのも色々新鮮でした。オブジェクトのパイプとかリダイレクトとか、とても面白い。
(Docker を使うことで Slackware でも恐らくある程度簡単に PowerShell を試すことが出来ると予想。)
→runc のインストールに手間取りましたが(構築依存のインストール後に一度シェルを開き直す必要があった)、Docker をインストール出来ました。powershell も問題なくインストール出来ます。
公式配布のバイナリが Slackware で普通に動きました
上記の Slackware への読み替え例
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/
※ /etc/slackpkg/blacklist の末尾に [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
※ 末尾に改行が 3 行あると、最初のファイルと 2 番目以降のファイルのハッシュ値が一致しました。
※ Write-Output に改行を含める修正をした際にダブルクォーテーションマークを一つ消し忘れて実行不能になっていました。失礼しました。
追記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