長年のInferno OSシェルは、非常に否定的な感情を引き起こしました。 そして、私はインフェルノshで何人かの人々が喜んでいることに気づいたことがなかった。 しかし、彼らが言うように、それは決して遅いよりはましです-今日、私はシェルに慎重に対処することに決めました、そして結果として、私も感銘を受けました-これは本当にユニークなことです! 信じられないほどエレガントでシンプル。
それでも、客観性を高めるために欠点から始めます。 主なことは、シェルが非常に遅いことです。 理由は
/dis/sh.dis
      
      ませんが、すべてのシェルがインフェルノ(実際にはいくつかありますが、現在は
/dis/sh.dis
      
      について話して
/dis/sh.dis
      
      )は非常に遅いです。 これは非常に奇妙です 通常、Limbo(JITモード)で記述されたアプリケーションの速度は、CとPerlなどの高速スクリプト言語の速度の間です。 したがって、
sh
      
      本格的なアプリケーションは機能しません。 しかし、それにもかかわらず、くしゃみごとにLimboを発見するのは不便です。そのため、すべての同じ開始スクリプトやその他の小さなことを
sh
      
      で記述する必要があります。 2番目の重大な欠点は、テキストコンソールで使用することの不便さ、コマンド履歴の欠如、自動補完、入力時の便利な編集が非常にうっとうしいことです(しかし、私はrlwrapユーティリティについて聞いたところです。 第三-このシェルの構文は、対になっていない引用符を使用することは珍しく、絶対に他のシェルのバックライトを使用してスクリプトの構文を強調しようとすると(インファナルの既製のバックライトがないため)完全な悪夢につながります。 今日、この問題を解決するために、vimの構文強調表示を実装しました。そのため、シェルに対処するために座りました。その結果、構文強調表示の代わりに、抵抗できず、この記事を書いています。 :)
内容
- 私たちのスクリプトは何から、何から、何から作られていますか?
 -   アプリケーションを起動する 
      
 -   行、行リスト、コマンドブロック 
      
 -   環境変数 
      
 - コマンド出力のインターセプト
 - 組み込みコマンド
 -   if、for、関数を追加 
      
 - 面白いささいなこと
 - まとめ
 
私たちのスクリプトは何から、何から、何から作られていますか?
/dis/sh.dis
      
      サポートされる機能は印象的です! 条件付きステートメントとループさえありません! 機能なし。 そして、何がそこにあり、これらの条件にどのように存在することができますか? ご覧ください。 だから何ですか:
- アプリケーションの起動(パイプラインおよびI / Oリダイレクトを含む)
 - 行、行リスト、コマンドブロック
 - 環境変数
 - ファイル名テンプレート(*、?、[...])
 - いくつかの組み込みコマンドと文字列を操作するためのコマンド
 
exit
      
      、
run
      
      、および
load
      
      3つが使用され
load
      
      。 一般的に使用される文字列コマンドは
quote
      
      と
unquote
      
      です。
run
      
      については、現在のシェル(アナログ
.
      
      またはbashの
source
      
      で指定されたスクリプトを実行するだけです。
しかし、
load
      
      -はい、それは爆弾です! Limboで作成された追加モジュールを現在のシェルにロードし、関数、例外、正規表現、数学演算などの場合、シェルに機能を完全に追加できます。 など これらのモジュールを動的にアンロードできる
unload
      
      コマンドもあります。 :)それにも関わらず、ロード可能なモジュールがなくても、シェルは完全に機能的です-これを「裸のsh」にifとforを実装することで、記事の最後で証明します!
ああ、私が言及するのを忘れていた何か。 (今は確かに「ああ!」だと思いますか?)彼はまだコメントを支持しています。
#
      
      始まる。 :)
アプリケーションを起動する
* nixシェルとほとんど同じです:
-  アプリケーション( 
.dis
ファイル)およびスクリプト(最初の行がshebang#!
であるファイル)を実行します -  チームは分離されてい
;
 -   
&
介してバックグラウンドでコマンドを実行する - コマンドパイプライン
 -   
>
、>>
および<
介してstdin / stdoutをリダイレクトします 
 ; echo one two | wc 1 2 8
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      しかし、違いがあります。
追加のコマンドI / Oリダイレクト
-  ファイル記述子の指定(stderrのリダイレクトまたは追加の記述子に加えて、デフォルトで0、1、および2が使用可能) 
      
-  
cmd <stdin.txt >stdout.txt >[2]stderr.txt
 -   
cmd >[1=2]
(stdinをstderrにリダイレクト) -   
cmd <[3]file.txt
(コマンドは、ファイルから読み取るために追加のファイル記述子3を開いて起動されます) -   
cmda |[2] cmdb
(最初のコマンドのstdoutの代わりに、stderrはパイプライン入力に送信され、stdoutは単に画面に表示されます) -   
cmda |[1=2] cmdb
(stderrはstdout cmdaではなくパイプラインに再度送信されますが、stdinではなくstdout cmdbに接続します-もちろん、読み取りと書き込みではなくオープンします) 
 -  
 -  読み書きのために同時に開く 
      
-   
cmd <>in_pipe <>[1]out_pipe
(stdinとstdoutは、同時に異なるファイルの読み取りと書き込みのために開かれています) 
 -   
 -  非線形コンベア 
      
-   
cmd <{;;} >{;;}
(cmd <{;;} >{;;}
両方のブロックはcmdと並行して起動されますが、cmdは2つのパラメーターを受け取ります-ファイル名a la/fd/
、パイプ経由で最初のstdoutに接続されますコマンドブロックと2番目のコマンドブロックの標準入力) 
 -   
 
cmp
      
      ファイルをヘルプと比較する標準コマンドは、ファイルではなく、他の2つのコマンドの結果を比較できます:
cmp <{ ls /dir1 } <{ ls /dir2 }
      
      。
$ステータス
終了ステータスアプリケーションコードを実装するための非常に珍しいアプローチ。 従来のオペレーティングシステムでは、すべてのプログラムは数字で終了します。すべてが正常な場合は0、失敗した場合はその他のプログラムです。 infernoでは、すべてのプログラムは単純に終了するか(すべてが正常に処理されている場合)、例外をスローします。例外の値はエラーテキストを含む文字列です。
これが通常のエラーである場合、この行は「fail:」で始まる必要があります。 アプリケーションは「クラッシュ」しませんでしたが、このエラーをこのアプリケーションを起動したユーザーに返すことで終了したいだけです(通常、これはシェルです)。 エラーが「fail:」で始まらない場合、アプリケーションが終了した後、そのプロセスはメモリに残り(「壊れた」状態)、デバッガーで調べて、この例外が発生した理由を見つけることができます(保存する代わりに、コアダンプに類似)メモリ内のディスクでハングします)。
したがって、実行中のコマンドの完了後、その「終了コード」-つまり 例外エラーテキストは
$status
      
      環境変数にあります(「fail:」以外の例外テキストに何も含まれていない場合、「
$status
      
      」行は「failed」になり
$status
      
      )。
行、行リスト、コマンドブロック
Infernovシェルは、文字列と文字列のリストのみを扱います。
ひも
文字列は、スペースや特殊文字を含まない単語、または一重引用符で囲まれた文字です。 そのような文字列内の唯一の「エスケープ可能な」文字は、単一引用符自体であり、2つのそのような引用符で記述されています。
 ; echo 'quote '' <-- here' quote ' <-- here
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      \ n、\ t、変数の補間などはありません in rowsはサポートされていません。 文字列に改行文字を挿入する必要がある場合は、そのまま(Enterキーを押して)貼り付けるか、この文字を含む変数と文字列を連結します。 infernoには、コードによって任意の文字を出力できるunicode(1)ユーティリティがあります。これは、ヘルプを使用して行われます(構造
"{}
      
      については以下で説明しますが、現時点ではbash
``
      
      類似物と考えてください):
 ; cr="{unicode -t 0D} ; lf="{unicode -t 0A} ; tab="{unicode -t 09} ; echo -n 'a'$cr$lf'b'$tab'c' | xd -1x 0000000 61 0d 0a 62 09 63 0000006
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      文字列には二重引用符は使用されません(ちなみに、二重二重引用符はインファーンシェルでは何にも使用されません)。
コマンドブロック
コマンドのブロックは中括弧内に書き込まれ、ブロック全体がシェルによって1行として処理されます(中括弧内の文字を含む)。
 ; { echo one; echo two } one two ; '{ echo one; echo two }' one two
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      単一引用符で
sh
      
      れた文字列との唯一の違いは、
sh
      
      がコマンドブロックの構文をチェックし、それらをよりコンパクトな行に再フォーマットすることです。
 ; echo { cmd | } sh: stdin: parse error: syntax error ; echo { cmd | cmd } {cmd|cmd} ; echo { echo one echo two } {echo one;echo two}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      行のリスト
文字列のリストは、空白文字で区切られたゼロ個以上の文字列です。 これらは括弧で囲むことができますが、ほとんどの場合、これは必要ありません。
シェルコマンドは、 単に行のリストです。最初の行にはコマンドまたはコードブロックが含まれ、残りの行はそれらのパラメーターです。
 ; echo one two three one two three ; echo (one two) (three) one two three ; (echo () (one (two three))) one two three ; ({echo Hello, $1!} World) Hello, World!
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      文字列のリストを連結するには、
^
      
      演算子を使用します。 同じ数の要素を含む2つのリストに適用できます(要素はペアで連結されます)、またはリストの1つに要素が1つだけ含まれている必要があり、2番目のリストの各要素と連結されます。 特別な場合として、両方のリストにそれぞれ1つの要素しか含まれていない場合、2行の通常の連結が取得されます。
 ; echo (abc) ^ (1 2 3) a1 b2 c3 ; echo (ab) ^ 1 a1 b1 ; echo 1 ^ (ab) 1a 1b ; echo a ^ b ab
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      通常、1つのシェルコマンドを1行で記述する必要があります。「次の行でコマンドを続行する」ことを示す特別な文字はありません。 しかし、コマンドを複数の行に分割することは非常に簡単です。前の行の行(単一引用符)またはコマンドブロック(中括弧)または行のリスト(括弧)を開いたり閉じたりせず、リスト連結演算子で前の行を終了します。
環境変数
地獄のシェルで変数を操作することは、従来のシェルに一見似ていますが、だまされてはいけません!
 ; a = 'World' ; echo Hello, $a! Hello, World!
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      / env
Inferno OSの環境変数は、従来のOSとは異なる方法で実装されます。 環境変数を操作し、
int execvpe(const char *file, char *const argv[], char *const envp[]);
      
      ような悪夢を通して各プロセスに渡すための追加のPOSIX APIを導入する代わりに 環境変数は、単に
/env
      
      ディレクトリ内のファイルです。 これは興味深い可能性を提供します。たとえば、アプリケーションは
/env
      
      をエクスポート( listen(1)およびexport(4)経由)するだけで、ネットワーク経由で他のアプリケーションに環境変数へのアクセスを提供できます。
/env
      
      ファイルを作成、削除、変更することにより、環境変数を作成/削除/変更できます。 ただし、現在のshはメモリ内のすべての環境変数のコピーを保持しているため、
/env
      
      内のファイルを介して行われた変更では、現在のshではなくアプリケーションが起動されます。 一方、シェルの通常の方法で(
=
      
      演算子を使用して)変数を変更すると、
/env
      
      ファイルが自動的に更新されます。
シェル変数名には、次の文字を含めることができます。 ファイル名に使用できないもの(たとえば、names
.
      
      、
..
      
      、および
/
      
      を含む)。 このような名前の変数は、現在のshでのみ使用でき、起動したアプリケーションには表示されません。
リスト
別の重要な違いは、すべての変数に文字列のリストのみが含まれていることです。 1行を変数に入れることはできません-それは常に1行のリストになります。
空のリストを変数に保存すると(明示的に
a=()
      
      を割り当てるか、暗黙的に
a=
      
      を介し
a=
      
      )、変数が削除されます。
-   
$var
はvar変数から行のリストを取得していますが、その中にこれらの行が1つ以上含まれている場合があります。 -   
$#var
は、文字列リストの要素数を取得しています$var
 -   
$"var
は、$var
文字列リストのすべての要素を1行に連結したものです(スペースで区切ります) 
 ; var = 'first str' second ; echo $var first str second ; echo $#var 2 ; echo $"var first str second
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      ご覧のとおり、
$var
      
      と
$"var
      
      出力は視覚的に異なりません。
echo
      
      はすべてのパラメーターをスペースで表示し、
$"
      
      は変数のすべての要素をスペースで結合するためです。 ただし、最初の
echo
      
      コマンドは2つのパラメーターを受け取り、最後のパラメーターは受け取りました。
変数の値を何らかの方法で暗黙的に連結するたびに、シェルは連結演算子
^
      
      挿入します(覚えているように、これは文字列ではなく文字列のリストで機能します)。 これは便利ですが、習慣ではありませんが、予期しないことがあります。
 ; flags = abc ; files = file1 file2 ; echo -$flags $files.b -a -b -c file1.b file2.b ; echo { echo -$flags $files.b } {echo -^$flags $files^.b}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      変数のリストへの割り当ても機能します。 右側のリストが左側の変数よりも多くの要素で構成されている場合、最後の変数はリストの残りを受け取り、前の変数ではそれぞれに1つの要素があります。
 ; list = abcd ; (head tail) = $list ; echo $head a ; echo $tail bcd ; (xy) = (3 5) ; (xy) = ($y $x) ; echo 'x='^$x 'y='^$y x=5 y=3
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      スクリプトまたはコードブロックのすべてのパラメーターのリストは変数
$*
      
      、個々のパラメーターは変数
$1
      
      、
$2
      
      、...にあります。
スコープ
コードの各ブロックは、独自のスコープを形成します。 演算子
=
      
      および
:=
      
      して、変数に値を割り当てることができます。 1つ目は、既存の変数の値を変更するか、現在のスコープに新しい変数を作成します。 2番目は常に新しい変数を作成し、現在のブロックの終わりまで、この名前(存在する場合)で変数の古い値を上書きします。
 ; a = 1 ; { a = 2 ; echo $a } 2 ; echo $a 2 ; { a := 3; echo $a } 3 ; echo $a 2 ; { b := 4; echo $b } 4 ; echo b is $b b is
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      変数参照
変数名は、
$
      
      文字の後の行です。 また、任意の文字列(単一引用符または別の変数)を使用できます。
 ; 'var' = 10 ; echo $var 10 ; ref = 'var' ; echo $$ref 10 ; echo $'var' 10 ; echo $('va' ^ r) 10
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      コマンド出力のインターセプト
実際、このトピックはコマンドの起動を説明するセクションをより多く参照していますが、話していることを明確にするために、最初に文字列のリストを操作する方法を説明する必要があったため、今まで延期しなければなりませんでした。
構文の強調表示を永遠に壊してしまう非常に不対の引用符に会います。
-   
`{}
(`{}
実行し、文字列のリストとしてstdoutに出力したものを返します;リスト項目は$ifs
変数の値を使用して区切られます;定義されていない場合、3文字の文字列があると見なされます:スペース、タブおよび改行) -   
"{}
("{}
実行し、出力内容を単一行としてstdoutに返します-コマンドが出力するすべての行を連結します) 
ls
      
      呼び出しを介してファイルのリストをロードします。 ファイル名にスペースが含まれている可能性があるため、
`{}
      
      標準動作は機能しません。リスト項目を改行で区切るだけです。
 ; ifs := "{unicode -t 0A} ; files := `{ ls / } ; echo $#files 82 ; ls / | wc -l 82
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      しかし実際には、シェルによってファイル名のリストに展開されるファイル名テンプレートにより、より簡単で信頼性の高いものになります。
 ; files2 := /* ; echo $#files2 82
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      組み込みコマンド
組み込みコマンドには、通常のコマンドと文字列の操作用の2つのタイプがあります。
通常のコマンドは、アプリケーション/スクリプトの起動と同じ方法で呼び出されます。
 ; run /lib/sh/profile ; load std ; unload std ; exit
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      文字列を操作するためのコマンドは、
${ }
      
      を介して呼び出され、標準の文字列を返します(つまり、stdoutに出力せず、たとえば、変数の値へのアピールが返されます)。 これらは、通常のコマンドのパラメーターで使用するか、変数に値を割り当てる右側で使用する必要があります。 たとえば、コマンド
${quote}
      
      は、渡された文字列のリストを1行で
${unquote}
      
      、
${unquote}
      
      は反対の操作を実行して、1行を文字列のリストに変換します。
 ; list = 'ab' cd ; echo $list abcd ; echo ${quote $list} 'ab' cd ; echo ${unquote ${quote $list}} abcd
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      if、for、関数を追加
私が約束したように、これらの非常に必要なものを「裸のsh」で実装する方法を示します。 もちろん、実際にはこれは必要ありません。stdロード可能モジュールは必要なものすべてを提供し、それを使用する方がはるかに便利です。 しかし、それにもかかわらず、この実装は、裸のshの機能のデモンストレーションとして興味深いものです。
すべてが排他的に使用して実装されます:
- 変数
 - 行と行リスト
 - コードブロックとそのパラメーター
 
私たちは「機能」を実行します
前述したように、シェルコマンドは単なる行のリストです。最初の行はコマンドで、残りの行はそのパラメーターです。 また、シェルコマンドブロックは単なる文字列であり、変数に保存できます。 また、コマンドブロックはすべて、パラメーターを
$*
      
      、
$1
      
      などで受け取ります。
 ; hello = { echo Hello, $1! } ; $hello World Hello, World!
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      さらに、関数型プログラミングの最高の精神で関数をカリー化することもできます。 :)
 ; greet = {echo $1, $2!} ; hi = $greet Hi ; hello = $greet Hello ; $hi World Hi, World! ; $hello World Hello, World!
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      別の例-ブロックパラメーターを使用して、番号で目的のリストアイテムを取得できます。
 ; list = abcdef ; { echo $3 } $list c
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      あなたのために
実装することに興味があり、最小限の機能を実現した場合は、本格的な便利なものを作成しようとしませんでした。 forが必要でした(ループを1回停止する必要がありますが、これを行うのに問題がある場合は不要です)。
 ; do = { $* } ; dont = { } ; if = { (cmd cond) := $* { $2 $cmd } $cond $do $dont } ; for = { (var in list) := $* code := $$#* $iter $var $code $list } ; iter = { (var code list) := $* (cur list) := $list (next unused) := $list $if {$var=$cur; $code; $iter $var $code $list} $next } ; $for i in 10 20 30 { echo i is $i } i is 10 i is 20 i is 30 ;
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      面白いささいなこと
デフォルトでは、シェルはスクリプトの起動時に名前空間をフォークするため、スクリプトはそれを起動したプロセスの名前空間を変更できません。 これは、親の名前空間を構成するだけのタスクを持つスクリプトには適合しません。 このようなスクリプトは
#!/dis/sh -n
      
      始まる必要があります。
loaded
      
      さ
loaded
      
      組み込みコマンドは、ロードされたすべてのモジュールのすべての組み込みコマンドをリストします。 また、
whatis
      
      組み込みコマンドは、変数、関数、コマンドなどに関する情報を表示します。
シェルモジュールのリストを使用して変数
$autoload
      
      を作成すると、これらのモジュールは起動された各シェルに自動的にロードされます。
シェルには構文糖のサポートがあります:
&&
      
      および
||
      
      。 これらの演算子はbare shで使用できますが、組み込みの
and
      
      および
or
      
      コマンドの呼び出しに変換されます。これらはbare shではなく、 stdモジュールからのものです(したがって、
&&
      
      および
||
      
      は
load std
      
      後にのみ使用できます)。
 ; echo { cmd && cmd } {and {cmd} {cmd}} ; echo { cmd || cmd } {or {cmd} {cmd}}
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     
      Limboで作成されたアプリケーションは、たとえば、コマンドラインパラメーターで開始するとき、shコマンドのブロックを含む行は、 sh(2)モジュールを使用して非常に簡単に実行できます。
変数と行リストを使用したシェルの作業スタイルは、従来のシェルとは異なり、最初に言及されたときに、変数のパラメーターと値を一度だけシールドすることを考える必要があるという事実につながります。 そして、それらが持っているシンボルを考えることなく、好きな場所にそれらを転送できます。
まとめ
この短い記事は、インフェルニアンシェルのほぼ完全なリファレンスガイドです。 つまり、基本的なシェルのすべての機能が詳細に、そしてすべてのニュアンスと例とともに説明されています。 sh(1) , ,
$apid
      
      ,
$prompt
      
      , - ,
sh
      
      ., .
/, :
- ( )
 -       
^
 -      
=
 -      
$var
,$#var
$"var
 
/bin/false
      
      :)
run
      
      ).
, . , sh-std(1) :
-     ( 
and
,or
,if
) -        ( 
!
,~
,no
) -     ( 
apply
,for
,while
,getlines
) - 両方のタイプの関数(
fn
、subfn
) - 例外とステータスとの仕事(
raise
、rescue
、status
) - 作業列、およびリスト(
${hd}
、${tl}
、${index}
、${split}
、${join}
) - など