シェル制御構造と正規表現の基礎・UNIXテクニック集



まずはおさらい、シェル制御構造と正規表現の基礎

この記事は2008年~2009年頃に翔泳社で連載していた「業務で楽するためのUNIXテクニック集」の原本です。

はじめに

 この連載では、UNIX系OSをコマンドライン上から効率よく利用するために知っておくべきテクニックや、便利な小技を紹介していきます。

 今回は第0回ということで、今後便利なUNIXコマンドを紹介するにあたり、知っておきたい「シェルの制御構造」や「正規表現」などを、例を交えながら簡単に復習していきます。

スポンサードリンク

関連記事

対象読者

動作確認環境

 なるべくUNIX系OS一般に当てはまるよう解説するので、特に記述がない場合は、Mac OS X、Red Hat Linux上でも動作します。

 シェルに関しては、bash(Bourne-Again Shell 2および3)を想定しています。

いろいろな実行方法

パイプライン

 コマンドが左から右に順に実行され、前のコマンドの出力(標準出力)が次のコマンドに入力(標準入力)されます。

コマンド1 | コマンド2 | ...

コマンドリスト

 コマンドが左から右に順に実行されます。「;」(セミコロン)で区切ることで、1行に複数のコマンドを書くことができます。

コマンド1 ; コマンド2 ; ...

 次のように、コマンドを「&&」で区切って書くと、前のコマンドが成功した場合(終了コードが0の場合)のみ、後ろのコマンドが実行されます。

コマンド1 && コマンド2

 コマンド1が正常終了以外(終了コードが0以外)だったら、コマンド2を実行されます。

コマンド1 || コマンド2

 次の例では、一般的なコンパイル手順である「./configure」「make」「make install」を順番に実行します。途中でエラーが発生した場合、それ以降のコマンドは実行されません。このため不要なコンパイル時間の短縮になります。

$ ./configure && make && make install

コマンド置換

 「$()」で囲まれた部分は、そのコマンドの実行結果に置き換えられます。例えば、現在の日時のファイルを新規作成するには、touchコマンドを使用して次のように記述できます。

$ touch $(date +%F)
$ ls
2008-09-05

 dateコマンドは、現在の日時を表示します。また「+%F」オプションを付けることで、「yyyy-mm-dd」形式の日時を表示しています。

※注1
 コマンド置換は「`...`」(バッククォート)を使って、次のようにも記述できます。

$ touch `date +%F`

 ただし、ネストしやすい・読みやすいという理由から、bashでは「$()」を使うことが多いようです。

ブレース展開

 「,」カンマで区切った複数の文字列を「{}」ブレース(中括弧)で囲む事で、囲んだ文字列を展開することができます。

 複数のディレクトリを一度に新規作成するには、mkdirコマンドを使用して次のように記述できます。

$ mkdir {dir1,dir2,dir3}
$ ls
dir1/  dir2/  dir3/

 また、次のようにディレクトリ名に対して、部分的に中括弧を利用することもできます。

$ mkdir dir{1,2,3}
$ lsdir1/  dir2/  dir3/

このように、異なる文字列を複数個指定する場合に役立ちます。

シェルの変数展開

 変数から値を取り出すときに使われるのが変数展開です。「$変数名」または「${ 変数名 }」のように使います。

 代表的な4種類を紹介します。

変数展開 説明
${パラメータ#パターン} パラメータ前方から、パターンに最短一致した部分を除く
${パラメータ##パターン} パラメータ前方から、パターンに最長一致した部分を除く
${パラメータ%パターン} パラメータ後方から、パターンに最短一致した部分を除く
${パラメータ%%パターン} パラメータ後方から、パターンに最長一致した部分を除く

 次のように、拡張子の除去やファイル名の取得などで、よく使われます。

$ mypath=/home/hoge/foo.txt
$ echo ${mypath%.*}    → 拡張子を除く
/home/hoge/foo
$ echo ${mypath##/*/}  → ファイル名のみを得るfoo.txt

理解しておくと便利なシェルの制御構造

 bashは単純なコマンドだけではなく制御文も受けつけます。制御構造を使うと定型的な処理を行うのが楽になります。

コマンドの繰り返し

 シェルで繰り返しを行うにはfor文、while文、until文などがあります。ここでは、コマンドライン上でよく利用されるfor文、while文を紹介します。

for文

 for文ではリストから項目を1つずつ取り出します。取り出した値は、指定した一時変数に格納され、処理内容が実行されます。処理内容はdoとdoneの間に記述します。

$ for 変数 in リストdo 繰り返し実行される処理内容 done

 コマンドライン上では次のように使用します。

$ for i in 1 2 3 4 5  [改行]
> do                  [改行]
> echo "$i"           [改行]
> done                [改行]
1
2
3
4
5

 上記の「>」は2次プロンプトと呼ばれるもので、コマンドがまだ完結していないときにシェルが表示します。
また、次のように1行で書くこともできます。

$ for i in 1 2 3 4 5; do echo "$i"; done
※注2
 csh・tcshでは、複数行の構造を1行にまとめることはできません。
 また、bash 3以降では、数字の範囲を次のように指定することも可能です。

$ for i in {1..5}; do echo "$i"; done

 なお for文では、次のように条件の設定を指定することもできます。

$ for((i=0; i<5; i++)); do echo "$i"; done

 算術式は必ず二重括弧(())を使ってください。

 さらに、doとdoneは中括弧("{"と"}")で置き換えることができるため、次のようにC言語のような形式で指定することも可能です。

for((i=1; i<5; i++)){ echo "$i"; }

 次の例ではfor文を利用して、すべてのファイル名に拡張子「.bak」を付けています。

$ for file in *; { mv -i "$file" "$file.bak"; }

 なお、スペースなどを含むファイル名を正しく処理させるために、変数は「""」で囲むようにしてください。

while文

 while文は繰返し処理の制御を行います。構文は次のようにwhileに続けて条件を記述します。処理内容はdoとdoneの間に記述します。

while 条件do   繰り返し実行されるコマンドdone

 次の例は、すべてのファイル中の行数を表示します。while文を利用すると次のように記述できます。

$ ls *  ← 全ファイル・ディレクトリを一覧表示
sec1.txt        sec2.txt        sec3.txt
$ find ./ -type f | while read file; do wc -l "$file"; done
       3 ./sec1.txt
       4 ./sec2.txt
       5 ./sec3.txt

 findコマンドの出力結果を、while readでfileという変数に取り込みます。

※注3
 古いバージョンのfindコマンドを利用している方は、
「-print」オプションを追加しないと正しく動作しません。

条件分岐

 シェルの条件分岐にはcase文とif文があります。ここではif文を取り上げます

if文

 if文は、コマンドの終了コードに基づき、行わせる処理が異なる場合に使用します。

 構文は次のように、コマンド1の実行結果が成立(終了コードが0の場合)なら「コマンド1が成立した時に実行するコマンド」を実行します。「elif コマンド2...」は省略することも複数記述することもできます。「else ...」は省略可能です。

if コマンド1
then    コマンド1が成立(終了コードが0の場合)した時に実行するコマンド
[elif コマンド2
 then    コマンド1が不成立で、コマンド2が成立した時に実行するコマンド]
[else   コマンド1、コマンド2が不成立の時に実行するコマンド]
fi

 次の例ではif文を利用して、ディレクトリの中に存在するディレクトリとファイルの数を表示しています。

$ ls *   ← 全ファイル・ディレクトリを一覧表示
sec1.txt
dir:
dir2/
            sec1.txt        sec2.txt        sec3.txt
$ for dir in *; { if [ -d "$dir" ] ; then echo "$dir:"; ls "$dir" | wc -l; fi; }
dir:       4

 「-d "$dir"」の-dオプションは、引数$dirがディレクトリであるかどうかを調べます。ディレクトリの場合は成功します(0を返します)。リストを利用して、次のようにも記述できます。

$ for dir in *; { [ -d "$dir" ] && (echo "$dir:"; ls "$dir" | wc -l); }

理解しておくと便利な正規表現

 UNIXで使われる正規表現には、基本正規表現と拡張正規表現の2種類があります。

 sed、grep、exprはデフォルトでは基本正規表現を使用します。またperlやruby、emacsなどは、それぞれ独自に拡張した正規表現を使用しているので注意が必要です。

基本正規表現の表記一覧

記号 意味
[] 括弧内の任意の1文字にマッチします。
[^ ] 括弧内以外の文字にマッチします。
\(パターン\) 正規表現パターンをひとまとめのグループとして扱います。
* 直前の正規表現の0回以上の繰り返しを行います。
\{n,m\} 直前の正規表現のn回以上、m回以下の繰り返しを行います。
\{n\} 直前の正規表現のn回の繰り返しを行います。
\{n,\} 直前の正規表現のn回以上の繰り返しを行います。
^ 文字列の先頭、あるいは行の先頭にマッチします。
$ 文字列の最後、あるいは行の最後にマッチします。
\ 正規表現に使われる記号を普通の文字として扱います。
. 任意の1文字にマッチします。

 ファイル名から拡張子を取り除いて表示するコマンドは、sedコマンドを利用して次のように記述できます。

$ ls
test1.bak  test2.c  test3.html  test4.txt

$ ls * | sed 's/\.[^.]*$//'test1test2test3test4

 「sed 's/\.[^.]*$//'」とすれば最後のピリオド以降行末までが削除されます。また、入力がピリオドを含まない(拡張子を持たない)場合は、何も行いません。このコマンドであれば、拡張子が不定の場合でも使用可能です。

まとめ

 今回は、覚えておくべきシェルの制御構造、正規表現に関して簡単に説明しました。

 次回からは、これらのコマンドや制御構造を組み合わせて、業務で楽できるようなテクニックをテーマ別に紹介します。

スポンサードリンク