Description: Yash がサポートする単語展開について

# 単語の展開

コマンドを構成する各単語は、そのコマンドが実行されるときに展開されます。展開とは単語に含まれるパラメータやパターンを処理して具体的な文字列値に置き換えることです。展開には以下の七種類があります。

1. チルダ展開

2. パラメータ展開

3. コマンド置換

4. 数式展開

5. ブレース展開

6. 単語分割

7. パス名展開

これらの展開は上に挙げた順序で行われます。特に最初の四つ (チルダ展開・パラメータ展開・コマンド置換・数式展開) を四種展開といいます。

## チルダ展開

チルダ展開は、`~` で始まる単語を特定のパス名に置き換える展開です。単語の先頭にある `~` から最初の `/` まで (`/` がない場合は単語全体) が指定されたパス名に変換されます。ただし、置き換えられる部分が一文字でも​クォートされている場合は、チルダ展開は行われません。

展開される内容は、置き換えられる部分の書式によって以下のように決まります。

`~`

  単なる `~` は、​`HOME` 変数の値に置き換えられます。

`~ユーザ名`

  `~` の後にユーザ名が書かれている場合は、そのユーザのホームディレクトリのパス名に置き換えられます。

`~+`

  `~+` は、​`PWD` 変数の値に置き換えられます。

`~-`

  `~-` は、​`OLDPWD` 変数の値に置き換えられます。

`~+n`
`~-n`

  この n は 0 以上の整数でなければなりません。この形式のチルダ展開は、+n または -n で指定されるディレクトリスタック内のパスの一つに展開されます。(dirs 組込みコマンド参照)

変数代入の値に対してチルダ展開が行われる際、値がコロンで区切ってある場合は、コロンで区切ってある各部分をそれぞれ単語とみなしてチルダ展開します。例えば `HOME` 変数の値が `/home/foo` のとき、

    VAR=~/a:~/b:~/c

は

    VAR=/home/foo/a:/home/foo/b:/home/foo/c

と等価です。

チルダ展開に失敗した場合 (指定されたパス名が何らかの原因で得られなかった場合) の動作は POSIX では規定されていませんが、yash では何事もなかったかのように処理を続行します (置き換えられるはずだった部分はそのまま残され、エラーメッセージなどは出ません)。

POSIX 準拠モードでは `~` と `~ユーザ名` の形式の展開のみが有効です。

## パラメータ展開

パラメータ展開は、単語の一部をパラメータの値に置き換える展開です。

よく使われる単純なパラメータ展開の形式は `${パラメータ名}` です。これはパラメータ名で指定されたパラメータの値に展開されます。さらに、以下の場合にはパラメータ名を囲む括弧を省略して `$パラメータ名` のように書くこともできます。

- パラメータ名が​特殊パラメータの場合

- パラメータ名が一桁の​位置パラメータの場合

- パラメータ名が変数名で、直後に変数名の一部として誤解される恐れのある文字がない場合。例えば `${path}-name` という単語は `$path-name` と書くこともできますが、 `${path}name` を `$pathname` と書くことはできません。

パラメータ名として特殊パラメータでも位置パラメータでも変数名でもないものを指定した場合は、構文エラーになります。(Yash 以外のシェルでは構文エラーではなく展開エラーになるものもあります)

シェルの unset オプションが無効な場合、パラメータ名に存在しない変数を指定すると展開エラーになります。Unset オプションが有効な場合は、存在しない変数は空文字列に展開されます。

より複雑なパラメータ展開の形式では、パラメータの値を加工することができます。パラメータ展開の一般形は以下の通りです。

パラメータ展開

  `${ 前置詞 パラメータ名 インデックス 加工指定 }`

ここでは便宜上パラメータ名やインデックスの周りに空白を入れましたが、実際には空白を入れてはいけません。パラメータ名以外の部分はいずれも省略可能です。

### 前置詞

前置詞としてパラメータ名の直前に記号 `#` を置くことができます。この場合、このパラメータ展開はいま展開しようとしている値の文字数を表す整数に展開されます。展開しようとしているのが配列変数の場合、各要素がそれぞれ文字数を表す整数に置き換えられます。

### パラメータ名

パラメータ名には、​特殊パラメータ・​位置パラメータ・変数を指定することができます。この場合、パラメータ展開は指定されたパラメータの値に展開されます。指定したパラメータ名が​配列変数の場合、配列の各要素が特殊パラメータ `@` の場合と同様に単語分割されます (インデックス `[*]` が指定された場合を除く)。

パラメータ名としてパラメータ展開・コマンド置換・数式展開を指定することもできます。これは特に展開の入れ子と言います。この場合、パラメータ展開は内側の展開の展開結果に展開されます。なお、内側のパラメータ展開の括弧 `{ }` は省略できません。また展開の入れ子は POSIX 準拠モードでは使えません。

### インデックス

インデックスは展開する値の一部を抜き出すのに使います。インデックスは以下の書式をしています。

インデックス

  `[単語1]`

  `[単語1,単語2]`

ここでの単語1および単語2は通常の​トークンと同様に解釈されますが、`,` と `]` で強制的に区切られます。また空白やタブはトークンの区切りとはみなしません。

インデックスは、以下のように解釈されます。

1. まず、インデックスに含まれる単語1・単語2に対してパラメータ展開・コマンド置換・数式展開を行います。

2. インデックスが `[単語1]` の書式をしていて、単語1の上記展開結果が `*`、`@`、`#` のいずれかの場合は、インデックスの解釈は終了です。

3. 単語1と単語2の上記展開結果を数式とみなして、数式展開と同様に計算します。計算の結果得られる整数がインデックスとなります。数式展開の結果が整数でない場合は展開エラーです。単語2がない形式でインデックスを指定している場合は、単語2は単語1と同じ整数を指定しているものとみなされます。

パラメータ名が​配列変数の場合または特殊パラメータ `*` または `@` の場合、インデックスは配列の要素または位置パラメータの一部を指定しているものとみなされます。パラメータ名が上記以外の場合は、パラメータの値の一部を指定しているものとみなされます。インデックスで選択された配列の要素またはパラメータの値の一部のみが、パラメータ展開の結果として展開結果に残ります。インデックスによる選択について以下の規則が適用されます。

- インデックスの整数が負数のときは、要素または文字を最後から数えるものとみなされます。例えばインデックス `[-2,-1]` は配列の最後の二つの要素 (またはパラメータの値の最後の 2 文字) を選択します。

- インデックスの整数が存在しない要素または文字を指示している場合でも、エラーにはなりません。例えば配列の要素数が 4 のときにインデックス `[3,5]` が与えられたときは 3 番目以降の全ての要素が選択され、インデックス `[5,7]` が与えられた時はどの要素も選択されません。

- インデックスの整数の一方が 0 ならば、(もう一方が何であれ) 展開結果には何も残りません。

インデックスが `[単語1]` の書式をしていて、単語1の展開結果が `*`、`@`、`#` のいずれかだった場合は、パラメータは以下のように処理されます。

`*`

  パラメータ名が配列変数の場合、​特殊パラメータ `*` と同様に配列の全要素を​単語分割または連結します。パラメータ名が特殊パラメータ `*` または `@` の場合、同様に位置パラメータを単語分割・連結します。それ以外の場合はインデックス `[1,-1]` と同様です。

`@`

  インデックス `[1,-1]` と同様です。

`#`

  パラメータ名が配列変数の場合、このパラメータ展開は配列の要素数を表す整数に展開されます。パラメータ名が特殊パラメータ `*` または `@` の場合、このパラメータ展開は位置パラメータの個数を表す整数に展開されます。それ以外の場合、このパラメータ展開はいま展開しようとしている値の文字数を表す整数に展開されます。

パラメータ展開にインデックスが指定されていない場合は、インデックスとして `[@]` が指定されたものとみなされます。インデックスは​POSIX 準拠モードでは一切使えません。

例 1. 通常の変数の展開

以下のコマンドは文字列 `ABC` を出力します:

    var='123ABC789'
    echo "${var[4,6]}"

例 2. 位置パラメータの展開

以下のコマンドは文字列 `2 3 4` を出力します:

    set 1 2 3 4 5
    echo "${*[2,-2]}"

例 3. 配列の展開

以下のコマンドは文字列 `2 3 4` を出力します:

    array=(1 2 3 4 5)
    echo "${array[2,-2]}"

### 加工指定

加工指定はパラメータの値を加工します。加工された後の値がパラメータ展開の結果として展開されます。加工指定には以下の形式があります。

`-単語`

  パラメータ名が存在しない変数を指示している場合は、このパラメータ展開は単語に展開されます。(Unset オプションが無効な時でもエラーになりません)

`+単語`

  パラメータ名が存在する変数を指示している場合は、このパラメータ展開は単語に展開されます。(Unset オプションが無効な時でもエラーになりません)

`=単語`

  パラメータ名が存在しない変数を指示している場合は、単語の展開結果がその変数に代入された後、このパラメータ展開はその値に展開されます。変数以外のものに対して代入しようとすると展開エラーになります。(Unset オプションが無効な時でもエラーになりません)

`?単語`

  パラメータ名が存在しない変数を指示している場合は、エラーメッセージとして単語を標準エラーに出力します。(単語がない場合はデフォルトのエラーメッセージが出ます)

`:-単語`
`:+単語`
`:=単語`
`:?単語`

  これらは上記の `-`、`+`、`=`、`?` と単語の組み合わせの加工指定と同様ですが、単語を使用する条件が異なります。先頭に `:` が付かないものでは 『変数が存在するかどうか』 で判定されますが、`:` が付くものでは 『変数が存在し、その値が空文字列でないかどうか』 で判定されます。

`#単語`

  単語を​パターンとして見たとき、それがいま展開しようとしている値の先頭部分にマッチするならば、そのマッチする部分を削除します。結果として、このパラメータ展開はマッチしなかった残りの部分に展開されます。マッチの仕方が複数通りある場合はできるだけ短くマッチさせます。

`##単語`

  この加工指定は `#単語` と同様ですが、マッチの仕方が複数通りある場合はできるだけ長くマッチさせる点が異なります。

`%単語`

  この加工指定は `#単語` と同様ですが、値の先頭部分ではなく末尾部分にマッチさせる点が異なります。

`%%単語`

  この加工指定は `%単語` と同様ですが、マッチの仕方が複数通りある場合はできるだけ長くマッチさせる点が異なります。

`/単語1/単語2`

  単語1をパターンとして見たとき、それがいま展開しようとしている値の一部にマッチするならば、そのマッチする部分を単語2に置き換えます。結果として、このパラメータ展開はマッチした部分を単語2に置き換えた値に展開されます。マッチする箇所が複数ある場合は、最初の箇所が選ばれます。マッチの仕方が複数通りある場合はできるだけ長くマッチさせます。

  この加工指定は POSIX 準拠モードでは使えません。

`/#単語1/単語2`

  この加工指定は `/単語1/単語2` と同様ですが、いま展開しようとしている値の先頭部分にしかマッチしない点が異なります。

`/%単語1/単語2`

  この加工指定は `/単語1/単語2` と同様ですが、いま展開しようとしている値の末尾部分にしかマッチしない点が異なります。

`//単語1/単語2`

  この加工指定は `/単語1/単語2` と同様ですが、マッチする箇所が複数ある場合は最初の箇所だけではなく全ての箇所を単語2に置き換える点が異なります。

`:/単語1/単語2`

  この加工指定は `/単語1/単語2` と同様ですが、いま展開しようとしている値全体にマッチする場合しか対象としない点が異なります。

いずれの形式においても、加工指定に含まれる単語は (それが使用されるときのみ) 四種展開されます。

展開しようとしているパラメータ名が配列変数または特殊パラメータ ​`@` または `*` の場合、加工指定は配列の各要素または各位置パラメータに対してそれぞれ作用します。

## コマンド置換

コマンド置換は、指定されたコマンドを実行してその出力をコマンドラインに展開します。コマンド置換の書式は以下の通りです。

コマンド置換

  `$(コマンド)`

  ``コマンド``

コマンド置換では、コマンドが​サブシェルで実行されます。このときコマンドの標準出力がパイプを通じてシェルに送られます。結果として、コマンド置換はコマンドの出力結果に置き換えられます。ただし、コマンドの出力の末尾にある改行は除きます。

`$(` と `)` で囲んだコマンド置換のコマンドは、コマンド置換の入れ子やリダイレクトなどを考慮して予め解析されます。従って、`$(` と `)` の間には基本的に通常通りコマンドを書くことができます。ただし、数式展開との混同を避けるため、中のコマンドが `(` で始まる場合はコマンドの最初に空白を挿し挟んでください。

``` で囲むコマンド置換では、コマンド置換の入れ子などは考慮せずに、コマンドの中に最初に (バックスラッシュで​クォートしていない) ``` が現れたところでコマンド置換の終わりとみなされます。``` で囲んだコマンド置換の中に ``` で囲んだコマンド置換を書く場合は、内側の ``` をバックスラッシュでクォートする必要があります。その他、コマンドの一部として ``` を入れたいときは、(それがコマンド内部で一重または二重引用符でクォートされていても) バックスラッシュでクォートする必要があります。コマンドの中ではバックスラッシュは `$`・```・バックスラッシュ・改行の直前にある場合のみ引用符として扱われます。また、``` で囲んだコマンド置換が二重引用符の中で使われる場合は、コマンドの中に現れる二重引用符もバックスラッシュでクォートする必要があります。これらのバックスラッシュはコマンドが解析される前に削除されます。

`$(` と `)` で囲んだコマンド置換の中のコマンドは、そのコマンド置換を含むコマンドを解析する時に一緒に解析されます (POSIX 準拠モードを除く)。``` で囲んだコマンド置換の中のコマンドは、POSIX 準拠モードであるかどうかに関わらず、そのコマンド置換が実行される時に毎回解析されます。

## 数式展開

数式展開は、文字列を数式として解釈して、その計算結果を表す数値に展開します。数式展開の書式は以下の通りです。

数式展開

  `$((式))`

数式展開では、まず式に対してパラメータ展開・コマンド置換・(入れ子の) 数式展開が行われます。その結果得られた文字列を以下のように数式として解釈し、その計算結果を表す数値に展開されます。

Yash では、数式の中で整数 (C 言語の long 型) と浮動小数点数 (C 言語の double 型) を扱うことができます。ただし POSIX 準拠モードでは浮動小数点数は使えません。整数同士の演算の結果は整数に、浮動小数点数を含む演算の結果は浮動小数点数になります。

数式では C 言語と (ほぼ) 同様に以下の演算子が使えます。

1. `( )`

2. `++` `--` (後置演算子)

3. `++` `--` `+` `-` `~` `!` (前置演算子)

4. `*` `/` `%`

5. `+` `-` (二項演算子)

6. `<<` `>>`

7. `<` `<=` `>` `>=`

8. `==` `!=`

9. `&`

10. `^`

11. `|`

12. `&&`

13. `||`

14. `? :` (三項演算子)

15. `=` `*=` `/=` `%=` `+=` `-=` `<<=` `>>=` `&=` `^=` `|=`

`++` および `--` 演算子は POSIX 準拠モードでは使えません。

原子式としては整数リテラル・浮動小数点数リテラル・変数が使用できます。数リテラルの書式は C 言語に準じます。`0` で始まる整数リテラルは八進数、`0x` で始まる整数リテラルは十六進数とみなされます。浮動小数点数リテラルでは指数表記も使えます (例えば 1.23×106 は `1.23e+6`)。変数の値が計算で使われるとき、その値が数値でない場合はエラーになります。Unset オプションが有効ならば未定義の変数は 0 とみなします。

POSIX 準拠モードでは、変数は必ず数値として解釈されます。 POSIX 準拠モードでないときは、計算で使われる変数のみが数値として解釈され、他の変数はそのまま残ります。

    set +o posixly-correct
    foo=bar
    echo $((0 ? foo : foo)) # 「bar」を出力
    echo $((foo + 0))       # エラー

C 言語で結果が定義されていない数式は展開エラーになります。

## ブレース展開

ブレース展開は、ブレース (`{ }`) で囲んだ部分をいくつかの単語に分割します。ブレース展開は brace-expand オプションが有効な時のみ行われます。ブレース展開には二種類の形式があります。

カンマ区切りのブレース展開

  `{単語1,単語2,…,単語n}`

連続した数値のブレース展開

  `{始点..終点}`

  `{始点..終点..差分}`

一つ目の形式は、ブレースで囲んだ部分を一つ以上のカンマ (`,`) で区切ったものです。区切られたそれぞれの部分がブレース展開の前後の部分と結合されて、それぞれ単語として展開されます。例えば `a{1,2,3}b` は `a1b`、`a2b`、`a3b` という三つの単語に展開されます。

二つ目の形式は `{始点..終点}` または `{始点..終点..差分}` です。始点・終点・差分は全て整数である必要があります。この形式のブレース展開では、始点から終点までの各整数がブレース展開の前後の部分と結合されて、それぞれ単語として展開されます。差分は整数の間隔を指定します。例えば `a{1..3}b` は `a1b`、`a2b`、`a3b` という三つの単語に展開され、`a{1..7..2}b` は `a1b`、`a3b`、`a5b`、`a7b` という四つの単語に展開されます。始点が終点より大きい場合は整数は降順に展開されます。

複数のブレース展開を組み合わせたり、入れ子にしたりすることもできます。ブレースをブレース展開としてでなく通常の文字として扱うには、ブレースをクォートしてください。またカンマを区切りとしてでなく通常の文字として扱うには、カンマをクォートしてください。

ブレース展開では展開エラーは発生しません。ブレース展開が正しくできない場合は、単にそれはブレース展開ではなかったものとして、そのまま残されます。

## 単語分割

単語分割は、展開の結果をいくつかの単語に分割します。

単語分割で分割の対象となるのは、パラメータ展開・コマンド置換・数式展開で展開された結果の部分だけです。また、二重引用符による​クォートの中で展開された部分は、(特殊パラメータ `@` の展開を除いて) 分割の対象となりません。

単語分割は `IFS` 変数の値に従って行われます。`IFS` 変数が存在しない場合は、空白文字・タブ・改行の三文字が `IFS` 変数の値として使われます。

`IFS` 変数の値に含まれている文字を IFS 文字といいます。IFS 文字のうち空白文字またはタブまたは改行であるものを IFS 空白類といいます。IFS 空白類以外の IFS 文字を IFS 非空白類といいます。

分割は以下の規則に従って行われます。

1. 分割は、分割の対象となる展開結果の部分の中で、IFS 文字が現れる箇所で行われます。以下このような箇所を分割点と呼びます。複数の IFS 文字が連続して現れる場合は、それらをまとめて一つの分割点とします。

2. 分割点に IFS 非空白類が含まれている場合、その分割点に含まれる IFS 空白類はすべて無視されます。そして分割点に含まれる各 IFS 非空白類の前後で単語が分割されます。

3. 分割点に IFS 非空白類が含まれていない (分割点が IFS 空白類だけからなる) 場合、その分割点の前後で単語が分割されます。ただし、このような分割点が元の単語の先頭または末尾にある場合を除きます。

4. いずれの場合も、分割点は単語分割後の単語には残りません。

最後に、以下の条件がすべて成り立つならば、分割された最後の単語が結果から削除されます。

- empty-last-field オプションが無効

- 分割結果が複数の単語である

- 最後の単語が空文字列である

注 | `IFS` 変数の値が空文字列の場合は、単語は一切分割されません。

## パス名展開

パス名展開は、単語を​パターンとみなしてファイルを検索し、パターンにマッチする実在のファイルへのパス名に展開します。 パス名展開は glob オプションが無効な時は行われません。

パス名展開においてパターンがマッチするには、検索の対象となるディレクトリの読み込み権限が必要です。検索しようとしたディレクトリがシェルにとって読み込み可能でなければ、シェルはそのディレクトリは空であるとみなします。

以下のオプションがパス名展開の結果に影響します。

null-glob

  マッチするファイルがない時、通常 (このオプションが無効な時) はパターンはそのまま残りますが、このオプションが有効な時はパターンは削除され何も残りません。

case-glob

  通常 (このオプションが有効な時) は、大文字と小文字を区別してマッチングを行いますが、このオプションが無効な時は大文字と小文字を区別せずマッチングを行います。

dot-glob

  通常 (このオプションが無効な時) は、`*` や `?` などのワイルドカードやブラケット記法で始まるパターンはピリオドで始まるファイル名にマッチしません。しかしこのオプションが有効な時はこのような制約は解除されます。

mark-dirs

  このオプションを有効にすると、マッチしたファイルの種類がディレクトリの場合に展開されるパス名の最後に `/` が付きます。

extended-glob

  このオプションを有効にすると、パス名展開における拡張機能が使えるようになります。

パス名展開ではエラーは発生しません。マッチするファイルがない場合またはパターンが不正な場合は、展開は行われずパターンはそのまま残ります (null-glob オプションが有効な時を除く)。

ファイルの検索とパターンマッチングは `/` で区切られたパス名の構成要素ごとに行われます。ワイルドカードやブラケット記法を全く含まない構成要素はパターンとはみなされず、検索とマッチングは行われません。従って、case-glob オプションが無効な時、`/*/foo` と `/*/fo[o]` の展開結果が異なる可能性があります (前者では `foo` の部分がパターンとはみなされないので、例えば /bar/FOO というファイルがあってもマッチしません。)。

### パス名展開の拡張機能

Extended-glob オプションが有効な時は、以下の特殊なパターンが使えるようになります。

`**`

  指定されたディレクトリツリーに対し再帰的に検索を行います。すなわち、指定されたディレクトリと、そのサブディレクトリ、さらにそのサブディレクトリなどに対し検索を行います。ただし名前がピリオドで始まるディレクトリは検索の対象になりません。例えば `dir/**/file` というパターンは、dir/file や dir/foo/file や dir/a/b/c/file など、dir ディレクトリの中にある全ての file ファイルへのパスに展開されます。

  この特殊なパターンは、 `foo/bar/**` のようにパターン全体の最後にある場合には効果がありません。

`.**`

  `**` パターンと同様ですが、名前がピリオドで始まるディレクトリも含めて検索する点が異なります。

`***`

  `**` パターンと同様ですが、検索の中でディレクトリへのシンボリックリンクが見つかった場合、そのディレクトリの中も検索の対象に含める点が異なります。

`.***`

  `***` パターンと同様ですが、名前がピリオドで始まるディレクトリも含めて検索する点が異なります。