bashは、cmd.exeと同じくコマンドラインを解釈して実行するコマンドラインインタープリタなのだが、強力なスクリプトインタープリタでもある。cmd.exeもバッチ言語のインタープリタではあるが、bashに比べると少々非力だ。
bashのメリットの1つは、コマンドライン中に引数として変数を入れたり、文字列パターンを展開したりする機能などが入っていることだ。bashがこうしたコマンドラインの展開機能を持っているため、個々のコマンドは、渡された引数をちゃんと処理するプログラムとして動作すればよく、シェルの展開と組み合わせることで高度な機能を実現できる。
Windows OSのコマンドラインでは、ファイルを指定するとき簡単なワイルドカードしか利用できない。それに対してbashでは、正規表現に準じたファイルやディレクトリ名のパターン指定が行える。本連載の最後の解説として、bashのコマンドラインにおける展開機能について解説する。
展開とは、一定の書式で記述されたパターンをルールに従って文字列に変換する機能だ(このため置換と呼ばれることもある)。コマンドラインにbashの変数名を置けば、その内容に置き換えられる。
bashの展開機能には、以下のものがあり、この順番でコマンドラインを展開してから実行を行う。
bashの展開機能 | 意味 |
---|---|
ブレース展開 | ブレース記号によるリストなどの文字列展開 |
チルダ展開 | ユーザー名、ホームディレクトリ名を表すチルダ記号に関連する展開 |
変数パラメーター展開 | 変数名を内容に置き換え、同時に簡単な変数内容の加工を行う |
算術式展開 | 数式を計算して結果に置き換える |
コマンド置換 | コマンドを実行して結果に置き換える |
プロセス置換 | コマンド(プロセス)を実行した結果をファイルとして渡す。あるいはファイル出力をコマンドで加工する |
(単語の分割) | 上記の展開の結果生じたクオートされていないスペースを単語の切れ目として認識する |
パス名展開 | 限定された正規表現を含むパスを、一致する実在するファイル、ディレクトリへのパスに置き換える |
bashの展開機能 |
それぞれ少々面倒だが、ここを理解すれば、bashの多くのメリットを引き出せる。また、これらの展開は、対象となる文字列パターン(パラメーター展開なら変数名)がなければ、何も行われない。展開の名称は、bashの日本語manページに記述を合わせてあるので、検索して該当部分をさらに詳しく調べることもできる。展開全般に関しては、man bashで開いたら検索(スラッシュキー)で「^展開」(行頭にある展開)を検索させるといいだろう。
bashの展開機能のうちで最も強力なものがブレース展開だ。ブレースとは、波括弧のことである。この展開は、波括弧で括った部分を解釈して複数の単語(スペースで区切られた文字列)に変換する機能だ。“{1..10}”とすれば、スペースで区切られた1〜10までの数字に変換される(これをシーケンス式と呼ぶ)。このとき、ブレースの前後に付いた文字列もコピーして展開される。“cat Test/file{1..3}”とすれば、ブレース展開によって以下のコマンドラインが実行される。
cat Test/file1 Test/file2 Test/file3
ファイル名の末尾が数字のファイルならば、まとめて指定が可能だ。さらに便利なのは“{00..09}”などとすると(数字の前に0を付けると)、00、01、02……というパターンに変換される部分だ。あるいは“{a..z}”といった指定も可能だ。ブレース展開は単語をカンマで区切って指定することもできる。“Test2/file-{ok,bad,unknown}”と指定すれば、3つのファイルパスを生成できる。
Linuxでは、一般的にユーザーのホームディレクトリは、「/home」以下にユーザー名を使って作られる。ユーザー名が「shioda」ならば、ホームディレクトリは「/home/shioda」となる。
ホームディレクトリはユーザーが自由に利用できるディレクトリなので、ここにユーザーのファイルを置いたり、そのためのディレクトリを作ったりすることが多い。このとき、自身のホームディレクトリはbashのコマンドラインでは“~/”と表現でき、「/home/shioda/xyz」ならば“~/xyz”と表現できる。
これは、自身のホームディレクトリに対する略記として利用できるほか、他のユーザー(yoshida)なら“~yoshida/abc”などと表記できる。こうしたチルダに続く文字列をユーザーディレクトリに変換するのがチルダ展開である。チルダ展開にはその他の機能もあるが、これについてはbashのmanページを参照してほしい。
bashでは、変数を利用することができる。変数への代入は、コマンドラインで普通に代入文を記述すればよい。「teststring」という変数に「cat dog」という文字列を代入するなら“teststring="cat dog"”とすればよい。これで「teststring」に文字列が記憶されるが、これを呼び出したいときには、コマンドライン中に“${変数名}”と記述する。
ただし多くの場合、コマンドライン中の前後の文字列との組合せで間違う可能性がなければ、波括弧を省略して“$変数名”と記述できる。おそらく、この方が多く見かける表記だろう。なお、代入部分の後にセミコロンを置いてこの後に直接コマンドを書いてもよい。
また、パラメーター展開では、サブコマンドがあり、変数の内容に対して簡単な処理が行える。“${変数名:4:3}”と記述すると変数の内容の4文字目から3文字を取り出した文字列に変換が行われる(BASIC言語のMID$文字列関数と同じ)。似たような機能は、cmd.exeにもあるが、bashの方が高機能である。
「$((」と「))」で挟んだ式を計算して値に置き換えるのが算術式展開だ。この中でシェル変数への代入や変数の参照も行える。また、変数自体を変更する「++」「--」といった演算子も利用可能だ。演算子に関しては、manページの「算術式評価」に記載がある。注意するのはbashが扱う数値は整数である点だ。
逆クオート「`」または「$(」と「)」で囲まれた部分は、コマンドとして実行され、その結果に置き換えられる。逆クオートは古い形式だが、UNIX初期からあるshの表記方法であるため、まだ、古い形式を使うことも多い。
ただし、逆クオート内部の「\(逆スラッシュ)」の扱いに特殊な部分があり、注意する必要がある。内部にさらに逆クオート形式でコマンド置換を入れ子にするなら、逆クオートでエスケープさせる必要がある。慣れていないなら、新しい形式である“$(コマンド)”を使う方がいいかもしれない。
プロセス置換とは、コマンドの実行結果をファイルとしてコマンドの引数に指定できるようにするもの(逆にファイルへの出力をコマンドの入力にできる)。標準入出力によるパイプは、1つしかないため、1つのコマンドの出力だけしか渡すことができない。しかし、ファイルを引数として複数受け付けるコマンドもある。
ファイルの相違を調べるdiffコマンドは、2つのファイルを引数に取る。2つのコマンドの出力をdiffで比較させるような場合にプロセス置換を使う。
$ diff -y <(echo -e {1..10}"\n") <(echo -e {5..15}"\n")
このようにすることで、2つのコマンドの実行結果をファイルとしてdiffコマンドに渡すことができる。プロセス置換を使わないならそれぞれ、一時ファイルを作ってdiffコマンドの引数としなければならない。
以上の展開、置換が終了したあと、bashは、生成されたコマンドラインを再度解釈して単語を分離する。単語として分離された文字列は、それぞれが1つのパラメーターとなるため、ブレース展開の結果などは、元が1つであっても複数の引数として認識されるようになる。
ただし、単語の分割は、展開が1つでも行われたときに実行され、展開が何も実行されない(結果が変わらない)場合には単語への分割は発生しない。展開の結果、ダブルクオート「"」やシングルクオート「'」で囲まれていないスペースができると、そこが単語の切れ目として認識され、以後の処理で個別の単語として認識されるようになる。
パス名展開は、bashコマンドラインの展開の大きなポイントだ。なぜなら多くの場合、コマンドラインではファイルを引数として指定するためファイルやディレクトリへのパスの記述が大半を占めるからだ。イメージ的には、パス名展開は、cmd.exeのワイルドカードの強化版と思ってもいい。
まず、パス名展開は、これまで説明してきた展開が終了し、単語に分割された後に処理される。このとき、コマンドラインに「*」「?」「[」という正規表現の特殊文字が含まれているなら、正規表現に準拠したファイルの検索が行われ、実在のファイルやディレクトリへのパスと一致したパスのみが選択される。パス名展開の「*」「?」は、正規表現でいえば“[^/]*”、“[^/]?”のように、スラッシュとは一致しない繰り返しに相当する。
またこのパターンは、実在しないファイルへのパスには一致しない。サブディレクトリ「Test」に「file1」…… 「file5」が存在したとき、パス“*/*”は、サブディレクトリ内に存在するファイルにのみマッチする。
このとき、空のディレクトリ「Test2」があったとしても、“*/*”とは一致しない。なぜなら最初の「*」は「Test2」に一致するが、ファイルが何もなければ、2つ目の「*」に一致するものがないからである。つまり、“*/*”は、サブディレクトリに存在しているファイルに対してのみ一致する。
また、カレントディレクトリに幾つファイルがあっても、“*/*”はそれらのファイルには一致しない(サブディレクトリの中にあるファイルやディレクトリのみにマッチする)。パス名展開は、このように実在しているファイルやディレクトリのみに展開される。このため、安心してコマンドラインのファイル指定パスとして利用することができる。
パス名展開は、限定された正規表現であり、一致するのは必ず存在するファイルやディレクトリとなる。なお、パスの最後をスラッシュとすることでディレクトリ自体を指定できる。パス名展開では、メタキャラクタの「*」と「?」、そして文字クラスである「[」「]」が利用できる。
こうして全ての展開が終了した後にコマンドが実行される。なお、展開の結果が心配なら、今回の例で使ったようにechoコマンドなどの無害なコマンドの引数として指定してみることで、結果を調べることができる。
ざっと駆け足でWSLの基本部分を解説した。個々のコマンドについては、manで調べればよく、「Linuxで××したい」といった逆引き的な検索は、インターネット検索で十分なんとかなる。取りあえず便利なbashに慣れ、ちょっとしたファイル操作などは、WSL側から行えるようになると、さまざまなコマンドを使いながらbashやLinuxに慣れていくことができる。
まずは、コマンドラインでいろいろと試してみるところが重要である。このあたり、すぐに参考書などを読んでもなかなか頭に入らない。書籍などは、bashのコマンドラインに慣れ、いろいろやってみたいと思い始めた頃に読んだ方が頭に入りやすい。
Windows 10が動いていればWSLは無料ですぐに利用できる。ある意味、Linuxのディストリビューションをダウンロードしてインストールするよりも簡単だ。取りあえず手を動かしてみるところから始めてみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.