次のページ 前のページ 目次へ

3. 外部コマンド

3.1 PROMPT_COMMAND

Bash には、PROMPT_COMMAND という環境変数があります。この内容は Bash がプロンプトを出す前に普通の Bash のコマンドとして実行されます。

[21:55:01][giles@nikola:~] PS1="[\u@\h:\w]\$ "
[giles@nikola:~] PROMPT_COMMAND="date +%H%M"
2155
[giles@nikola:~] d
bin   mail
2156
[giles@nikola:~] 

ここでは、\tエスケープシーケンスは PS1 の中にはありませんので、 プロンプトには時間情報が含まれません。date +%H%Mによって 私の好みのフォーマットで時間が表示されます。しかし、時間がプロンプトと 別の行に表れます。Bash 2.0+ では、echo -n ...を使ってこれを 直すことができますが、Bash 1.14.7 ではできないようです。プロンプトの 表示方法が異なっており、テキストが2回表示されるようになってしまいます。

2156
[giles@nikola:~] PROMPT_COMMAND="echo -n [$(date +%H%M)]"
[2156][giles@nikola:~]$
[2156][giles@nikola:~]$ d
bin   mail
[2157][giles@nikola:~]$ unset PROMPT_COMMAND
[giles@nikola:~]

echo -n ...は date コマンドの出力を制御し、最後の改行を抑制する ことによって、プロンプトが1行で表示されるようになります。最後に unsetコマンドを使って、環境変数 PROMPT_COMMAND を除きます。

コマンド展開を行なうのに、$(<command>)という書き方を使って いることに注意してください。つまり、

$(date +%H%M)

は、「date +%H%Mのコマンドの出力で置換する」という意味です。 これは、Bash 2.0+ では動きますが、1.14.7 以前の Bash では、バッククォート を使って、`date +%H%M`とします。Bash 2.0+ でもバッククォートは 使えますが、$()の方がネストしやすいので使われなくなっています。 この文書ではこの書き方を使っていきます。もしあなたの使っている Bash が 古いバージョンなら、$()の代わりにバッククォートを使ってください。 コマンド展開がエスケープされている時(つまり \$(command) )は、バッククォートを 両方エスケープ(つまり \`command\` )してください

3.2 プロンプトの中での外部コマンド

普通の Linux のコマンドの出力を直接プロンプトに埋め込むこともできます。 たくさんのものを埋め込もうとはしないでください。 プロンプトが長くなりすぎます。あなたは 速いコマンドを使いたいとも思っているでしょう。プロンプトが画面に 表示されるたびに実行されて、作業中にプロンプトの表示に時間がかかると イライラしますから。(前の 例に似ていますが、これは Bash 1.14.7 でも動作します。)

[21:58:33][giles@nikola:~]$ PS1="[\$(date +%H%M)][\u@\h:\w]\$ "
[2159][giles@nikola:~]$ ls
bin   mail
[2200][giles@nikola:~]$

コマンド展開のドル印の前にあるバックスラッシュに注意して下さい。これが ないと外部コマンドは、PS1 文字列が環境に読み込まれる時にたった一度だけしか 実行されません。このプロンプトの場合には、どんなに長くプロンプトが 使われても、いつも同じ時間を表示することになります。 バックスラッシュを置くことによって、$()の内容がすぐには実行されなく なり、"date" はプロンプトが生成される度に実行されることになります。

Linux には多くの小さなユーティリティがついてきます。dategrepwcなどでデータを操作できます。このような プログラムを使って複雑なプロンプトを作るには、シェルスクリプトを 作って、それをプロンプトから実行させた方がいいでしょう。スクリプトの 中でもコマンドが正しい時に実行されるよう、エスケープシーケンスが 必要になることがあります(上のdateコマンドの例のように)。PS1の行の 中ではレベルが一段上がることになりますので、シェルスクリプトを作る ことによりこれを避けるのはよい考えです。

小さなシェルスクリプトをプロンプトの中で使う例を示しましょう。


#!/bin/bash
#     lsbytesum - sum the number of bytes in a directory listing
TotalBytes=0
for Bytes in $(ls -l | grep "^-" | cut -c30-41)
do
    let TotalBytes=$TotalBytes+$Bytes
done
TotalMeg=$(echo -e "scale=3 \n$TotalBytes/1048576 \nquit" | bc)
echo -n "$TotalMeg"

このようなものを関数(の方が効率的ですが、この文書では関数の書き方は 残念ながら扱いません)にしたり、パスの通っている  /bin ディレクトリの中に シェルスクリプトとして保存しておきます。プロンプトの中での使い方は、

[2158][giles@nikola:~]$ PS1="[\u@\h:\w (\$(lsbytesum) Mb)]\$ "
[giles@nikola:~ (0 Mb)]$ cd /bin
[giles@nikola:/bin (4.498 Mb)]$

3.3 プロンプトに何を置くか

私が示すプロンプトの例では、ほとんどいつもユーザー名、機械名、時間、現在の ディレクトリ名が表示されることにお気づきでしょう。時間を除けば、これらは プロンプトに表示される標準のもので、時間はおそらくその次に追加されることの 多いものでしょう。でも何をいれるかは、全く個人の趣味にかかるものです。 ここでは、私の知っている人たちの例を示してあなたに参考にしてもらいましょう。

Danのプロンプトは短いけれども効果的です。特に彼の仕事のやり方にとって。

[giles@nikola:~]$ cur_tty=$(tty | sed -e "s/.*tty\(.*\)/\1/")
[giles@nikola:~]$ echo $cur_tty
p4
[giles@nikola:~]$ PS1="\!,$cur_tty,\$?\$ "
1095,p4,0$ 

Danは、ディレクトリツリーを動き回っている間、プロンプトの長さが急に大幅に 変わるので、現在のディレクトリを表示するのは好きではありません。だからそれは 頭にたたき込んでおきます(またはpwdコマンドを使います)。彼は Unix を csh や tcsh で学んだので、コマンドヒストリーを よく使います(Bash 使いはあまりそうはしないようです)ので、プロンプトの最初に ヒストリ番号を表示します。次に(tty コマンドの出力を sed で加工して)tty の 文字列の中の特徴的な部分を表示します。これは "screen" を使っている人の 役に立ちます。三番目に、最後に使ったコマンドや パイプラインの exit 値を表示します(これはプロンプトから実行されるコマンド では意味がありませんので、exit値を別の変数に捕えて表示するようなことを 考える必要があります)。最後に \$ で通常のユーザーでは $ を、root に対しては # を表示するようにします。

Torben Fjerdingstad は、時々ジョブをサスペンドし、そのことについて忘れて しまうので、サスペンド中のジョブを思い出させてくれるプロンプトを使っている とメールしてくれました。

[giles@nikola:~]$ function jobcount {
> jobs|wc -l| awk '{print $1}'
> }
[giles@nikola:~]$ export PS1='\W[`jobcount`]# '
giles[0]# man ls &
[1] 4150

[1]+  Stopped (tty output)    man ls
giles[1]#

Torben は、awkで wc の出力の中のスペースを削っています。私なら sed か tr を使うことでしょう。その方がいいからではなく、その方が慣れて いるからです。他の方法もあることでしょう。Torben は PS1 文字列をシングル クォートで囲んでいますが、これは Bash がバッククォートを直ちに解釈するのを 防ぎます。私が述べたエスケープの方法は使っていません。

注意: Bash 2.02 にはシェルの組み込みコマンドである jobs が パイプに何も渡さないというバグが知られています。上のコードを Bash 2.02 で 試すと、サスペンドしたジョブがいくつあっても0が帰ってきます。Bash の保守を している一人である Chet Ramey は、v2.03 ではこれを直すと言っています。

3.4 Bash の環境と関数

すでに述べたように、PS1、PS2、PS3、PS4、PROMPT_COMMAND はすべて Bash の環境に保存されます。私たちのように DOS からきたものにとって、 DOS の環境領域は狭く、うまく拡張できなかったので、環境に多くの文字を いれるのは恐怖です。おそらく環境の大きさには実際上の上限があるので しょうが、私は知りませんし、DOS ユーザーが慣れていた大きさの何倍もの オーダーの話なのでしょう。Dan は次のように言っています。

「私の使っている対話型のシェルには、エイリアスが62と関数が25ある。 対話型だけで使うもので、簡単に bash で書ける場合は、関数にしている (エイリアスで書くには難しい場合)。もしメモリが気になるなら、bash を 使わない方がいいだろう。bash は私の linux box で動いているプログラムの 中で Oracle の次に大きい。top コマンドを時々動かして、M を押してメモリの 大きさでソートしてごらん。bash がほとんど一番上に近いところにあるのが わかるだろう。sendmail より大きいんだよ! メモリが気になるなら ash のような ものを使えばいい。」

こう書いた時彼はコンソールだけを使っていたのでしょう。X や X アプリケーション を動かせば、Bash より大きいのがいっぱいあります。でも考え方は同じです。 環境は使うべきもので、使い過ぎを心配する必要はないのですから。

こう書くと、Unix の導師たちには単純化しすぎていると検閲を受けるかもしれません。 しかし関数は基本的に小さなシェルスクリプトで、効率のために環境に読み込まれて います。Dan の言葉を借りれば「シェル関数は、限界に近いところまで効率的だ。 シェルスクリプトを source するのとほぼ同様のことをファイル入出力なしに 実行できる。すでにメモリに読み込まれているからだ。シェル関数は通常 ログインシェルかサブシェルかによって、.bashrc か .bash_profile ファイルから 読み込まれる。シェルスクリプトを動かす場合には、現在のシェルは fork し、 子プロセスが exec を行ない、おそらくパスが探され、カーネルはファイルを 開き、実行するのに十分なメモリがあるか確かめ、シェルスクリプトであった 場合には新しいシェルをスクリプト名を引数として与えて起動し、そのシェル がファイルを開いて、各行を実行する。シェル関数の場合と比べると、各行を 実行する以外の部分は不必要なオーバーヘッドにすぎない。」


次のページ 前のページ 目次へ