saphire シェル

1.文法

1.0.0 １コマンド

    コマンド名 引数1 引数2 ...

    を一つのコマンドを実行します。引数の数は任意です。
    コマンドと引数の区切りにはスペースとタブが使えます。
    実行するコマンドはディスクに保存されたプログラムが使用できます。コマンドを検索するパスには環境変数PATHが使用されます。例えばPATHに/bin:/usr/binが入っていれば（:がPATHの区切りです。例えばexport PATH=/home/hoge/bin\:/home/hoge2/binと設定します。saphireでは:は特別な意味を持つのでクォートが必要です)

    >ls -al

    と実行すると/bin/lsと/usr/bin/lsが検索され先に検索された/bin/lsが実行されます。
    
    引数には慣習的に-で始まるオプション, --で始まるロングオプションがあります。ロングオプションは-lといった一文字のオプションが覚えにくいという理由から覚えやすい--listなどと分かりやすいオプション名で使えるようになったものです。
    引数の書き方としては、lsなど一般的な外部コマンドでは-a -lと二つオプションをつけることと-alと一つのオプションをつけることは同じ意味として処理されます。ただ残念なことにsaphireの組み込みコマンドでは-alは-aと-lとは見なされません。-a -lと分けて書いてください。またオプションに引数を取るコマンドでは一般的に-T 4と書くことと-T4と書くことは同じですが、saphireの組み込みコマンドでは-T 4と書いてください。

1.0.1 標準入力、標準出力、エラー出力とリダイレクト

    一つのコマンドには入力元、出力先、エラー出力先があります。例えばvimという文章編集コマンドを実行しているときは、入力元はキーボード、出力先は画面、エラー出力先も画面です。エラー出力というのはコマンドに不具合があった場合にそれを知らせる特別なメッセージを表示する場所のことです。UNIXではこの入力元や出力先を設定によってファイルに変えることができます。それがリダイレクトです。saphireには以下のリダイレクトがあります。

    > ファイル名             標準出力をファイルに書き込む
    %2> ファイル名           エラー出力をファイルに書き込む
    >> ファイル名            標準出力をファイルに追記
    %2>> ファイル名          エラー出力をファイルに追記
    < ファイル名             ファイルを標準入力としてコマンドに渡す
    %>                       エラー出力を標準出力として扱う（両方標準出力に流す)
    %>>

    例

    puts ファイルに書き込む文字列 > file1

    とするとfile1に「ファイルに書き込む文字列」という文字列が書き込まれます
    
1.0.2 １文

    コマンド1 引数... リダイレクト... | コマンド2 引数... リダイレクト...

    とコマンドを | でつなげたものが一文です。コマンド1の出力先がコマンド2の入力先として扱われて、また、そのコマンド2の出力先が次のコマンドの入力先として扱われ、、、と最後のコマンドまで実行すれば最後のコマンドが画面に出力します。
    これをパイプでつなぐといいます。

    文末には以下の区切りが使えます。

    ;               普通の文の区切り
    改行            普通の文の区切り
    &&              次の文は文のリターンコードが0(実行成功を表す)でないと実行されません
    ||              次の文は文のリターンコードが0以外(実行失敗)でないと実行されません
    &               文をバックグランド実行します

    また変数の展開は文を単位で行われます。

1.0.3 バックグラウンド処理
    文は&をつけるとバックグランド実行となります。ただし組み込みコマンドやユーザーコマンドの実行は並列的に行うことができません。有効なのは外部コマンドだけです。例えばdefはユーザーコマンドを定義するコマンドですが

    > def fun { sleep 10 }
    > fun &

    はバックグランド実行できません。

    > def fun { sleep 10 & }
    > fun

    は可能です。

    jobsでバックグラウンドや実行中サスペンド(CTRL-Z)したプロセスグループの一覧が見れます。

    fg jobsの一覧の番号

    でそのプロセスグループをフォアグランドに持ってくることができます。

    bg jobsの一覧の番号

    でそのプロセスグループにSIGCONTが送れます。つまり停止したプロセスを再開できます。停止する場面は例えばcpでコピー中にCTRL-Zした場合です。この場合bgするとバックグラウンドでコピーの続きをやってくれます。

1.1 クォート

    クォートとは引用を表し引用符で括るなどとして他の文として解釈されないようにするためのものです。使っている特別な記号を単なる文字列として処理したいときに使います。
    saphireには一文字のクォートとシングルクォートとダブルクォートがあります。一文字のクォートは\で、この後に来た記号や文字はsaphireで特別な意味を失います。例えば行末を表す;はそのままでは行末としてsaphireが処理しますが、\;とすると;はその意味を失い文字列として処理します。クォートは排他的です。別のクォート中はクォートは意味を失います。

    > puts 一つ目の文字列を表示; puts ２つ目の文字列を表示
    一つ目の文字列を表示
    ２つ目の文字列を表示

    > puts 一つ目の文字列を表示\; puts ２つ目の文字列を表示
    一つ目の文字列を表示; puts ２つ目の文字列を表示

    \自身を使いたい場合は\\を使います。

    > puts これが\\文字です
    これが\文字です

    シングルクォートは一つ目の'から２つ目の'までを文字列として扱います。

    > puts '一つ目の文字列; puts 二つ目の文字列'
    一つ目の文字列; puts 二つ目の文字列

    ダブルクォートも同じです。

    > puts "一つ目の文字列; puts 二つ目の文字列"
    一つ目の文字列; puts 二つ目の文字列

    > puts "シャア \赤い彗星\ アズナブル"
    シャア \赤い彗星\ アズナブル

    シングルクォートやダブルクォートの違いはダブルクォートは中で変数を展開する点です。変数は文字列を格納する入れ物です。

    > futatuna=赤い彗星
    > puts "シャア "\""$futatuna"\"" アズナブル"
    シャア "赤い彗星" アズナブル

    > futatuna=赤い彗星
    > puts 'シャア "$futatuna" アズナブル'
    シャア "$futatuna" アズナブル

    変数以外にも$()によるコマンド置換も同じ規則です。

    クォートとクォートの組み合わせですが、全てのクォートは他のクォート内では意味を失います。

    > puts "\aaa"
    \aaa

    > puts '\aaa'
    \aaa

    > puts "'"
    '

    > puts '"'
    "

    > puts \"
    "

    > puts \'
    '

1.2 特殊文字列

    saphireではラインフィールドやキャリッジリターン, タブなどを埋め込めます。
    \n ラインフィールドとして扱う
    \r キャリッジリターンとして扱う
    \t タブとして扱う

    printは文字列を表示するコマンドです。putsと違い最後に改行しません。

    > print 初めまして\nこんにちは\n
    初めまして
    こんにちは

    > print 初めまして\nこんにちは\n
    初めまして
    こんにちは

    > print 初めまして\\nこんにちは\\n
    初めまして\nこんにちは\n

1.2.5 空文字
    
    ''
    ""

    は空の文字列がコマンドに追加されます。

    > puts "aaaa.c" | sub '\.' ''
    aaaac

1.3 算術演算

    足し算や掛け算など数値計算を行います。書式はbashと同じく$((計算式))です。
    使える演算子は

    (計算式)          計算順の優先順位を変える
    pai               3.14...を表す
    log 計算式
    int 計算式
    ceil 計算式
    floor 計算式
    log10 計算式
    abs 計算式
    exp 計算式
    sin 計算式
    cos 計算式
    asin 計算式
    acos 計算式
    atan 計算式
    sinh 計算式
    cosh 計算式
    sqrt 計算式

    式 ** 式 指数計算
    式 * 式  乗算
    式 / 式  除算
    式 % 式  除算の余りを得る
    式 + 式  足し算
    式 - 式  引き算

    上記は優先順位順です

    > puts $((1.1 + 1))
    2.1

    > a=1.1
    > puts $(( $a * 3 ))
    3.3

    > puts $(( log 3 + 3 ))    # log 6と同じです
    1.791759

    > puts $(( (log 3) + 3))
    4.098612

1.3.1 変数
    変数にはローカル変数、グローバル変数、環境変数があります。

    ローカル変数はスタックを使った普通の変数です。関数が呼ばれるごとにフレームが積まれ、関数から抜けるごとにフレームが除去されます。
    代入方法はa=1として=が混じったコマンド名を使い代入します。注意してほしいのはa = 1とは書けないことです。なぜならa = 1とするとaというコマンドに=と1という引数があることになるからです。a=1などとしてください。
    参照方法は$aと変数名に$をつけてください。${a}でも可です。また$$aというのもあります。この違いですが${a}は単に表記上の問題のためにあります。$aと変わりがありません。$aと$$aの違いは、$aの場合saphireが使う特殊文字があればクォートします。$$aはクォートしません。

    >a="a;b"; puts $a
    a;b

    >a="a;b"; puts $$a
    command not found

    となります。$aの場合はputs a\;bと展開されるのに対し$$aの場合はputs a;bと展開されてbというコマンドがないというエラーがでます。

    > a=1; puts $a
    1
    > b=b; puts a${b}c      # puts a$bcとすると$bcという変数になってしまう
    abc
    > a=1; i=a; puts ${$i}    # perlのように$$iは使えません
    1
    > a=1; def fun { a=2; puts $a; }; fun; puts $a
    2
    1
    > var a=1; puts $a
    1
    > ls / | var a b c; puts $a $b $c     # パイプでデータを受け取れます
    bin boot cdrom

    グローバル変数は関数内や関数の外でも使える変数です。
    関数の外でa=1などとするとグローバル変数が作成されます

    環境変数はsaphireから起動したプロセス中でも参照できる変数です。

    変数の参照時の優先順位は

    ローカル変数 > グローバル変数 = 環境変数 = 配列 = 連想配列

    となっています。
    というかグローバル変数と環境変数と配列と連想配列は
    同じ名前で一つを定義すると他は消されます

    変数名には英数字と_が使えます。環境変数はアプリケーションによっては記号が使えないことが問題になるかもしれません。

    変数とシングル、ダブルクォートの関係ですが上で書いたとおり、ダブルクォート中は変数の展開をして、シングルクォート中は変数の展開はしません。

    >a="a;b"; puts $a
    a;b

    >a="a;b"; puts '$$a'
    $$a

    >a="a;b"; puts "$a"
    a\;b

    変数には添え字があって、部分文字列を得ることができます。

    >a=abc; puts $a[0]
    a

    >a=abc; puts $a[0..1]
    ab

    >a=abc; puts $a[0..-2]
    ab

    >a=abc; puts $a[0,0,0,1]
    aaab

    >a=abcdefg; puts $a[0..1, 3..4, 0]
    abdea

    あと変数には配列があります。配列はグローバル変数と同じで各関数共通に使うことができます。（ローカル変数としては扱えない）
    配列は$配列名で配列の全ての要素を空白区切りで貼り付けます。$配列名<,>で区切り文字を,に変えたりすることができます。$配列名[インデックス]で要素にアクセスできます。$配列名_sizeで配列のサイズにアクセスできます。

    > ary_new A
    > A[0]=a        # ary_new A a b c でも良い
    > A[1]=b
    > A[2]=c
    > puts $A[0];
    a
    > print $A[1..2]
    b
    c
    > print $A[1,2]
    b
    c
    >print $A[0, 2, 2, 0..-1]
    a
    c
    c
    a
    b
    c

    >print $A[2..1]
    c
    b

    > print $A
    a
    b
    c
    > puts $A<,>
    a,b,c
    > puts $A_size
    3
    > puts $A[1..2]< >
    b c

    > cat main.c | ary_new A    # 配列Aに各行が要素として格納されます
    > puts $A[0..1]
    #include <stdio.h>
    #include <stdlib.h>

    もう一つ変数にはハッシュ（連想配列)があります。連想配列も配列と同じでローカル変数としては使えません。

    連想配列にはすべての文字がキーに使えます。(アスキー文字以外にUTF-8文字も使えます)

    >hash_new a
    >a[a]=abc
    >a[あ]=def
    >puts $a[a]
    abc

    >puts $a[あ]
    def

    >print $a
    a
    abc
    あ
    def

    >puts $a<,>
    a,abc,あ,def

    >puts $a | lines \*2+0
    abc  # 値をとる
    def

    >puts $a | lines \*2+1
    a     # キーをとる
    あ

    高速な変数参照方法として@変数名があります。これは一単語として変数を展開します。つまり

    >a=abc; puts aaa@a

    とするとputsにはaaaとabc二つの引数が渡されます。

    >a=abc; puts aaa abc

    と同じです。

    >a=abc; puts aaa$a

    とするとputsにはaaaabcのひとつの引数が渡されれるのと違います。

    巨大なテキストファイルを処理するときのループでは@を使うことをお勧めします。

    > cat big_file.txt | each { | split | ary_new a; puts @a[0] @a[1] }

    あと、巨大なテキストファイルをコマンドラインに貼り付ける時にも@を使うことをお勧めします。たとえばbig_file.txtが数Mバイトのサイズがあるとき

    > cat big_file.txt | var -a a; puts $a

    はputs (big_file.txtの内容)と展開してから、その巨大なコマンドラインを字句解析して固まってしまいますが

    > cat big_file.txt | var -a a; puts @a

    は@aを変数aとして字句解析してからvmの内部でaを展開してputsに直接わたします。結果、固まりません。

    @の仲間に@@変数名があります。
    これは配列の場合ひとつの要素が一つの引数として渡されます。

    > ls | ary_new a; cp @a /tmp
    aの内容がすべて一つの引数として展開されエラー

    > ls | ary_new a; cp @@a /tmp
    aの内容一つずつ一つの引数として展開されるのでok

    @, @@による変数の参照ではパーサーを介さないのでチルダ展開は無効になります

1.3.5 リターンコード(終了コード)

    コマンドを実行したら、そのリターンコードがローカル変数$RCODEに入っています。

    >true; puts $RCODE
    0

    >false; puts $RCODE
    1

    リターンコードにはコマンド実行中にエラーがあった場合に0以外の数値が入力されます。

    ! 文(コマンド 引数 ... | コマンド ....)

    コマンドを実行してリターンコードを反転する。
    文のリターンコードが０以外ならなら０に０なら１にします。

    > ! puts aaa\nbbb | match -q a; puts $RCODE
    1

1.4 コマンド展開

    コマンド結果をコマンドに貼り付けます。書式は$(複文) $$(複文)

    >$(print "puts aaa")
    command not found

    >$$(print "puts aaa")
    aaa

    上はputs\ aaaが貼り付けられるのでエラー
    下はputs aaaが貼り付けられるのでputs aaaが実行される

    範囲指定による部分文字列の取り出しも可能です

    >puts $(ls)
    AUTHOR

    >puts $(ls)[0..2]
    AUT

    @()による高速なコマンド展開もあります。

    >puts @(print a: print b)
    ab

    ただ、こちらは結果はコマンドの引数でひとつとして扱われます。

    >puts @(print a)@(print b)
    a b

    $()はコマンド結果をコマンドラインに貼り付けて字句解析されてから実行されますが、@()は実行時にvmで実行されて直接putsに渡されます

    例えばbig_fileが数Mバイトのテキストファイルなら

    > puts $(cat big_file.txt)

    はbig_file.txtiがコマンドラインに貼り付けられて字句解析してからputs が実行されるので膨大な量のコマンドラインを字句解析するので固まりますが

    > puts @(cat big_file.txt)

    は固まりません。

    もう一つあります。@@()です。
    これはコマンドの出力結果が一行ごとに一つの引数として扱われます

    > cp @(ls) /tmp
    lsの内容がすべて一つの引数として展開されエラー

    > cp @@(ls) /tmp
    lsの内容一つずつ一つの引数として展開されるのでok

1.6 グロブ

    カレントディレクトリを基点としてグロブにマッチするファイル名を貼り付けます。

    *                       任意の文字列
    ?                       任意の一文字
    []                      文字列クラス。[abc]ならaかbかcか。[a-z]ならa-z全部。[^a]はa以外。

    > puts [^mM]*
    a a.c aaa autom4te.cache bin conf10097.dir conf11225.dir conf15278.dir conf22554.sh conf9022.dir config.cache config.h config.h.in config.log config.status configure configure.in doc install.sh libsaphire.so.1.0 saphire saphire.c saphire.c.bak saphire.c.bak2 saphire.h saphire.o saphire.sao saphire.sh test.c tmp todo.txt usage.en.txt usage.ja.txt

    カレントディレクトリからm, M以外で始まるファイル名を表示

    注意してほしいのは.*は.から始まるファイル名にマッチしますがsaphireでは.と..にはマッチしません。

1.7.1 グローバルパイプ

    最後にコマンド名を書かずにパイプで終わるとグローバルパイプに書き込まれます。逆にコマンド名を書かずにパイプで始まるとグローバルパイプから読み込まれます。書き込んだ後読み込む前に別の処理を行っても大丈夫です。

    > ls | match -g . | join , |> 
    > lv main.c
    > sleep 10
    > |> less                      # ls | match -g . | join ,の結果がlessされる

    グローバルパイプは何度も読み込めます。
    > echo aaa |>
    > |> print
    aaa
    > |> print
    aaa

    スタッカブルなグローバルパイプもあります。

    > echo aaa |-
    > echo bbb |-
    > |- print
    bbb
    > |- print
    aaa

    キューなグローバルパイプもあります。

    > echo aaa |~
    > echo bbb |~
    > |~ print
    aaa
    > |~ cat
    bbb

    ナンバーグローバルパイプもあります。

    > echo aaa |1
    > echo bbb |2
    > |1 print
    aaa
    > |2 print
    bb

    追記

    > echo aaa |>
    > echo bbb |>>
    > |> print
    aaa
    bbb

    |>数値で |> lines 数値 | chompと同じ

    >puts a\nb\nc |>
    >|>1pomch
    b
    >|>2 pomch
    c

1.7.1 STDINパイプ
    
    |は"read -p -a"と同じです。標準入力から読み込みます。

    > puts aaa\nbbb\nccc | (| print; | print; | sub -g . X)
    aaa
    bbb
    ccc
    aaa
    bbb
    ccc
    XXX
    XXX
    XXX

1.7.2 ファイルハンドル
    
    <>
    はreadと同義です

    >puts aaa\nbbb\nccc | while(<> |>){ |> chomp | add D\n }
    aaaD
    bbbD
    cccD

1.7.3 ブロック

    ブロックには中括弧によるブロックがあります。

    > def fun { print エフェメラ; puts クルツ }

    ブロックはパーサーによってコンパイルされたものがコマンドに渡されます。

    > eval { ls }
    main.c sub.c

    > print { ls }
    何も表示しない

    ユーザー関数でブロックを実行したい場合yeildを使います。

    > def fun { yeild 0; yield 1; yield 1 }
    > fun { puts a } { puts b}
    a
    b
    b

    ブロックには引数が渡せます

    > def fun { yeild 0 data1 data2 }
    > fun { :a b: puts $a $b }
    data1
    data2

1.7.4 サブシェル

    ()で括ったコマンドを一つのグループとして扱います。saphireのサブシェルは新たにsaphireを実行してそこでコマンドを実行させているわけではありません。新たにプロセスを作ってはいません。

    > (print aaa; print bbb) | more
    aaabbb

    > if( ((true || flase) && true) || false ) { puts yes }
    yes

1.8 制御文

    if文
    if (条件文) {実行文} elif (条件文) {実行文} .... else {実行文}

    > a=1; if ([ $a = 1 ]) { puts yes } else { puts no } | uc
    YES

    > a=1; vim a.sh
    if ([ $a = 1 ]) {
        puts yes
    } else {
        puts no
    }
    > load a.sh | uc
    YES

    ブロックの中では改行が許されます。

    while文

    while (条件式) {実行文}

    > i=0; while([ $i -lt 3 ]) { puts $i; i=$(($i+1)) }
    0
    1
    2

    > vim a.sh
    max=0
    cat main.c | each {
        | length | var len

        if([ $max -lt $len ]) {
            max=$len
        }
    }

    puts main.cの最大行は$max文字です。

    > load a.sh
    main.cの最大行は78文字です。

    while文はbreakやCTRL-Cで割り込むことで抜けられます。

    def文

    def 関数名 ブロック

    > def fun { puts $ARGV_size; puts $ARGV[0] $ARGV[1]; puts $ARGV<\n> }; fun a b c
    3
    a b
    a
    b
    c

    >vim a.sa
    def fun {
        a=2
        global b=3
        puts $a $b
    }

    > load a.sa; a=1; global b=2; (puts $a $b; fun; puts $a $b) | less
    1 2
    2 3
    1 3

    関数内では引数はARGVという配列に渡されます。また中で宣言されたローカル変数(varによる宣言やa=1など)は関数から抜けるときに自動的に消えます。

    特殊な構文として引数名を指定することができます。

    > def fun(_ARGV) { puts $_ARGV_size; puts $_ARGV<,> }; fun a b c
    3
    a,b,c

    このとき注意してほしいのはこのように引数が指定されたユーザー関数funが呼ばれた時に関数用にスタックは作られないということです。つまりfun内では外のローカル変数にアクセスできますし、fun内で定義したローカル変数は外でも生きています。これはブロックを使ったユーザー定義の制御構文の定義に使用します。

    > vim saphire.sh
    def each(_ARGV) {
        NR=1
        while(<> |>) {
            yield 0

            NR=$(($NR + 1))
        }
    }

   このように定義するとeachに渡すブロックで外で定義されたローカル変数にアクセスできます。

   > a=1; ls -al | each { |> var line; puts "$a:$line"; ++ a }; puts $a

   関数はreturnで戻れます

1.9 ブレース展開
    
    今のところありません。

2.0 プロセス置換

    <(複文) 

    コマンドの結果をファイルとして扱う

    > cat <(echo aaa) <(echo bbb)
    aaa
    bbb

    > diff <(ls ./etc) <(ls /etc)

    ls ./etcとls /etcの両方の出力がdiffされる

    >(複文)

    ファイルとして扱ったコマンド結果をコマンドとして扱う

    > ls | tee >(grep main | less) | less

    ls の結果がlessされてから、ls | grep mainの結果がlessされる

    saphireのプロセス置換はパーサーによる変換だけで処理されます。
    >(), <()のところがtmpに作られる一時ファイルに置き換えて
    色々文も追加して処理させています。
    bashやzshとは実装方が違うと思われるので動作に違いがあるかもしれません。
    saphireの実装による利点は端末制御を行うコマンドも>()の中に
    書ける点です。

2.0.1 例外処理

    try { 文 } catch { 例外発生時の処理 }

    文の戻り値が0以外なら catch後のブロックを実行します
    エラーが発生したときもです。

    > try { false; puts aaa } catch { puts bbb }
    bbb

    注意してほしいのはパイプの最後のコマンドの戻り値しか見ないことです。

    > try { false | cat; puts aaa } catch { puts bbb }
    aaa

    エラーが発生した場合はパイプの途中でもcatch以下に移行します。

    > try { fg 10000 | less } catch { puts bbb }
    bbb

2.0.5 インタラクティブシェル
    
    bashと同じくreadlineを使っているのでbashと同じような使用方法が使えます。特徴としてはmigemoによる日本語ファイル名の補完を行う点です。

    > ls
    UTF8時代だから日本語ファイル名を使おう エフェメラクルツ

    > ls efe[TAB]
    > ls エフェメラクルツ

    と補完されます

    C-xを押すとファイル選択メニューが表示されます

    ファイル選択メニューでの操作は

    SPACE ファイル選択 TAB,W 選択したファイルをコマンドラインに挿入
    BackSpace 親ディレクトリに移動
    Return ディレクトリ選択時そのディレクトリに移動

    実装はreadline.cで行っています。
    
2.0.6 オプション
    -c "コマンド" コマンドの実行
    -rs ランタイムスクリプトをソースファイルとして実行
    -ro ランタイムスクリプトをコンパイル済みファイルとして実行(デフォルト)
    -rn ランタイムスクリプトを実行しない
    -ts ターミナル漢字コードをsjisとする
    -tw ターミナル漢字コードをutf8とする
    -te ターミナル漢字コードをeucjpとする
    -s saphireの漢字コードをsjisとする
    -w saphireの漢字コードをutf8とする
    -e saphireの漢字コードをeucjpとする
    -Lu ラインフィールドの設定をLFとする
    -Lm ラインフィールドの設定をCRとする
    -Lw ラインフィールドの設定をCRLFとする
    --version バージョンを表示する

    bashシェルでsaphireを使うこともできます。

    $ ls | saphire -c '| each { | scan . | each { | add -n 0 X } | join "" | pomch }' | less
    
2.0.7 スクリプトファイルの実行

    saphire スクリプトファイル名
    saphiresh スクリプトファイル名
    
    とすることでスクリプトファイルを実行できます。
    (saphireの方がインタラクティブシェル関連のライブラリをロードしない分速い)
    
    グローバル変数$SCRIPT_FILE_NAMEにスクリプトファイル名が入っています。
    配列$ARGVに引数が入っています。
    
    スクリプトファイルはUTF8で書かれている必要があります。
    また改行コードはLFで無いといけません。
    
2.1 組み込みコマンド

    msleep

    sleepと同じく実行を止めます。画面にアニメーションが表示されます。

    true

    リターンコード 0を返します

    false

    リターンコード 1を返します

    [

    -I 入力を受け取って比較

    条件判定式を実行します。詳しくはman [してください。ただしbashなどの[の全ての条件式を実装しているわけではありません。
    saphireの[の特徴としては[ 文字列 -re 正規表現 ]で正規表現の条件式が使えるところです。マッチした場合以下の特殊なローカル変数を使います。

    $PREMATCH  マッチした文字列の前の部分が入る
    $MATCH マッチした文字列が入る
    $POSTMATCH マッチした文字列の後の部分が入る

    $数値 グループ化でマッチしたものが前から順番に入ります

    >[ abcdefg -re c ]; puts $PREMATCH,$MATCH,$POSTMATCH
    ab,c,defg

    グループ化した文字列は$数値に入ります。

    >[ abcdefg -re '(.+)(c)(.+)' ]; puts $1,$2,$3
    ab,c,defg

    -reの仲間に下があります

    -rei 大文字小文字区別しない
    -rem 複数行対応
    -reim, -remi 大文字小文字区別しない、複数行対応

    あとsaphireには文字列の大小比較があります。

    -slt

    左側の文字列が小さいなら真

    -sle

    左側の文字列が小さいか等しいなら真

    -sgt

    右側の文字列が大きいなら真

    -sge

    右側の文字列が大きいか等しいなら真

    -silt

    左側の文字列が小さいなら真(大文字小文字は同じ物として扱う)

    -sile

    左側の文字列が小さいか等しいなら真(大文字小文字は同じ物として扱う)

    -sigt

    右側の文字列が大きいなら真(大文字小文字は同じ物として扱う)

    -sige

    右側の文字列が大きいか等しいなら真(大文字小文字は同じ物として扱う)

    ほかにもsaphireの[は文のパイプの２番目以降で実行されると標準入力から文字列を受け取って、その文字列を対象文字列の引数として判定することができます。

    >print aaa | [ = aaa ]; puts $RCODE
    0

    >print aaa | [ ! = aaa ]; puts $RCODE
    1

    >print aaa | if([ -I = aaa ]) { puts yes } else { no }
    yes

    >print aaa | if(| [ = aaa ]) { puts yes } else { no }
    yes

    この条件式は特別な構文ではなく組み込みコマンドなので書き方に注意してください
    [aaa=aaa]  ->[aaa=aaa]というコマンドは無いのでだめ

    [ aaa=aaa ] -> 第二引数がaaa=aaaとなるのでだめ

    [ aaa = aaa] -> 第３引数がaa]となるのでだめ
    [ aaa = aaa ] -> ok

    index 対象文字列 検索文字列
    index -I 検索したい文字列
    | index 検索したい文字列

    rindex 対象文字列 検索文字列
    rindex -I 検索したい文字列
    | rindex 検索したい文字列

    文字列から検索したい文字列が現れる場所(文字列の先頭からのインデックス)を出力します。文字列のインデックスは0からです。rindexは検索を右から左に行います

    -q 判定だけして出力をしない。リターンコードの値だけを見たいときに使います。

    -nl 出力の最後に改行を加えない

    -I 標準入力から文字列を受け取る(saphireの組み込みコマンドではパイプの二番目以降にコマンドが実行されると自動的に-Iがついたものとして処理されます)
    -c 数値 検索回数の指定。2が指定されると２回目のマッチした位置が返されます

    -n 数値 文字列の検索開始位置を指定します。
    -b バイト単位で数えたインデックスを返す
    -t ターミナルでの文字列幅で数えたインデックスを返す。
    -i 英字の大文字と小文字を区別しない

    length 文字列
    length -I
    | length 

    文字列の長さを出力します。

    -I 入力された文字列の長さを返します。(パイプの二つ目以降にコマンドがある場合は必要ありません。)
    -nl 改行を出力しません
    -s 文字列のエンコードをSJISとして数えます。
    -e 文字列のエンコードをeucjpとして数えます。
    -w 文字列のエンコードをUTF8として数えます。
    -t ターミナルでの文字列の幅を返します。
    -b バイト数を返します
    -L 改行の数を返す

    uc
    uc -I
    | uc

    文字列を大文字に変換します

    -I 入力された文字列を処理します。(パイプの二つ目以降にコマンドがある場合は必要ありません。)
    -l 改行を出力します。
    -s sjisエンコードとして処理
    -e eucjpエンコードとして処理
    -w utf8エンコードとして処理

    lc
    lc -I
    | lc

    文字列を小文字に変換します

    -I 入力された文字列を処理します。(パイプの二つ目以降にコマンドがある場合は必要ありません。)
    -l 改行を出力します。
    -s sjisエンコードとして処理
    -e eucjpエンコードとして処理
    -w utf8エンコードとして処理

    chomp
    chomp -I
    | chomp

    文字列の最後にある改行を除去します。(LF, CRLF, CRのどれでも対応しています)

    pomch
    | pomch

    文字列の最後に改行が無ければ改行を追加する(LF, CRLF, CRの区別はlinefieldの設定による)

    -Lw CRLFを加える
    -Lu LFを加える
    -Lm CRを加える

    substr 対象文字列 インデックス [カウント]
    substr -I インデックス [カウント]
    | substr インデックス [カウント]

    -c 入力をchompして（改行を除去して）から処理します
    -l 最後に改行文字を出力します
    -s 文字列のエンコードをSJISとして数えます。
    -e 文字列のエンコードをeucjpとして数えます。
    -w 文字列のエンコードをUTF8として数えます。
    -b バイトi単位で数えます

    eval ブロック
    | eval

    ブロックを実行します。
    | evalはパイプからデータを読み込んで実行します

    fg ジョブ番号

    ジョブ番号のジョブをフォアグランドグループにします。（前面に持ってくる）
    bg ジョブ番号

    ジョブ番号のジョブにSIGCONTを送ります。バックグラウンドのまま実行を再開します。

    jobs

    ジョブの一覧を表示します。

    rehash

    コマンドライン補完のコマンド名の一覧のキャッシュを更新します。新しいプログラムがインストールされたら実行すると、そのプログラムの補完が有効になります。

    kanjicode [-s|-e|-w]

    -s saphireが処理する漢字コードをSJISとして設定します
    -e saphireが処理する漢字コードをEUCJPとして設定します
    -w saphireが処理する漢字コードをUTF8として設定します
    -q 実行結果を出力しません

    saphireが処理する漢字コードというのは具体的には変数や配列の添え字の数え方や文字の数え方に関連する内部関数の-s,-e,-wを指定しない場合のデフォルトの文字コードということです

    linefield [-Lw|-Lm|-Lu]

    -Lw saphireが処理する改行コードをCRLFとして処理します
    -Lm saphireが処理する改行コードをCRとして処理します
    -Lu saphireが処理する改行コードをLFとして処理します
    -q 実行結果を出力しません

    saphireが処理する改行コードというのは組み込みコマンドがパイプによって文字列を一行受け取る場合の改行コードです。ほかにもjoinやsplitなど組み込みコマンドの処理で使われます。

    var 変数名 変数名

    ローカル変数の宣言

    var 変数名=値 変数名=値...

    ローカル変数に値を代入したり初期化します

    var -I 変数名 変数名...
    | var 変数名 変数名...

    各変数名に入力から一行ずつ受け取って、chompして一行を各変数名に代入します

    > print aaa\nbbb | var a b; puts $a,$b
    aaa,bbb

    -ncオプションでchompせずに改行コードも一緒に変数に代入します

    -a パイプで受け取る文字列を一行単位ではなくて全て受け取る

    例 ls -al | var -a a

    aにls -alの出力が全て入ります

    -f 区切り文字(複数可能) 区切り文字列を改行でなく別のものに設定します。

    -I 入力から文字列を受け取り変数に設定する

    -p 出力を行う

    -l 改行を出力する

    global 変数名 変数名

    グローバル変数の宣言

    global 変数名=値 変数名=値...

    グローバル変数に値を代入したり初期化します

    global -I 変数名 変数名
    | global 変数名 変数名...

    各変数名に入力から一行ずつ受け取って、chompして一行を各変数名に代入します
    -ncオプションでchompせずに改行コードも一緒に変数に代入します

    -a パイプで受け取る文字列を一行単位ではなくて全て受け取る

    例 ls -al | global -a a

    aにls -alの出力が全て入ります

    -f 区切り文字(複数可能) 区切り文字列を改行でなく別のものに設定します。

    -nc 改行をchompせずに変数に代入する

    -I 入力から文字列を受け取り変数に設定する

    -p 出力を行う

    -l 改行を出力する

    export 変数名 変数名

    環境変数の宣言

    export 変数名=値 変数名=値...

    環境変数に値を代入したり初期化します

    export -I 変数名 変数名
    | export 変数名 変数名...

    各変数名に入力から一行ずつ受け取って、chompして一行を各変数名に代入します
    -ncオプションでchompせずに改行コードも一緒に変数に代入します

    -a パイプで受け取る文字列を一行単位ではなくて全て受け取る

    例 ls -al | export -a a

    aにls -alの出力が全て入ります

    -nc 改行をchompせずに変数に代入する

    -I 入力から文字列を受け取り変数に設定する

    -p 出力を行う

    -l 改行を出力する

    print 出力文字列
    print -I
    | print

    -l 文字列の最後に改行文字を追加する
    -I 入力を受け取って出力する
    -f 区切り文字を設定する

    puts 出力文字列
    puts -I
    | puts

    -I 入力を受け取って出力する
    -f 区切り文字を設定する

    compile ファイル名

    saphireのスクリプトをコンパイルしてコンパイル結果をファイル名.saoとして出力する

    load ファイル名

    saphireのスクリプトを読み込み実行する

    -sao コンパイル済みのスクリプトファイルを読み込み実行する

    exit

    -f ジョブがあっても強制的に終了する

    saphireを終了する

    split -I [区切り文字の正規表現]

    split 文字列 [区切り文字の正規表現]

    文字列を区切り文字の正規表現で区切って区切りを改行として出力する
    区切り文字が省略されると\s+が渡される

    | split [区切り文字の正規表現]

    入力を受け取り区切り文字で区切って区切りを改行として出力する

    -f 文字列  区切り文字を改行から文字列に変更するS
    -I 標準入力から入力を得る
    -m 複数行対応
    -i 大文字小文字無視
    -s 文字列のエンコードをSJISとして数えます。
    -e 文字列のエンコードをeucjpとして数えます。
    -w 文字列のエンコードをUTF8として数えます。

    >split "aaa bbb ccc"
    aaa
    bbb
    ccc

    >split "aaa,bbb,ccc" ,
    aaa
    bbb
    ccc

    >print aaa bbb ccc | split
    aaa
    bbb
    ccc

    >print aaa,bbb,ccc | split ,
    aaa
    bbb
    ccc

    >ls | chomp | split -f , -m | pomch
    main.c,test.c,Makefile,configure.in

    add 文字列 文字列 ...
    add { ブロック } { ブロック } ...

    パイプに文字列を追加する

    -n インデックス インデックス番目に文字列を追加する
    -L 行数 行数のところに文字列を追加する(0なら1行目)
    -s 文字列のエンコードをSJISとして数えます。
    -e 文字列のエンコードをeucjpとして数えます。
    -w 文字列のエンコードをUTF8として数えます。
    -b -nのインデックスをバイトi単位で数えます

    > print aaa | add ccc\n
    aaaccc

    > puts abc | add -n -2 { print d }
    abcd

    > puts アンディ\nエフェメラ\nタッタ | add -L 1 @(puts アード)
    アンディ
    アード
    エフェメラ
    タッタ

    > puts アンディ\nエフェメラ\nタッタ | add -L 1 {puts アード}
    アンディ
    アード
    エフェメラ
    タッタ

    del
    del 文字数
    del -n インデックス 文字数

    文字数分パイプの文字列を末尾から削除する

    -n 末尾からではなくてインデックスから文字列を削除する
    -s 文字列のエンコードをSJISとして数えます。
    -e 文字列のエンコードをeucjpとして数えます。
    -w 文字列のエンコードをUTF8として数えます。
    -b -nのインデックスをバイトi単位で数えます
    -L 行数 行数行を削除

    >print abcdE | del 
    abcd

    >print aaa\nbbb\nccc\nddd\neee | del -L 2 2
    aaa
    bbb
    eee

    join -I [区切り文字列]
    join 文字列 [区切り文字列]
    | join [区切り文字列]

    改行区切りの文字列に文字列を間に挟んでつなげる

    >ls saphire* | join , | del | add \n
    saphire,saphire.c,saphire.c.bak,saphire.h,saphire.sao,saphire.ksh,saphire.o

    x 文字列 個数

    文字列を個数分出力します

    | x 個数

    入力文字列を個数分出力します

    > print a | x 5 | pomch
    aaaaa

    | lines 行数 [-b|-B] (ブロック) 行数|式...

    入力から受け取り指定された行数の行を表示
    ブロックがあれば、各行が標準入力に入れられて
    ブロックが実行される

    -r 行を反転して表示する

    行数には

    数字 その行番号

    数字..数字 範囲指定

    \*数字1+数字2 y = 数字1*x + 数字2 [x=1,2,3...] のyにあたる行

    (lines \*2+1 --> 奇数の行)
    (lines \*2+0 --> 偶数の行)

    x数字1+数字2 y = 数値2から数字1個ずつ得る

    が使えます。

    -b ブロックは指定された全ての毎行に対してブロックを実行します。
    -B ブロックは指定された全ての行を入力に入れてからブロックを一回実行します

    >print aaa\nbbb\nccc | lines 0 0..1
    aaa
    aaa
    bbb

    >puts aaa\nbbb\nccc | lines -r
    ccc
    bbb
    aaa

    >print aaa\nbbb\nccc | lines 0 1..0
    aaa
    bbb
    aaa

    >print aaa\nbbb\nccc\nddd | lines 0 -B { | uc } 1 -B { | sub -g . D } 2..-1
    AAA
    DDD
    ccc
    ddd

    >ls | lines 0 -B { | var a } 1..-1 -b { | var -a b }

    aに１行目が
    bに最後の行が入ります

    >ls | lines 0 -B { | var a } 1..-1 -B { | var -a b }

    aに１行目が
    bに２行目以降が入ります

    > times 15 { :i: ++ i; puts i } | lines x5+0 -B { | join , }
    1,2,3,4,5
    6,7,8,9,10
    11,12,13,14,15

    | select 条件式

    条件式を成り立つ場合その行を出力

    >print aaa\nbbb\nccc | select { | [ -re ^a ] }
    aaa

    | rows 数値 [-b|-B] (ブロック)

    -s 文字列のエンコードをSJISとして扱いインデックスを数えます。
    -e 文字列のエンコードをeucjpとして扱いインデックスを数えます。
    -w 文字列のエンコードをUTF8として扱いインデックスを数えます。
    -r 文字列を逆順にして出力する

    数字 その文字インデックス
    数字..数字 文字インデックス範囲

    \*数字1+数字2 y = 数字1*x + 数字2 [x=1,2,3...] のyにあたる文字

    (rows \*2+1 --> 奇数の文字)
    (rows \*2+0 --> 偶数の文字)

    x数字1+数字2 y = 数値2から数字1個ずつ得る

    が使えます。

    -b ブロックは指定された全ての毎文字に対してブロックを実行します。
    -B ブロックは指定された全ての文字を入力に入れてからブロックを一回実行します

    パイプで渡された各文字ごとにブロックを実行するか
    ブロックが無ければ表示する

    -b ブロックは指定された文字全てで一度ブロックが実行されます
    -B ブロックは 指定された文字は標準入力に入られてブロックは一度だけ実行されます

    > print abcdefg | rows 0 3 -b { | puts }
    ad

    > print abcdefg | rows 0 -b { | uc } 1..-1 -b { | puts }
    Abcdefg

    > print abcdefg | rows 0 -b { | uc } 1..-1 -B { | puts }
    Ab
    c
    d
    e
    f
    g

    > print abcdefg | rows -r |pomch
    gfedcba

    ary_new 配列名 要素1 要素2 .....
    配列を宣言

    ary_new -I
    | ary_new 配列名

    入力から各行を１要素として配列に代入(各行はchompされる)

    -a 入力を改行で区切らず全ての入力を一つの要素として代入
    -nc １行を１要素として配列に代入する場合chompしない
    -I 入力を受け取り配列の要素として代入する
    -p 出力する
    -l 出力に改行をつける

    ary_add -I 配列名
    | ary_add 配列名

    入力から各行を１要素として配列に追加する(末尾)

    | ary_add -n インデックス 配列名

    インデックスの場所に要素を追加する

    -a 入力を全て代入
    -n 数値 数値のところにインサート
    -nc chompしない
    -p 出力を行う
    -l 出力に改行をつける

    ary_erase 配列名 インデックス

    配列の要素を削除

    match

    正規表現の比較演算子

    match -I [正規表現] -> 標準入力を[正規表現]で比較
    コマンド | match [正規表現] -> コマンドの出力全てを[正規表現]で比較
    match [文字列] [正規表現] -> [文字列]を[正規表現]で比較
    コマンド | match -L [正規表現] -> grepの様に各行に正規表現がマッチするか比較する。
    コマンド | match -g [正規表現] -> コマンドの出力全てを[正規表現]で比較。マッチしても止まらず何度も比較を繰り返す
    match -g [文字列] [正規表現] -> [文字列]を[正規表現]で比較。マッチしても何度も比較を繰り返す

    -q 出力しない
    -nl 出力を改行しない
    -n 行番号をつける
    -I 標準入力から比較する文字列を受け取る。コマンドの２つ目からは自動的に-Iがあるとみなす
    -f [文字列] グループ化のマッチした文字列の出力の区切りに[文字列]を使う。デフォルトはタブ
    -g グローバル。一文字ずつ比較する
    -L 行指向比較。一行ずつ比較する。grepと似た動きをする。
    -i 正規表現が大文字と小文字を区別しない
    -m 複数行マッチする
    -s sjisエンコードとして処理
    -e eucjpエンコードとして処理
    -w utf8エンコードとして処理

    $PREMATCH マッチした前の部分
    $MATCH マッチした部分
    $POSTMATCH マッチした後の部分

    $MATCH_COUNT マッチした回数

    $数値 グループ化でマッチしたものが前から順番に入ります

    >ls | match -L -n minato_curses
    11:minato_curses.c
    12:minato_curses.o
    13:minato_curses.h

    (行指向)

    >ls | match .
    A

    (一文字ヒットしたら以後比較しない)

    >ls | match -g .
    A
    U
    T
    H
    O
    R
    S

    >print 'file:123' | match '(.+?):(\d+)'
    file	123

    >print 'file:123' | match -f \n '(.+?):(\d+)'
    file
    123

    >print abcdef | match '..(.).(.).'; puts $1,$2
    c	e
    c,e

    >puts mikan | match -f , '(m)i(kan)'
    m,kan

    matchはegrepと違ってマッチした行ではなくてマッチした部分を出力します。
    egrepのように行指向で処理を行いたい場合は-Lをつけてください。
    またグループ化した場合はそのグループ化してマッチした部分をフィールド区切りで出力します。(フィールドの初期値はタブ)

    >puts "file:123" | match '(.+?):(\d+)'
    file	123

    matchは一行ごとに処理はしません。受け取った文字列全てに検索を行います。(デフォルトでは検索回数は１回だけ。-gで何回も)
    一行ごとに処理したい場合はeachなどと組み合わせてください

    > ls | each { | match -q -i m && | print }
    Makefile
    Makefile.in
    README.ja.txt
    autom4te.cache
    gmon.out.hayai
    gmon.out.osoi
    saphire_commands.c
    saphire_commands.o
    saphire_main.c
    saphire_main.o
    saphire_vm.c
    saphire_vm.o
    main.c

    scan 文字列 正規表現
    | scan 正規表現

    内部的にはmatch -g と同じです。

    [文字列]を[正規表現]で比較。マッチしても何度も比較を繰り返す

    -I 標準入力から比較する文字列を受け取る。コマンドの２つ目からは自動的に-Iがあるとみなす
    -f [文字列] グループ化のマッチした文字列の出力の区切りに[文字列]を使う。デフォルトはタブ
    -i 正規表現が大文字と小文字を区別しない
    -m 複数行マッチする
    -s sjisエンコードとして処理
    -e eucjpエンコードとして処理
    -w utf8エンコードとして処理

    $MATCH_COUNT マッチした回数

    $数値 グループ化でマッチしたものが前から順番に入ります

    >ls | scan .
    A
    U
    T
    H
    O
    R
    S

    scan 正規表現 { ブロック }

    マッチするごとにブロックが実行されます。標準入力にマッチした文字列が入ってます。ただし一文字ごとにブロックを起動するのはパフォーマンス的に我慢なら無いレベルだと思っていてください。

    > print aaaa123-bbb1234 | scan '\d+' { | x 2 | pomch }
    123123
    12341234

    > cat big_file.txt | scan . { | print }
    固まる。多分一日中。

    以下の変数が使えます。

    $PREMATCH マッチした所から前の部分の文字列
    $PREMATCH2 前回マッチした所までの前の部分の文字列
    $MATCH マッチした文字列
    $POSTMATCH マッチした所から後の部分の文字列
    $1〜$9 グループ化した文字列

    注意してほしいのは-mをつけないとscanは入力を行ごとに処理しているため使いづらい点です。

    > print 123b456\n789b000 | scan b { }

    のブロック内では
    1回目
    PREMATCH 123
    PREMATCH2
    MATCH b
    POSTMATCH 456
    2回目
    PREMATCH 789
    PREMATCH2
    MATCH b
    POSTMATCH 000

    となります。

    erase 文字列 正規表現
    | erase 正規表現

    正規表現にマッチする部分を文字列から削除する
    グループ化した場合はマッチする部分のうちグループ化された部分だけ文字列を削除する

    -g 何度も正規表現を繰り返す
    -m 複数行対応
    -i 大文字小文字無視
    -s sjisエンコードとして処理
    -e eucjpエンコードとして処理
    -w utf8エンコードとして処理

    > erase abcdefg\n .
    bcdefg

    > erase abcdefg\n '.$'
    abcdef

    > puts abcdefg | erase '(.).(.)$'
    abcdf

    sub 文字列 正規表現 変換後の文字列
    | sub 正規表現 変換後の文字列

    subは全体の変換を行います。
    (行志向の変換が行いたければwhile(<>|>), eachと組み合わせてください)

    >ls
    saphire_vm.c
    saphire_vm.o
    libsaphire.so
    libsaphire.so.1
    libsaphire.so.1.0
    main.c
    readline.c
    readline.o

    >ls | sub a A
    sAphire_vm.c
    saphire_vm.o
    libsaphire.so
    libsaphire.so.1
    libsaphire.so.1.0
    main.c
    readline.c
    readline.o

    >ls | each{ | sub a A }
    sAphire_vm.c
    sAphire_vm.o
    libsAphire.so
    libsAphire.so.1
    libsAphire.so.1.0
    mAin.c
    reAdline.c
    reAdline.o

    > sub -g aaa a x | pomch
    xxx

    グループ化した文字列は\1,\2,...に入ります。変換後の文字列で
    \1,\2を使うとグループ化した文字列が入ります。
    変換後の文字列に\自身を使いたい場合は\\を使ってください

    > print mikan | sub '(.)i(...)' '\1\2'
    mkan

    変換は一回しか行いません。-gをつけると何回も行ってくれます。

    -g グローバル。一行に二度以上マッチしていたら、２度以上変換を行います
    -q 出力を表示しない
    -i 大文字と小文字を区別しない
    -m 行をまたいだ正規表現
    -c 変換前にチェックする
    -s sjisエンコードとして処理
    -e eucjpエンコードとして処理
    -w utf8エンコードとして処理

    マッチした回数はローカル変数SUB_COUNTに入ります。

    sub 正規表現 { ブロック }

    マッチした文字列を標準入力にいれてマッチした地点でブロックを実行します。出力が変換結果となります。

    > print abcd | sub -g . { print x } | pomch
    xxxx

    ブロック内では

    $PREMATCH 文字列の最初からマッチした地点まで
    $PREMATCH2 前回マッチした文字列の最後から今回マッチした文字列まで
    $MATCH マッチした文字列
    $POSTMATCH マッチした文字列より後ろ
    $1 〜 $9 グループ化した文字列

    が使えます。

    注意してほしいのはsubは-mをつけないと行ごとに入力を処理している点です。。つまり

    > print 123b456\n789b000 | sub -g b { }

    のブロック内では
    1回目
    PREMATCH 123
    PREMATCH2
    MATCH b
    POSTMATCH 456
    2回目
    PREMATCH 789
    PREMATCH2
    MATCH b
    POSTMATCH 000

    となります。

    read

    -a すべてを読み込む
    -n 数字 数字行読み込む
    -p 読み込んでも読み込んだ分のバッファを消さない

    readはコンテキストに応じた標準入力から一行読み込む。もう一度読み込むと続きから読み込む。全てを読み込むとリターンコード1を返す。

    > puts aaa\nbbbb | read
    aaa

    > puts aaa\nbbb | (read | chomp | add D\n; read | chomp | add E\n)
    aaaD
    bbbE

    > puts aaa\nbbb | read -a
    aaa
    bbb

    > puts aaa\nbbb\nccc\neee\nfff | (read; read -n 2 | cat -n; read -a)
    aaa
          1 bbb
          2 ccc
    eee
    fff

    > puts aaa\nbbb\nccc | (read -a -p; read -a -p)
    aaa
    bbb
    ccc
    aaa
    bbb
    ccc

    read ファイル名

    ファイル名のファイルから一行読み込む。
    EOFに達すると1を返しcloseされる。
    ファイルはsaphireの終了時にcloseされる

    -n 数値 数値行読み込む
    -a 全て読み込む

    cd ディレクトリ

    ディレクトリに移動する。$PWDに現在のカレントディレクトリが入る

    cdだけだと$HOMEに移動する

    | selector

    入力から読み込んだテキストからユーザーが一行または複数行選択して選択した行を出力する

    データのエンコーディングはUTF8のみしか対応していません。

    -r 前回のカーソル位置、スクロールトップ位置を使用する。
    -c カーソル位置 カーソル位置を設定する
    -t スクロールトップ位置 スクロールトップ位置を設定する
    -m 複数行の選択を許す。スペースで複数行選択できる

    max 数値1 数値2

    大きいほうの数値を出力する

    -nl 出力の最後に改行を加えない

    min 数値1 数値2

    小さいほうの数値を出力する

    -nl 出力の最後に改行を加えない

    extname ファイル名

    -I 入力をパイプや標準入力から受け付ける
    -nl 出力の最後に改行を加えない

    拡張子を出力する

    parentname ファイル名

    -I 入力をパイプや標準入力から受け付ける
    -nl 出力の最後に改行を加えない

    ディレクトリ名のみ出力する

    noextname ファイル名

    -I 入力をパイプや標準入力から受け付ける
    -nl 出力の最後に改行を加えない

    拡張子以外のファイル名を出力する

    raise メッセージ

    メッセージをエラーメッセージに入れて実行を停止する

    hash_new 連想配列名 連想配列名2 ...

    連想配列を作成する

    | hash_new 連想配列名

    パイプ入力から連想配列のデータを受け取って連想配列を作る

    > print key value key2 value2 key3 value3 | split | hash_new a
    > puts $a[key]
    value

    hash_new -I 連想配列名

    標準入力から連想配列のデータを受け取って連想配列を作る

    | hash_add 連想配列名

    > print key value | split | hash_add a

    連想配列にデータを追加する

    hash_erase 連想配列名 キー

    連想配列の要素を削除する

    sweep

    全てのグローバル変数、連想配列、配列を消す

    -reg 正規表現のキャッシュを消す
    -f 関数定義をクリアする
    -g グローバルパイプの内容を消す

    引数に変数名を書くとその変数だけ消す

    sweep A   # A だけ消す

    show

    全てのグローバル変数、連想配列、配列の定義情報を見る

    -v グローバル変数の値も表示する

    printf フォーマット 引数1 引数2 ...
    | printf フォーマット

    -l 改行を最後に出力する

    フォーマットはC言語と同じです。

    > printf "%10s,%10s\n" abcdefg abcdefg
       abcdefg,   abcdefg

    > ls | printf "[%s], [%s], [%s]\n[%s], [%s], [%s]\n"
    [AUTHORS], [saphire_commands.c], [saphire_curses.c]
    [saphire_debug.c], [saphire_extra.c], [saphire_hash.c]

    | sort
    | sort ブロック

    一行ごとのソート。

    ブロックを省略すると { [ $a -slt $b ] }が渡される

    $a 左の一行が入っている
    $b 右の一行が入っている

    > ls | sort
    > ls | sort { [ $a -slt $b ] }

    lsの出力を行ごとに昇順に並び替える

    > ls | sort { [ $a -sgt %b ] }

    lsの出力を行ごとに降順に並び替える

    > print $ARGV | sort { [ $a -slt $b ] } | ary_new ARGV2

    ARGVを昇順にソートしてARGV2に代入

    ++ ローカル変数名

    ローカル変数の値を１増やす

    | ++
    パイプから入力を受け取り1増やして出力する

    -- ローカル変数名
    ローカル変数の値を１減らす

    | --
    パイプから入力を受け取り1減らして出力する

    | + 数字
    パイプから入力を受け取り数字で加算して出力する

    | - 数字
    パイプから入力を受け取り数字で減算して出力する

    | \* 数字
    パイプから入力を受け取り数字で乗算して出力する

    | / 数字
    パイプから入力を受け取り数字で除算して出力する

    | mod 数字
    パイプから入力を受け取り数字で除算して、余りを出力する

    | pow 数字
    パイプから入力を受け取り数字乗して出力する

    range 数字１ 数字２

    数字１から数字２までの範囲の数字を作成

    > range 1 5
    1
    2
    3
    4
    5

    --> seqを使ってください。ただseqをループの中で使うのはかなりパフォーマンスが悪いです。気が向けばrangeをseqクローンに改造します。

    こんなことにも使えます

    > ls | ary_new a; puts $a[$(range 30 50 | chomp | join ,)]

    pcat ブロック ブロック ....

    複数のブロックの出力結果をパイプに流す

    > pcat { echo aaa } { puts bbb } | less
    aaa
    bbb

    | ptee ブロック ブロック ....

    パイプで受け取った入力を複数のブロックに渡す
    受け取ったデータは標準入力に入っています

    >puts aaa | ptee { | cat } { | print } { | sub -g a b }
    aaa
    aaa
    bbb

    ネストも可能

    > puts aaa | ptee { | cat } { | ptee { | print } { | print } } | less
    aaa
    aaa
    aaa

    return 数値

    第一引数を終了コードとして関数から復帰する
    数値を省略すると終了コードが0となる

    each { block }

    毎行に一回ブロックを実行する。STDINパイプにその行が入っている

    >ls | each { | print }
    main.c
    sub.c

    ブロックに引数を持たすと行番号が得られます。

    > ls | each { :i: | print | add -n 0 $i }
    0:main.c
    1:sub.c

    foreach 文字列...文字列 ブロック

    並べられた文字列毎にブロックを実行する。文字列はSTDINパイプに入っている。

    >foreach a b c { | pomch }
    a
    b
    c

    >ary_new A a b c
    >foreach $A< > { | pomch }
    a
    b
    c

    >ary_new A a b c
    >foreach @@A { | pomch }
    a
    b
    c

    | p

    デバッグ用の何もしないフィルタ。
    フィルタの内容を見ることができます。
    データのエンコーディングはUTF8しか対応していません。

    SPACE, RETURNが押されたら次のコマンドにデータを流します。
    ESCAPE, CTRL-C, qが押されたらエラーを出して処理を止めます

    -c カーソル位置 カーソル位置を設定する
    -t スクロールトップ位置 スクロールトップ位置を設定する

saphire.saで定義されているユーザー関数

    times 回数 ブロック

    回数分ブロックを実行します

    ブロックに引数を持たすと回数のデータが変数に入ります。

    > times 3 { :i: puts $i }
    0
    1
    2

    upto 初期値 終了値 ブロック

    > upto 1 5 { :i: puts $i }
    1
    2
    3
    4
    5

    downto 初期値 終了値 ブロック

    >downto 5 3 { :i: puts $i }
    5
    4
    3

    step 初期値 終了値 増分 ブロック

    >step 3 10 3 { :i: puts $i }
    3
    6
    9

    | field [number]

    Nth フィールドを返す

    | field_num

    フィールドの数を返す

    shelp

    使い方を見る

    smake

    makeの実行結果から一行選んで、その行をエディッターで起動する

    sgrep

    grepの実行結果から一行選んで、その行をエディッターで起動する

    case value regex1 block1 regex2 block2...

    >a=1; case $a 0 { puts a } 1 { puts b } 2 { puts c } 3 { puts d }
    b

    for 変数 in 文字列 文字列.... ブロック

    並べられた文字列をひとつずつ変数に入れてブロックを実行する。

    >for a in a b c { puts @a }
    a
    b
    c

    >ary_new A a b c
    >for a in $A< > { puts @a }
    a
    b
    c

    >ary_new A a b c
    >for a in @@A { puts @a }
    a
    b
    c

サンプルスクリプト

    lsの出力の各ファイル名を一文字目だけ大文字にして出力

    ls | each { | var a; uc $a[0]; puts @a[1..-1] }
    ls | each { | rows 0 { | uc } 1..-1 { | pomch } | chomp }

    lsの出力でminatoにマッチして~にマッチしないファイル名とその数を出力
    (grep minato | grep -v \~ | wc -lみたいなもの)

    nr=1; count=0; ls | each { | match -q minato && | ! match -q \~ && (++ count; print $nr\:; | print ); ++ nr }; puts $count

    egrep

    ls | each { | match -q 文字列 && | print }

    姓名の逆転

    cat data | each { | split | if(| length -L | chomp | [ -ge 2 ]) { | lines 1 0 | printf "%s:%s" } else { | printf "%s:nothing\n" } }

    c拡張子のファイルが何個あるか

    ls | each { | split '\.' | lines -1 } | sort | select { | chomp | [ = c ] } | length -L

    ls *.c | wc -l

     add comma to number

     > n=0; print 1234567890 | rows -r | scan . | each { | chomp; | if([ $n = 2 ]) { print ,; n=0 } else { ++ n } } | rows -r | pomch
     1,234,567,890

    > print 1234567890 | rows -r | (scan ... { | pomch }; print $POSTMATCH) | rows -r | join
     1,234,567,890
    > print 1234567890 | rows -r | (scan ... { | pomch }; print $POSTMATCH) | rows -r | join ,
     1,234,567,890
    > print 1234567890 | sub -g '(\d)(?=(?:\d\d\d)+(?!\d))' '\1,'
     1,234,567,890

    print if not match regex

    > ls | each { ! | match -q '^saphire' && | print }

    > times 50 { :i: puts $(($i+1)) } | each { | printf "%02d\n" } | join , | scan '\d+,\d+,\d+,\d+,\d+,\d+,\d+,\d+,\d+,\d+'

    > times 50 { :i: ++ i; puts $i } | each { | printf "%02d\n" } | lines x10+0 -B { | join , }

    1から50までの数値を10個1行で表示2桁にして

パフォーマンス

    圧倒的に悪いです。よくこんなの作りました(絶望
    文字列のパース処理が特に(scan . ブロックでごにょごにょやると我慢できないレベル)。変換やフィールド処理はわりと使えるレベル・・・だと思いたい。結局フィルタとして変数使ってごにょごにょやるにはperl, rubyがベストです(泣

    抜け道として数Mオーバーの文字列処理はsed, awk, perl, rubyを使うしかないです。もしパフォーマンスを改善しようと思えば書き直すしかないと思ってます。

