私が知っている他のどの言語よりも、私は何か小さなことが必要になるたびにグーグルでバッシュを「学びました」。その結果、機能しているように見える小さなスクリプトを一緒にパッチワークすることができます。しかし、私は本当に何が起こっているのかわからないので、プログラミング言語としてのBashのより正式な紹介を望んでいました。例:評価順序は何ですか?スコープルールは何ですか?タイピングの分野は何ですか、例:すべてが文字列ですか?プログラムの状態は何ですか?変数名への文字列のキーと値の割り当てです。それ以上のものがありますか?スタック?ヒープはありますか?等々。
この種の洞察についてはGNU Bashマニュアルを参照することを考えましたが、それは私が望んでいるものではないようです。これは、構文の説明というよりは、構文糖衣の洗濯物リストです。コアセマンティックモデル。オンラインでの百万と1つの「bashチュートリアル」はもっと悪いだけです。おそらく最初にsh
を研究し、これに加えてBashを糖衣構文として理解する必要がありますか?これかどうかはわかりませんただし、正確なモデルです。
助言がありますか?
編集:私は理想的に私が探しているものの例を提供するように頼まれました。私が「形式的セマンティクス」と見なすもののかなり極端な例は 「JavaScriptの本質」に関するこの論文 です。おそらく、少し形式的でない例は Haskell 2010レポート です。
シェルは、オペレーティングシステムのインターフェイスです。これは通常、それ自体が多かれ少なかれ堅牢なプログラミング言語ですが、オペレーティングシステムやファイルシステムとの対話を容易にするように設計された機能を備えています。 POSIXシェル(以下、単に「シェル」と呼びます)のセマンティクスは、LISPのいくつかの機能(S式はシェルと多くの共通点があります 単語分割 )とCを組み合わせた、ちょっとした意味です。 (シェルの 算術構文 セマンティクスの多くはCから来ています)。
シェルの構文のもう1つのルートは、個々のUNIXユーティリティのミッシュマッシュとしての育成に由来します。シェルに組み込まれていることが多いもののほとんどは、実際には外部コマンドとして実装できます。 _/bin/[
_が多くのシステムに存在することに気付いた場合、ループに対して多くのシェルネオファイトをスローします。
_$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]`
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:
# We're in a child process
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の優先順位に従います。ただし、式は拡張とは異なります。 bashマニュアルのEXPANSION
セクションから:
拡張の順序は次のとおりです。ブレース拡張。チルダ展開、パラメーターと変数の展開、算術展開、およびコマンド置換(左から右の方法で実行)。単語分割;およびパス名の展開。
ワードスプリット、パス名の展開、パラメーターの展開を理解していれば、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' # another way to transiently export a name
123
_
これらのスコープルールを組み合わせることができます。
_$ foo() {
> local -x bar=123 # Export foo, but only in this scope
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
_
ええと、タイプ。ええBashには実際には型がなく、すべてが文字列に展開されます(または、おそらくWordの方が適切です)。しかし、さまざまな型を調べてみましょう。拡張。
ほとんどすべてのものを文字列として扱うことができます。 bashのベアワードは文字列であり、その意味は適用される展開に完全に依存します。
裸の単語が実際には単なる単語であり、引用符はそれについて何も変わらないことを示すことは価値があるかもしれません。
_$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
_
_$ fail='echoes'
$ set -x # So we can see what's going on
$ "${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 # Must re-evaluate the assignment
$ echo $foo
20
$ echo "${foo:0:1}" # Still just a string
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
_を参照することもあります。引数count _$#
_に含まれていないため、これは紛らわしいと思いますが、番号付きのパラメーターなので、まあ。 _$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" # Even if the zeroth index isn't set
$ …
_
引用符または円記号を使用して単語の分割を防ぐ場合、配列は指定された単語の分割を維持します。
_$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
_
配列と位置パラメータの主な違いは次のとおりです。
$12
_が設定されている場合は、_$11
_も設定されていることを確認できます。 (空の文字列に設定することもできますが、_$#
_は12より小さくなりません。)_"${arr[12]}"
_が設定されている場合、_"${arr[11]}"
_が設定されている保証はなく、配列は1まで小さくすることができます。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変数は文字列です。ただし、変数が整数の場合、Bashでは変数の算術演算と比較が可能です。ルールBash変数の例外は文字列であり、変数がタイプセットされているか、そうでない場合は宣言されています
$ A=10/2
$ echo "A = $A" # Variable A acting like a String.
A = 10/2
$ B=1
$ let B="$B+1" # Let is internal to bash.
$ echo "B = $B" # One is added to B was Behaving as an integer.
B = 2
$ A=1024 # A Defaults to string
$ B=${A/24/STRING01} # Substitute "24" with "STRING01".
$ echo "B = $B" # $B STRING is a string
B = 10STRING01
$ B=${A/24/STRING01} # Substitute "24" with "STRING01".
$ declare -i B
$ echo "B = $B" # Declaring a variable with non-integers in it doesn't change the contents.
B = 10STRING01
$ B=${B/STRING01/24} # Substitute "STRING01" with "24".
$ echo "B = $B"
B = 1024
$ declare -i B=10/2 # Declare B and assigning it an integer value
$ echo "B = $B" # Variable B behaving as an Integer
B = 5
オプションの意味を宣言する:
Bashのマンページには、ほとんどのマンページよりもかなり多くの情報があり、あなたが求めているもののいくつかが含まれています。 10年以上のスクリプトbashの後の私の仮定は、shの拡張としての歴史のために、いくつかのファンキーな構文を持っているということです(shとの下位互換性を維持するため)。
FWIW、私の経験はあなたのようなものでした。さまざまな本(O'Reillyの「Learningthe Bash Shell」など)は構文に役立ちますが、さまざまな問題を解決するための奇妙な方法がたくさんあり、それらのいくつかは本になく、グーグルで検索する必要があります。