注:@ jw013は、以下のコメントで次のサポートされていない異議を述べています。
一般的に、自己修正コードは悪い習慣と見なされているためです。昔の小さなアセンブリプログラムでは、条件分岐を減らしてパフォーマンスを向上させる賢い方法でしたが、今日ではセキュリティリスクが利点を上回っています。スクリプトを実行したユーザーがスクリプトに対する書き込み権限を持っていない場合、アプローチは機能しません。
私がいることを指摘することによって、彼のセキュリティの異議に答え任意の特別な許可がされている一度だけ必要なあたりにインストール/アップデートするためにアクション更新/インストール自己インストール私は個人的にかなり確保呼ぶだろう-スクリプトを。私はまたman sh
、同様の手段で同様の目標を達成することへの言及を彼に指摘しました。当時、私は、セキュリティ上の欠陥や、私の答えに表されているかもしれないし、そうでないかもしれない一般的に助言されていない慣行が、それに対する答えよりも質問自体に根ざしている可能性が高いことを指摘しませんでした:
/path/to/script.shとしてスクリプトを実行すると、常にPATHで使用可能なZshが常に使用されるように、シェバンを設定するにはどうすればよいですか?
満足できなかったため、@ jw013は、まだサポートされていない議論を少なくとも2、3の誤ったステートメントでさらに進めることで異議を唱え続けました。
2つのファイルではなく、単一のファイルを使用します。[ man sh
参照]
パッケージには、一つのファイルが別のファイルを修正しています。自分自身を変更するファイルがあります。これら2つのケースには明確な違いがあります。入力を受け取り、出力を生成するファイルは問題ありません。実行時に自分自身を変更する実行可能ファイルは、一般的に悪い考えです。あなたが指摘した例はそれをしません。
そもそも:
ONLY EXECUTABLEいずれかにCODE EXECUTABLE SHELLスクリプトです#!
自体
(ただしさえ#!
ある正式に指定されていません)
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
{ ${l=lsof -p} $$
echo "$l \$$" | sh
} | grep \
"COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE
##OUTPUT
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
file 8900 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
file 8900 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
file 8900 mikeserv 0r REG 0,35 108 15496912 /tmp/zshUTTARQ (deleted)
file 8900 mikeserv 1u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 255r REG 0,33 108 2134129 /home/mikeserv/file
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 8906 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
sh 8906 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
sh 8906 mikeserv 0r FIFO 0,8 0t0 15500515 pipe
sh 8906 mikeserv 1w FIFO 0,8 0t0 15500514 pipe
sh 8906 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
{ sed -i \
'1c#!/home/mikeserv/file' ./file
./file
sh -c './file ; echo'
grep '#!' ./file
}
##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links
#!/home/mikeserv/file
シェルスクリプトは単なるテキストファイルです-何らかの効果を得るためには、別の実行可能ファイルによって読み取られる必要があり、その命令はその実行可能ファイルによって解釈され、最後に実行可能ファイルが解釈を実行しますをシェルスクリプト。シェルスクリプトファイルの実行に、2つ未満のファイルを含めることはできません。zsh
独自のコンパイラには例外がありますが、これについては経験がほとんどないため、ここでは説明しません。
シェルスクリプトのハッシュバンは、目的のインタープリターを指すか、無関係であるとして破棄する必要があります。
シェルには、入力の解析と解釈の2つの基本モードがあります。現在の入力が <<here_document
または定義している{ ( command |&&|| list ) ; } &
-言い換えると、シェルはトークンを読み取った後に実行するコマンドの区切り文字として解釈しますまたはファイルを作成し、別のコマンドのファイル記述子にマップするための指示として。それでおしまい。
シェルを実行するコマンドを解釈するとき、一連のトークンを区切ります 予約語のれます。該当する場合- -かのようなトークン終値シェルが遭遇するとリストがいずれかの改行などの閉じたトークンで区切られるまでトークン開口部は、コマンドリストを読み込むし続けなければならない})
ため({
、実行する前に。
シェルは、単純なコマンドと複合コマンドを区別します。複合コマンドは実行前に読まれなければならないコマンドのセットですが、シェルは実行されません。$expansion
、その構成のいずれかに単純なコマンドが単独でそれぞれを実行するまで。
そのため、次の例では、;semicolon
予約語が個々の単純なコマンドを区切り、エスケープされていない\newline
文字が2つの複合コマンドを区切ります。
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
echo "simple command ${sc=1}" ;\
: > $0 ;\
echo "simple command $((sc+2))" ;\
sh -c "./file && echo hooray"
sh -c "./file && echo hooray"
#END
FILE
##OUTPUT
simple command 1
simple command 3
hooray
これは、ガイドラインの簡素化です。シェルビルトイン、サブシェル、現在の環境などを考慮すると、はるかに複雑になりますが、ここでの目的には十分です。
そして、ビルトインとコマンドリストについて言えば、 a function() { declaration ; }
は単純なコマンドに複合コマンドを割り当てる手段にすぎません。シェルは$expansions
、宣言文自体で何も実行してはいけません-含めるために<<redirections>
-代わりに、定義を単一のリテラル文字列として保存し、呼び出されたときに特別なシェル組み込みとして実行する必要があります。
そのため、実行可能なシェルスクリプトで宣言されたシェル関数は、リテラル文字列形式で解釈シェルのメモリに格納されます-入力として追加されたヒアドキュメントを含めるために展開されません-ビルドされたシェルとして呼び出されるたびに、ソースファイルとは無関係に実行されますシェルの現在の環境が続く限り。
リダイレクト演算子<<
と<<-
両方は、ヒアドキュメントとして知られるシェル入力ファイルに含まれる行のリダイレクトを許可します。にコマンドの入力に。
ここで、文書は次後に始まる単一の単語として扱われなければならない\newline
とのみを含む行があるまで継続デリミタと\newline
ないと、[:blank:]
の間でsは。次に、次のヒアドキュメントがあれば、開始します。形式は次のとおりです。
[n]<<word
here-document
delimiter
...ここで、オプションn
はファイル記述子番号を表します。番号を省略すると、ヒアドキュメントは標準入力(ファイル記述子0)を参照します。
for shell in dash zsh bash sh ; do sudo $shell -c '
{ readlink /proc/self/fd/3
cat <&3
} 3<<-FILE
$0
FILE
' ; done
#OUTPUT
pipe:[16582351]
dash
/tmp/zshqs0lKX (deleted)
zsh
/tmp/sh-thd-955082504 (deleted)
bash
/tmp/sh-thd-955082612 (deleted)
sh
分かりますか?上記のシェルごとに、シェルはファイルを作成し、ファイル記述子にマップします。ではzsh, (ba)sh
、シェルで通常のファイルを作成し/tmp
、出力をダンプし、記述子にマップし、削除します/tmp
記述子のカーネルのコピーがすべてのことが残っているようにファイルを。dash
そのすべてのナンセンスを回避|pipe
し、リダイレクト<<
ターゲットを対象とした匿名ファイルに出力処理をドロップします。
このMAKES dash
さん:
cmd <<HEREDOC
$(cmd)
HEREDOC
機能的にのと同等bash
:
cmd <(cmd)
一方、dash
の実装は少なくともPOSIXlyに移植可能です。
WHICH MAKES いくつかのファイルを
だから私が行うとき、以下の答えで:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat
} <<SCRIPT >$0
[SCRIPT BODY]
SCRIPT
_fn ; exec $0
FILE
次のことが起こります。
まずcat
、シェルがfor FILE
に作成したファイルの内容を./file
実行し、実行可能にしてから実行します。
カーネルはを解釈し、に割り当てられたファイル記述子で#!
呼び出します。/usr/bin/sh
<read
./file
sh
は、で始まり、で終わる複合コマンドで構成されるメモリに文字列をマップします。_fn()
SCRIPT
とき_fn
に呼び出され、sh
まず記述子にして定義されたファイルをマップ解釈する必要があります<<SCRIPT...SCRIPT
前に呼び出す_fn
ので、内蔵のユーティリティ特別なようSCRIPT
です_fn
さん<input.
文字列によって出力printf
とcommand
に書き出される_fn
の標準アウト >&1
-現在のシェルのリダイレクトされますARGV0
-か$0
。
cat
その連結<&0
標準入力 -ファイル・ディスクリプタをSCRIPT
かけ- >
切り捨て現在のシェルのARGV0
引数、または$0
。
そのすでに現在で読み込み完了複合コマンド、sh exec
および新しく書き換え- -実行可能だ$0
引数を。
時間から./file
その含まれる命令は、それがする必要があることを指定するまで呼ばれてexec
再びD、sh
単一でそれを読み込み、複合コマンドながら、それはそれらを実行して一度./file
自体はまったく何もしない喜んでその新しい内容を受け入れ除きます。実際に作業しているファイルは/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
おかげで、すべての後
したがって、@ jw013が以下を指定する場合:
入力を受け取り、出力を生成するファイルは問題ありません...
...この答えに対する誤った批判の中で、彼は実際にここで使用されている唯一の方法を無意識のうちに容認しています。
cat <new_file >old_file
回答
ここでの答えはすべて良いのですが、完全に正しいものはありません。誰もが動的かつ恒久的にパスできないと主張しているようです#!bang
。以下に、パスに依存しないシバンのセットアップのデモを示します。
デモ
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE
出力
$0 : ./file
lines : 13
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
12 > SCRIPT
13 > _rewrite_me ; out=$0 _rewrite_me ; exec $0
$0 : /home/mikeserv/file
lines : 8
!bang : #!/usr/bin/zsh
shell : /usr/bin/zsh
1 > #!/usr/bin/zsh
2 > printf "
...
7 > sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 > sed -e 'N;s/\n/ >\t/' -e 4a\\...
分かりますか?スクリプトに自分自身を上書きさせるだけです。そして、git
同期後に一度だけ発生します。その時点から、#!bang行に正しいパスがあります。
今ではそのほとんどすべてが毛羽立っています。これを安全に行うには、次のものが必要です。
上部で定義され、書き込みを行う下部で呼び出される関数。このようにして、必要なものをすべてメモリに保存し、上書きを開始する前にファイル全体が読み込まれるようにします。
パスがどうあるべきかを決定する何らかの方法。command -v
それにはかなり良いです。
ヒアドキュメントは実際のファイルなので、本当に役立ちます。その間、スクリプトを保存します。文字列も使用できますが...
実行するものと同じコマンドリスト内のスクリプトを上書きするコマンドをシェルが読み取ることを確認する必要があります。
見て:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE
exec
コマンドを1行だけ下に移動したことに注意してください。今:
#OUTPUT
$0 : ./file
lines : 14
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
13 > _rewrite_me ; out=$0 _rewrite_me
14 > exec $0
スクリプトは次のコマンドで読み取ることができないため、出力の後半は取得しません。それでも、欠落しているコマンドは最後のものだけだったため:
cat ./file
#!/usr/bin/zsh
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
このスクリプトは、主にすべてヒアドキュメントに記載されていたため、必要な通りに実行されましたが、適切に計画しない場合は、ファイルストリームを切り捨てることができます。
env
両方/ビンと/ usr / binにはないのですか?which -a env
確認してみてください。