シェルは、オペレーティングシステムのインターフェイスです。これは通常、それ自体が多かれ少なかれ堅牢なプログラミング言語ですが、オペレーティングシステムやファイルシステムとの対話を容易にするように設計された機能を備えています。POSIXシェル(以下、単に「シェル」と呼びます)のセマンティクスは、LISP(S式はシェルの単語分割と多くの共通点があります)とC(シェルの算術構文の多く)のいくつかの機能を組み合わせた、ちょっとした意味論です。セマンティクスはC)から来ています。
シェルの構文のもう1つのルートは、個々のUNIXユーティリティのミッシュマッシュとしての育成に由来します。シェルに組み込まれていることが多いもののほとんどは、実際には外部コマンドとして実装できます。/bin/[
多くのシステムに存在することに気付いたときに、ループに多くのシェルネオファイトをスローします。
$ if '/bin/[' -f '/bin/['; then echo t; fi
t
ワット?
シェルがどのように実装されているかを見ると、これははるかに理にかなっています。これが私が演習として行った実装です。それはPythonですが、それが誰にとってもハングアップではないことを願っています。それほど堅牢ではありませんが、有益です。
#!/usr/bin/env python
from __future__ import print_function
import os, sys
'''Hacky barebones shell.'''
try:
input=raw_input
except NameError:
pass
def main():
while True:
cmd = input('prompt> ')
args = cmd.split()
if not args:
continue
cpid = os.fork()
if cpid == 0:
os.execl(args[0], *args)
else:
os.waitpid(cpid, 0)
if __name__ == '__main__':
main()
上記により、シェルの実行モデルがほぼ同じであることが明確になることを願っています。
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
拡張、コマンド解決、実行。シェルのセマンティクスはすべて、上記の実装よりもはるかに豊富ですが、これら3つのいずれかにバインドされています。
すべてのコマンドではありませんfork
。実際、外部として実装された意味をなさないコマンドがいくつかありますが(必要になるなどfork
)、それらでさえ、POSIXに厳密に準拠するために外部として利用できることがよくあります。
Bashは、POSIXシェルを強化するための新しい機能とキーワードを追加することにより、このベースに基づいて構築されています。これはshとほぼ互換性があり、bashは非常にユビキタスであるため、一部のスクリプト作成者は、スクリプトがPOSIXlyの厳密なシステムで実際に機能しない可能性があることに気付かずに何年もかかります。(また、人々が1つのプログラミング言語のセマンティクスとスタイルにそれほど関心があり、シェルのセマンティクスとスタイルにはほとんど関心がないのではないかと思いますが、私は分岐しています。)
評価の順序
これはちょっとしたトリックの質問です。Bashは、プライマリ構文の式を左から右に解釈しますが、算術構文ではCの優先順位に従います。ただし、式は拡張とは異なります。EXPANSION
bashマニュアルのセクションから:
拡張の順序は次のとおりです。ブレース拡張。チルダ展開、パラメーターと変数の展開、算術展開、およびコマンド置換(左から右の方法で実行)。単語分割; およびパス名の展開。
ワードスプリット、パス名の展開、パラメーターの展開を理解していれば、bashの機能のほとんどを理解することができます。名前に空白が含まれているファイルをglobと照合できるようにするため、ワード分割後にパス名を展開することが重要であることに注意してください。これが、一般に、コマンドの解析よりもglob展開の適切な使用が優れている理由です。
範囲
関数スコープ
古いECMAscriptと同様に、関数内で名前を明示的に宣言しない限り、シェルには動的スコープがあります。
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
環境とプロセスの「範囲」
サブシェルは親シェルの変数を継承しますが、他の種類のプロセスはエクスポートされていない名前を継承しません。
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y'
123
これらのスコープルールを組み合わせることができます。
$ foo() {
> local -x bar=123
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
規律の入力
ええと、タイプ。ええ。Bashには実際には型がなく、すべてが文字列に展開されます(または、単語の方が適切かもしれません)。しかし、さまざまな種類の展開を調べてみましょう。
文字列
ほとんどすべてのものを文字列として扱うことができます。bashのベアワードは文字列であり、その意味は適用される展開に完全に依存します。
拡張なし
裸の単語が実際には単なる単語であり、引用符はそれについて何も変わらないことを示すことは価値があるかもしれません。
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
部分文字列の展開
$ fail='echoes'
$ set -x
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
拡張の詳細についてParameter Expansion
は、マニュアルのセクションをお読みください。それは非常に強力です。
整数と算術式
名前にinteger属性を吹き込んで、代入式の右辺を算術として扱うようにシェルに指示できます。次に、パラメータが展開されると、文字列に展開される前に整数演算として評価されます。
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo
$ echo $foo
20
$ echo "${foo:0:1}"
2
配列
引数と位置パラメータ
配列について説明する前に、位置パラメータについて説明する価値があるかもしれません。シェルスクリプトの引数は、番号のパラメータを使用してアクセスすることができ、$1
、$2
、$3
、などあなたが使用して一度にすべてのこれらのパラメータにアクセスすることができます"$@"
拡大がアレイと共通して多くのものを持っています、。set
またはshift
ビルトインを使用して、または単にこれらのパラメーターを使用してシェルまたはシェル関数を呼び出すことにより、位置パラメーターを設定および変更できます。
$ bash -c 'for ((i=1;i<=$#;i++)); do
> printf "\$%d => %s\n" "$i" "${@:i:1}"
> done' -- foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showpp() {
> local i
> for ((i=1;i<=$#;i++)); do
> printf '$%d => %s\n' "$i" "${@:i:1}"
> done
> }
$ showpp foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showshift() {
> shift 3
> showpp "$@"
> }
$ showshift foo bar baz biz quux xyzzy
$1 => biz
$2 => quux
$3 => xyzzy
bashマニュアルでは$0
、位置パラメータと呼ばれることもあります。引数の数に含まれていないので、これは紛らわしいと思います$#
が、番号付きのパラメーターなので、まあ。$0
シェルまたは現在のシェルスクリプトの名前です。
配列
配列の構文は位置パラメーターに基づいてモデル化されているため、必要に応じて、配列を名前付きの「外部位置パラメーター」と考えるのが最も適切です。配列は、次のアプローチを使用して宣言できます。
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
インデックスで配列要素にアクセスできます。
$ echo "${foo[1]}"
element1
配列をスライスできます。
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
配列を通常のパラメーターとして扱う場合、ゼロ番目のインデックスを取得します。
$ echo "$baz"
element0
$ echo "$bar"
$ …
引用符または円記号を使用して単語の分割を防ぐ場合、配列は指定された単語の分割を維持します。
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
配列と位置パラメータの主な違いは次のとおりです。
- 位置パラメータはスパースではありません。
$12
が設定されていれば、確実$11
に設定されていることも確認できます。(空の文字列に設定することもできますが$#
、12より小さくすることはできません。)"${arr[12]}"
が設定されている場合、設定される保証"${arr[11]}"
はなく、配列の長さは1まで短くなる可能性があります。
- 配列の0番目の要素は、明確にその配列の0番目の要素です。位置パラメータでは、0番目の要素は最初の引数ではなく、シェルまたはシェルスクリプトの名前です。
shift
配列、次のような、スライスに持って、それを再割り当てしますarr=( "${arr[@]:1}" )
。することもできますがunset arr[0]
、そうするとインデックス1の最初の要素になります。
- 配列は、グローバルとしてシェル関数間で暗黙的に共有できますが、それらを表示するには、シェル関数に位置パラメーターを明示的に渡す必要があります。
パス名展開を使用してファイル名の配列を作成すると便利なことがよくあります。
$ dirs=( */ )
コマンド
コマンドは重要ですが、マニュアルよりも詳細に説明されています。SHELL GRAMMAR
セクションをお読みください。さまざまな種類のコマンドは次のとおりです。
- 単純なコマンド(例
$ startx
)
- パイプライン(例
$ yes | make config
)(笑)
- リスト(例
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- 複合コマンド(例
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- コプロセス(複雑、例なし)
- 関数(単純なコマンドとして扱うことができる名前付きの複合コマンド)
実行モデル
もちろん、実行モデルにはヒープとスタックの両方が含まれます。これは、すべてのUNIXプログラムに固有のものです。Bashには、シェル関数の呼び出しスタックもあり、caller
組み込みのネストされた使用によって表示されます。
参照:
SHELL GRAMMAR
bashマニュアルのセクション
- XCUシェルコマンド言語ドキュメント
- GreycatのwikiのBashガイド。
- UNIX環境での高度なプログラミング
特定の方向にさらに拡大してほしい場合は、コメントをお願いします。