外部コマンドとビルトインコマンドという存在

スポンサーリンク
エンジニア

最近とある事情により調べたのだが、

UnixやLinuxにおけるコマンドについて、

新しい事実を知ったのでメモを兼ねて書き記しておく。

同じコマンドでも実は複数の種類がある

Linux系かつGUIを提供するOSを普段使いで用いている人は、

個人PCユーザー全体の割合からすればまだまだ低いと思うので、

コマンドの登場頻度は割と高いはずだ。

(意識しないように作られてるだけで、GUIの裏ではコマンドが打たれているが)

Mac OS も元はUNIX。ターミナルからlsコマンドを打ったことがある人は多いはず。

一般的なUnixやLinuxを操作する場合は

TeraTermやPuTTYなどの仮想端末ソフトを立ち上げ、

そして、その中で様々な"コマンド"を明示的に実行する。

  • 作業ディレクトリを変える "cd" (change directory)
  • 作業ディレクトリ配下のファイル・ディレクトリを列挙する"ls"
  • 標準出力に文字列を出力する "echo"

などなど。これらのコマンドはOSが変わっても、

UNIX系統のサーバーで作業すると大体似たノリで使えるので

ついつい前の現場で書いたようなスクリプトを流用しがちなのだが、

ここには一つ注意すべき点がある。

これらのコマンドの中には、

"OSインストール時に導入される"ものと、(=外部コマンド)

"使用しているシェルに機能として組み込まれている"もの、(=内部コマンド)

そして”同じ名前でその両方が存在する"もの

の三種類が混在しているのである。

それらが同じ機能とは限らないため、スクリプトの流用には注意が必要となる。

そもそもシェルってなんだっけ

細かい説明に移る前に、"シェル"という概念について今一度ざっくりおさらいしたいが、

あまり長くなるのも良くないので別記事にまとめる事とした。

簡潔に言うと、シェルとは

「OSの中枢(カーネル)に外部からの指示内容を実行してもらうためのプログラム」

「カーネルの指示内容の実行結果を指示してきた外部に返すためのプログラム」

である。要はプログラムであり、中に関数を複数保持している。

さて、そうなると次に出てくる疑問はこうだろう。

シェルに組み込んで何がうれしいねん

ある処理をするコマンドを、シェルの機能として組み込んで

(シェルと同じプロセス内で呼び出し可能にして)何がうれしいのか。

一つは「該当の処理までが早くなる」ということである。

感覚的に捉えるとある意味当たり前で、

コンピュータのどこかに存在するコマンドを探しに行かなくてもよくなるからだ。

コマンド一つを探すにもメモリ、CPUなどのリソースは使用される。

Unix/Linuxの基礎的なコマンドが生まれた当時、

あまりコンピューターの性能は高くないので、資源節約を目的とした

ビルトインにしよう、というのはわりあい自然な流れである。

もう一つは、「組み込みでないと表現できない処理がある」というパターン。

例えば、現在実行しているシェルの作業ディレクトリを変更する

cdコマンド

など。UNIXでは、プロセスごとに作業ディレクトリを管理するので、

外部からコマンドを実行してディレクトリを変える事が原理的に不可能である。

従って、シェル・ビルトインとして実装する以外に上記の処理をさせる事が

出来ないのだ。

※なお、中には/usr/bin/cd などが存在する(外部コマンド版cdがある)OSも

あるらしいが、それには色々と事情があるらしい(→cd - 通信用語の基礎知識)

具体的にどう気を付ければいいねん

では、OSデフォルトのコマンドと、シェルのビルトインのコマンド。

それらがあって、どう困るのか。そして、どう気を付けるのか。

片方ずつにしか実装されていないものは別に問題ない。

問題があるのは

「ビルトインと外部コマンド両方が存在する場合に、

 望む結果が得られない/バグの温床となりうる」ことである。

じゃあどう気を付けたらいいのか。

こういう時は基本に立ち返ろう。

コマンドマニュアルを読め

・・・これじゃあちょっと身もふたもないし、実は正確でもないので、

少し掘り下げてみようか。

Linux系OSの環境を準備

お手元にUNIX系の環境がすぐに用意できる人はあまり多くないが、

Windows10ユーザーなら話は別だ。

「プログラムと機能」から

"WSL(Windows Subsystem for Linux)"機能を有効にすると、

特別な準備なくLinuxのOSを仮想的に立ち上げることができる。

▲Windowsユーザーにはおなじみの機能有効化
(PowerShellなどもこの画面経由)

Windows10でなく、今あるWindowsの環境を失いたくないという人は

Oracle社製のVirtualBoxと適当なイメージをDLして、ゲストOSを立ち上げればよい。

(こちらもいずれ別記事で解説する)

僕の場合はWindows10なので、上記機能を有効にした後、以下のように

Windows StoreからUbuntuをダウンロードし、実行してUbuntuを立ち上げる。

▲先日まで使っていたWindows8.1ではダウンロードできない
▲コマンドプロンプトに四苦八苦していた時代から考えると隔世の念あり

単純なechoコマンドでも挙動に違いが出る

ユーザーやパスワードの設定を経てUbuntuにログインしたら、

echoコマンドの種類を調べてみよう。

多くのLinuxでは-a オプション付きのtype コマンドを実行すると、

エイリアスがあればエイリアスを含めて、そのコマンドの種類を表示してくれる。

▲2行、2種類のechoコマンドが表示された。エイリアスがあると増える

さて、どうやらechoコマンドには2種類あるらしい。

何もつけずにechoコマンドを実行すると、シェル・ビルトインが優先される。

外部コマンド版を明示的に実行するには/bin/echo を フルパスで指定してやればよい。

外部コマンドおよびシェル・ビルトイン版でそれぞれ-eオプション

(エスケープシーケンス有効)を付けて"\123"を出力させると、以下のようになる。

▲同じ文字を出力するかと思いきや。

このように、同じ出力結果が期待されるものの実際には異なる挙動を示す

SとはASCIIコードを8進数で表したときに123に該当する文字である。

これは本来、8進数は"\0123"と入力するのがマニュアル上正しいが、

外部コマンド版echoは0を抜いて記述したものも8進数として解釈してくれる

ためである。

一方、Ubuntuのデフォルトシェルであるbashのechoでは忖度してくれない。

(むしろそのほうが良いが)

この差異を回避するには、両方ともマニュアルでの記述通り"\0123"とすればよい。

▲二つともヘルプ・マニュアル通りに記述すると同じ出力になる(ビルトインはhelpコマンド、/bin/echo はmanコマンド)

期待通り、両方とも”S”の出力が得られた。

おわりに

業務上の都合でシェルスクリプトなどを書く場面は意外と多く、

またそのシェルが一種類とは限らない。

今回はUbuntuのデフォルトがbashのため、

bashでのビルトインと外部コマンドの差異を確認したが、

世の中にはsh,ksh,zsh,csh,yash などなど多くのシェルがあり、

またOSにも系列がいくつかあるため、差異の出方は複数通り存在しうる。

潜在的なバグが発生しかねないため、別の現場で実装したようなスクリプトは

極力流用しないのが自分の身を守る術である。

そうして現場トラブルをなるべく減らして快適に仕事をするように整える、

それがしがないシステムエンジニアのささやかなライフハックなのであった。

コメント