Linux におけるシェルの進化

Bourne からBash、そしてさらにその先へ

コンピューターで行う日常的なタスクのほとんどには、ポイント・アンド・クリック式のインターフェースで十分ですが、他の環境に勝る Linux の強みをフルに活かすには、GUI の殻を破ってコマンドラインを利用することが結局必要となってきます。利用できるコマンド・シェルには、bash から Korn シェル、C シェル、そして各種の特異なシェルまで、さまざまにあります。この記事を読んでシェルについて学び、自分に最も適したシェルを見つけてください。[注: リスト 2 とリスト 3 に、マイナーな変更を加えました。]

M. Tim Jones, Platform Architect, Intel

author photo - M. Tim JonesM. Tim Jones は組み込みソフトウェアのエンジニアであり、『Artificial Intelligence: A Systems Approach』、『GNU/Linux Application Programming』(現在、第 2 版です) や『AI Application Programming』(こちらも現在、第 2 版です)、それに『BSD Sockets Programming from a Multilanguage Perspective』などの著者でもあります。技術的な経歴は静止軌道衛星用のカーネル開発から、組み込みシステム・アーキテクチャーやネットワーク・プロトコル開発まで、広範にわたっています。彼は Intel に勤務していて、コロラド州ロングモン在住です。



2012年 1月 20日

Tim とつながるには

Tim は developerWorks で人気の高いお馴染みの著者の 1 人です。Tim が書いたすべての developerWorks 記事を閲覧してみてください。また、developerWorks コミュニティーでは、Tim のプロフィールを調べることや、彼やその他の著者、そして他の開発者とつながることができます。

シェルはエディターに似ています。誰もがそれぞれに好みを持っていて、自分の選択を躍起になって擁護します (そして、その好みに乗り換えるべき理由を説きます)。同じように、シェルはそれぞれに異なる機能を提供しますが、いずれのシェルにしても、数十年も前に作り出されたコアとなる概念を今でもかたくなに実装しています。

私が初めて最新のシェルというものを経験したのは、SunOS でソフトウェアを開発していた 1980年代のことです。あるプログラムからの出力を別のプログラムの入力として適用できること (さらには、この操作を連鎖的に繰り返せること) を覚えてからは、シェルをフィルター機能や変換機能を作成するための単純で効率的な手段として使うようになりました。このシェルのコアとなる概念によって、他のツールと有効に組み合わせられる、柔軟で単純なツールを作成する手段を得たというわけです。このように、シェルはカーネルやデバイスを操作するだけの手段ではなく、今ではソフトウェア開発で一般的なデザイン・パターンとなっている統合サービス (例えば、パイプとフィルターなど) でもありました。

この記事ではまず、近年のシェルの歴史を簡単に説明した後、現在 Linux で使用できる有用なシェル、そして特異なシェルのいくつかを取り上げて詳しく調べます。

シェルの歴史

小言語としてのシェル

シェルは特殊化されたドメイン特化言語 (小言語) であり、特定の使用モデルを実装します。この場合の使用モデルとは、オペレーティング・システムへのインターフェースを提供するものです。テキスト・ベースのオペレーティング・システムのシェルに加え、グラフィカル・ユーザー・インターフェースのシェルや、特定の言語専用のシェル (Python シェルや Ruby の irb など) もあります。シェルの概念は、goosh と呼ばれる Web フロント・エンドによる Web 検索にも適用されています。この Google 専用のシェルでは、searchmorego などのコマンドを使用して、Google でコマンドライン検索を実行することができます。

シェル (すなわち、コマンドライン・インタープリター) には長い歴史がありますが、この記事では初めて開発された UNIX のシェルから説明を始めます。この V6 シェルと呼ばれる初の UNIX シェルは、1971年に Ken Thompson 氏 (ベル研究所) によって開発されました。V6 シェル (/bin/sh) は、Multics でのその前身と同じく、カーネル外部で動作する独立したユーザー・プログラムです。グロビング (*.txt などのパラメーターを展開してファイル名とパターン・マッチングさせる処理) のような概念は、条件式を評価する if コマンドと同じく、glob という名前の別のユーティリティーに実装されていました。このように分離することによって、V6 シェルの C 言語ソースは 900 行を下回るサイズに抑えられていました (オリジナル・ソースへのリンクが「参考文献」に記載されています)。

V6 シェルが導入したリダイレクト (< > および >>) およびパイプ (| または ^) の簡潔な構文は、現在のシェルでも引き続き使用されています。また、V6 シェルでは連続したコマンド (; を使用) や非同期コマンド (& を使用) の呼び出しもサポートされていることがわかります。

この Thompson シェルに足りなかったのは、スクリプトを作成できなかったことです。それというのも、このシェルは、コマンドを呼び出して結果を表示するためのインタラクティブなシェル (コマンド・インタープリター) としての役割を果たすことのみを目的としていたからです。


1977年以降に開発された UNIX シェル

Thompson シェルの次は、1977年以降に開発されたシェルに目を向けます。1977年は、Bourne シェルが発表された年です。AT&T ベル研究所の Stephen Bourne 氏が V7 UNIX 用に作成した Bourne シェルは、今日でも有用なシェルとして (場合によってはデフォルトのルート・シェルとして) 使用されています。Bourne 氏がこのシェルを開発したのは、彼が ALGOL68 コンパイラーに取り組んだ後だったため、Bourne シェルの文法には ALGOL (ALGOrythmic Language) との類似性が色濃く出ています。ソース・コード自体は C 言語で作られているものの、マクロさえも使用されていることから、ALGOL68 の特色が反映されています。

Bourne シェルには主に 2 つの目標がありました。それは、オペレーティング・システムに対してインタラクティブにコマンドを実行するためのコマンド・インタープリターとしての役割を果たすこと、そしてスクリプトを作成する際のコマンド・インタープリターとして機能すること (シェルから呼び出すことのできる再利用可能なスクリプトを作成すること) です。Bourne シェルには Thompson シェルを単に置き換えるだけでなく、それまでのシェルに比べて優れた点がありました。Bourne 氏はスクリプトに制御フロー、ループ、および変数を導入し、より関数型に近い言語でオペレーティング・システムを (インタラクティブな方法と、そうではない方法の両方で) 操作できるようにしています。また、このシェルではシェル・スクリプトをフィルターとして使用することができるので、シグナルを扱うための処理を統合することができます。ただし、関数を定義する機能は欠けていました。最終的にこのシェルには、私たちが現在使用している数々の機能が組み込まれることになりました。例えば、コマンド代入 (逆引用符を使用)、スクリプト内に保存された文字列リテラルを組み込むためのヒア・ドキュメントなどです。

シェルの歴史のなかで、Bourne シェルは重要な前進となっただけでなく、多数のシェルを派生させる土台にもなりました。Bourne シェルから派生したシェルの多くは、現在でも典型的な Linux システムで使用されています。図 1 に、重要なシェルの系列を示します。この図を見るとわかるように、Bourne シェル (sh) から Korn シェル (ksh)、Almquist シェル (ash)、そしてよく使われている Bourne Again Shell (bash) が開発されました。C シェル (csh) に関しては、Bourne シェルがリリースされた時点では、まだ開発中でした。図 1 に示されているのは主な系列だけです。ここには示されていなくても、Bourne シェルが大きな影響を与えたシェルは他にもあります。

図 1. 1977年以降に開発された UNIX/Linux シェル
1977年以降に開発された UNIX/Linux シェルの系図

この後、上の図に示されているシェルのいくつかを取り上げ、シェルの前進に貢献した言語および機能の例を検討します。


シェルの基本アーキテクチャー

Bourne 氏が開発したシェルからも明らかなように、仮想的なシェルの基本的アーキテクチャーは単純なものです。図 2 に示されているとおり、基本アーキテクチャーはパイプラインのようになっていて、入力が分析および構文解析され、記号が展開され (これには、山括弧、チルダ、変数およびパラメーターの展開と代入、ファイル名の生成など、さまざまな方法が使用されます)、最後に (シェルの組み込みコマンド、または外部コマンドを使用して) コマンドが実行されます。

図 2. 仮想的なシェルの単純なアーキテクチャー
仮想的なシェルの単純なアーキテクチャーとカーネルを示す図

オープソースの bash シェルのアーキテクチャーについて学ぶには、「参考文献」セクションに記載されているリンクを参照してください。


Linux シェルの詳細

ここからは、いくつかの Linux シェルについて詳しく探り、それぞれの貢献を振り返るとともにサンプル・スクリプトについて調べて行きます。このセクションで取り上げるシェルは、C シェル、Korn シェル、そして bash です。

Tenex C シェル

C シェルは 1978年に、当時カリフォルニア州立大学バークレー校の大学院生だった Bill Joy 氏が BSD (Berkeley Software Distribution) UNIX システム用に開発したシェルです。その 5 年後、C シェルには Tenex システム (DEC PDP システムでよく使用されていたオペレーティング・システム) の機能が統合されます。Tenex は、コマンドラインの編集機能に加え、ファイル名とコマンドの補完機能を導入したシステムです。この統合の結果生まれた Tenex C シェル (tcsh) には、csh との後方互換性が残されていますが、そのインタラクティブな機能は全体が改善されています。tcsh を開発したのは、カーネギー・メロン大学の Ken Greer 氏です。

C シェルの重要な設計目標の 1 つとなっていたのは、C 言語と同様のスクリプト言語を作成することです。当時、主に使用されている言語は C であったため (Tenex オペレーティング・システムも大部分が C で開発されていました)、これは有益な目標でした。

Bill Joy 氏が C シェルに導入した便利な機能は、コマンド・ヒストリーの機能です。この機能は、それまでに実行されたコマンドの履歴を保持することで、ユーザーが同じコマンドを実行するときに、その履歴を調べてコマンドを簡単に選択して実行できるようにします。例えば、history コマンドを入力すると、これまでに実行されたコマンドが表示されます。その後は、上下矢印キーを使ってコマンドを選択するか、あるいは !! を使用して前回のコマンドを実行します。また、前回のコマンドの引数を参照することもできます。例えば、!* は前回のコマンドのすべての引数を参照し、!$ は前回のコマンドの最後の引数を参照します。

tcsh スクリプトの簡単な例を見てください (リスト 1)。このスクリプトは引数を 1 つ (ディレクトリー名) 取り、そのディレクトリー内に置かれているすべての実行可能ファイルと併せ、検出したファイル数を出力します。以降の例でも、このスクリプト設計を使用することで、シェル間の違いを明らかにします。

この tcsh スクリプトは、3 つの基本セクションに分かれています。まず始めに、シバン (#! 記号) を使用することで、このファイルを、指定されたシェル実行可能プログラム (この例では、tcsh バイナリー) で解釈可能なファイルとして宣言していることに注意してください。こうすることにより、このファイルを実行する際にはインタープリターのバイナリー・ファイルに続けてこのファイルを指定しなくても、通常の実行可能ファイルとして実行できるようになります。スクリプトは検出した実行可能ファイルのカウントを保持します。そのために、このカウントをゼロで初期化します。

リスト 1. すべての実行可能ファイルを検出する tcsh スクリプト
#!/bin/tcsh
# find all executables

set count=0

# Test arguments
if ($#argv != 1) then
  echo "Usage is $0 <dir>"
  exit 1
endif

# Ensure argument is a directory
if (! -d  $1) then
  echo "$1 is not a directory."
  exit 1
endif

# Iterate the directory, emit executable files
foreach filename ($1/*)
  if (-x $filename) then
    echo $filename
    @ count = $count + 1
  endif
end

echo
echo "$count executable files found."

exit 0

最初のセクションでは、ユーザーから渡された引数をテストします。#argv 変数は、渡された引数の数 (コマンド名自体は除外) を表します。これらの引数にアクセスするには、引数のインデックスを指定します。例えば、#1 (argv[1] の省略形) は最初の引数を参照します。このスクリプトが期待する引数は 1 つだけです。引数が見つからない場合はエラー・メッセージを出力し、コンソールに入力されたコマンド名を $0 (argv[0]) で示します。

2 番目のセクションでは、渡された引数がディレクトリーであることを確認します。引数がディレクトリーであれば、-d 演算子が True を返します。ただし、演算子の前に否定を意味する ! を指定していることに注意してください。このようにして、引数がディレクトリーでない場合にはエラー・メッセージを出力します。

最後のセクションでは、ディレクトリー内のファイルを繰り返し処理し、それが実行可能なファイルであるかどうかをテストします。ここでは、便利な foreach イテレーターを使用したループ処理によって、括弧内に示されるそれぞれのエントリー (この例では、ディレクトリー) についてループのなかで判定をしています。このステップでは、ファイルが実行可能であるかどうかを判定するために、-x 演算子を使用します。ファイルが実行可能であれば、そのファイル名が出力されて、カウントがインクリメントされます。そして、スクリプトの最後で実行可能ファイルのカウントを出力するという仕組みです。

Korn シェル

David Korn 氏によって設計された Korn シェル (ksh) は、Tenex C シェルと同じ頃に発表されました。Korn シェルで非常に興味深い特徴の 1 つは、これがスクリプト言語として使用されると同時に、Bourne シェルとの後方互換性を備えていることです。

Korn シェルは、2000年まではプロプライエタリー・ソフトウェアでしたが、その年 (Common Public License の下で) オープンソースとしてリリースされました。Bourne シェルとの強力な後方互換性を提供するだけでなく、Korn シェルには他のシェルの機能も統合されています (この点は、csh の場合と同様です)。さらに、Ruby や Python などの最近のスクリプト言語に見られる高度な機能も提供しており、例えば連想配列や浮動小数点演算を使用できるようになっています。現在、IBM AIX や HP-UX をはじめとする多数のオペレーティング・システムで使用可能な Korn シェルは、POSIX (Portable Operating System Interface for UNIX) シェル言語標準のサポートを目指しています。

Bourne シェルから派生した Korn シェルは、C シェルにも似ていますが、もっとよく似ているのは Bourne シェルや bash です。Korn シェルで実行可能ファイルを検出する例を見てみましょう (リスト 2)。

リスト 2. すべての実行可能ファイルを検出する ksh スクリプト
#!/usr/bin/ksh
# find all executables

count=0

# Test arguments
if [ $# -ne 1 ] ; then
  echo "Usage is $0 <dir>"
  exit 1
fi

# Ensure argument is a directory
if [ ! -d  "$1" ] ; then
  echo "$1 is not a directory."
  exit 1
fi

# Iterate the directory, emit executable files
for filename in "$1"/*
do
  if [ -x "$filename" ] ; then
    echo $filename
    count=$((count+1))
  fi
done

echo
echo "$count executable files found."

exit 0

リスト 2 を見て最初に気付くのは、リスト 1 と似ているということでしょう。構造に関して言うと、このスクリプトはリスト 1 とほとんど同じですが、条件文、式、および繰り返し処理を行う方法は明らかに異なります。C と同様の判定用の演算子を採用する代わりに、ksh では典型的な Bourne スタイルの演算子 (-eq-ne-lt など) を採用しています。

Korn シェルには、繰り返し処理に関する違いもあります。Korn シェルでは for in 構造が使用され、指定されたサブディレクトリーの内容を表すコマンド ls '$1/* の標準出力から作成されたファイルのリストを、コマンド代入によって表現します。

上記で定義されている機能に加え、Korn はエイリアス機能 (単語をユーザー定義の文字列に置換する機能) もサポートします。その他にも、Korn にはデフォルトで使用不可に設定されている多くの機能があります (ファイル名補完など)。これらの機能は、ユーザーによって使用可能にすることができます。

Bourne-Again Shell

Bourne-Again Shell (bash) は、Bourne シェルの代替手段となるように意図されたオープンソースの GNU プロジェクトです。Brian Fox 氏によって開発された bash は、現在使用できるシェルのなかで最も広まっているシェルの 1 つとなっています (Linux、Darwin、Windows、Cygwin、Novell、Haiku などで使用されています)。その名前が示唆するように、bash は Bourneシェルの上位集合であり、ほとんどの Bourne スクリプトをそのまま変更せずに実行することができます。

スクリプトの後方互換性をサポートするだけでなく、bash は Korn シェルおよび C シェルの機能も統合しているため、コマンド・ヒストリー、コマンドライン編集、ディレクトリー・スタック (pushd および popd)、多数の有用な環境変数、コマンド補完なども使用することができます。

bash は進化を続け、新しい機能や (Perl と同様の) 正規表現、連想配列などを取り込んでいきました。これらの機能の一部は他のスクリプト言語には存在しないかもしれませんが、他の言語にも対応するスクリプトを作成することは可能です。この点で言うと、リスト 3 に記載するサンプル・スクリプトは、シバン行 (/bin/bash) を除けば Korn シェル・スクリプト (リスト 2) とまったく同じです。

リスト 3. すべての実行可能ファイルを検出する ksh スクリプト
#!/bin/bash
# find all executables

count=0

# Test arguments
if [ $# -ne 1 ] ; then
  echo "Usage is $0 <dir>"
  exit 1
fi

# Ensure argument is a directory
if [ ! -d  "$1" ] ; then
  echo "$1 is not a directory."
  exit 1
fi

# Iterate the directory, emit executable files
for filename in "$1"/*
do
  if [ -x "$filename" ] ; then
    echo $filename
    count=$((count+1))
  fi
done

echo
echo "$count executable files found."

exit 0

これらのシェルの間の重要な違いは、どのライセンスの下でシェルがリリースされているかです。bash はご想像のとおり、GNU プロジェクトによって開発されていることから GPL の下でリリースされていますが、csh、tcsh、zsh、ash、および scsh はいずれも、BSD または BSD と同様のライセンスの下でリリースされています。Korn シェルは、Common Public License の下で使用可能です。


特異なシェル

冒険してみたい方は、ニーズや好みに応じて別のシェルを使用することもできます。例えば、Scheme シェル (scsh) は、Scheme (Lisp 言語から派生した言語) を使用したスクリプト環境を提供します。Pyshell は、Python 言語を使用して同様のスクリプトを作成するための試みです。さらに組み込みシステム用に、シェルとすべてのコマンドを 1 つのバイナリーに統合して配布および管理を単純化した BusyBox もあります。

リスト 4 に、Scheme シェルですべての実行可能ファイルを検出するスクリプトの一例を記載します。このスクリプトは奇異に見えるかもしれませんが、これまでに説明したスクリプトと同様の機能を実装しています。このスクリプトには、引数のカウントをテストするための 3 つの関数と (最後に) 直接実行可能なコードが含まれています。このスクリプトの中心部は、showfiles 関数の中にあります。この関数は、(with-cwd によって作成された) リストを繰り返し処理し、リストの各要素の後で write-ln を呼び出します。このリストを生成するために、指定されたディレクトリーを繰り返し処理して、実行可能なファイルをフィルタリングしています。

リスト 4. すべての実行可能なファイルを検出する scsh スクリプト
#!/usr/bin/scsh -s
!#

(define argc
        (length command-line-arguments))

(define (write-ln x)
        (display x) (newline))

(define (showfiles dir)
        (for-each write-ln
                (with-cwd dir
                        (filter file-executable? (directory-files "." #t)))))

(if (not (= argc 1))
        (write-ln "Usage is fae.scsh dir")
        (showfiles (argv 1)))

まとめ

シェルの歴史のなかでも初期の頃に登場したシェルの概念の多く、そしてインターフェースの大部分は、約 35 年を経た今でも変わっていません。これは、初期のシェルを考案した作成者たちの極めて優れた能力の証です。絶えず改革を続ける業界のなかでシェルは改良されてきたものの、大幅に変更されてはいません。これまで、特殊化したシェルを作成しようとする試みも何度か行われましたが、Bourne シェル系列のシェルが今でも最もよく使用されているシェルとなっています。

参考文献

学ぶために

  • J.A. Neitzel によって開発、保守されている V6 Thompson Shell Port (osh) は、osh およびこのシェルが依存する外部シェル・ユーティリティー (ifgoto など) に関する素晴らしいリソースです。Thompson シェルで作成されたユーティリティーおよびオリジナルのソース・コード自体のアーカイブも調べることができます。
  • 非公式の Google シェルである Goosh は、一般に使用されている Google 検索インターフェースをベースにシェル・インターフェースを実装します。Goosh は、従来とは異なるインターフェースにシェルを適用できることを明らかにする興味深い例です。
  • 現在私たちが使用している多くのシェルは、Bourne シェルから派生したものです。ソース・ファイルには、C マクロの使用によって実現された ALGOL68 の特色がある程度反映されています。
  • Bourne シェル、Korn シェル、および C シェルの機能を統合した Bourne-Again Shell は、Linux で最もよく使用されているシェルです。素晴らしい読み物として、「The Architecture of Open Source Applications」の第 3 章を読んでください。Bash の構成および内部要素を学ぶことができます。
  • シェル・スクリプトに関するその他の developerWorks の記事を調べてください。bash について学ぶには、Daniel Robbins による連載、「bash 例解」の第 1 回 (2000年3月)、第 2 回 (2000年4月)、第 3 回 (2000年5月) があります。また、Korn シェル・スクリプト (2008年6月) や Tcsh シェル変数 (2008年8月) について学ぶ記事もあります。
  • kornshell サイトで、ドキュメントやその他のリソースを含め、Korn シェルに関する最新情報を入手してください。
  • ウィキペディアに、さまざまなシェルの一般特性、対話機能、プログラミング機能、構文、データ型、および IPC メカニズムなどを比較した見事な表が記載されています。
  • Tim の記事、「BusyBox を使って組み込み Linux システムを単純化する」(developerWorks、2006年8月) では、BusyBox アプリケーションについて詳しく探り、この静的シェル・アーキテクチャーに新しいコマンドを追加する方法を説明しています。
  • developerWorks Linux ゾーンで、Linux 開発者および管理者向けのハウツー記事とチュートリアル、そしてダウンロード、ディスカッション、フォーラムなど、豊富に揃った資料を探してください。
  • さまざまな IBM 製品および IT 業界についての話題に絞った developerWorks の Technical events and webcasts で時代の流れをキャッチしてください。
  • 無料の developerWorks Live! briefing に参加して、IBM の製品およびツール、そして IT 業界の傾向を素早く学んでください。
  • developerWorks の on-demand demos で、初心者向けの製品のインストールとセットアップから、熟練開発者向けの高度な機能に至るまで、さまざまに揃ったデモを見てください。
  • Twitter で Tim をフォローしてください。また、Twitter で developerWorks をフォローすることも、developerWorks で Linux に関するツイートのフィードに登録することもできます。

製品や技術を入手するために

  • ご自分に最適な方法で IBM 製品を評価してください。評価の方法としては、製品の試用版をダウンロードすることも、オンラインで製品を試してみることも、クラウド環境で製品を使用することもできます。また、SOA Sandbox では、数時間でサービス指向アーキテクチャーの実装方法を効率的に学ぶことができます。

議論するために

  • My developerWorks コミュニティーに加わってください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=785458
ArticleTitle=Linux におけるシェルの進化
publish-date=01202012