bash 入門

tsuji

ユーザーの入力をカーネルに伝えまたその逆を行う、いわば両者の橋渡しを行うプログラムをシェルといいます。ユーザーから見てカーネルを包んでいる貝殻(shell)のように見えることからそう名付けられました。
シェルには多くの種類がありますが、Linuxにおけるデファクトスタンダードになっているbashについてどんな機能があるかを見ていきます。

補完

まず知っておきたい基本的な機能が補完です。コマンドプロンプトでTabキーを押すことで、コマンド名やファイル名を補完することが出来ます。

$ ls
abc1  abc2  def
$ cat a[Tab]
$ cat abc

確定できる部分までが補完されました。ここからさらに2回Tabキーを叩くと候補が表示されます。

$ cat abc[Tab][Tab]
abc1  abc2

リダイレクト・パイプ

リダイレクトとパイプという機能を理解するにはプロセスがどのように入出力を行っているかを知っておく必要があります。
Linuxのプロセスは基本的な入出力先として「標準入力」「標準出力」「標準エラー出力」の3つを持っています。通常これらは端末に関連づけられているので、例えば標準出力に出力するプログラムをリダイレクトせずに実行した場合端末上に実行結果が表示されることになります。

$ echo foo
foo

リダイレクトすることで標準入出力が指すものを端末からファイルに変更することが出来ます。

文法意味
< file標準入力先をfileに変更する。
> file標準出力先をfileに変更する。fileがすでに存在していた場合上書きされる。
>> file標準出力先をfileに変更する。fileがすでに存在していた場合追加される。
$ echo foo > output.txt
$ cat output.txt
foo

$ echo foo >> output.txt
$ cat output.txt
foo
foo

$ echo foo > output.txt
$ cat output.txt
foo

$ wc -l < output.txt
      1

また標準出力を他のコマンドの標準入力につなげることが出来ます。この機能をパイプといい、各コマンドを「|」でつなげて表現します。

$ getent passwd | head -5
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

うまくパイプを使うことで簡単に高度な処理をこなすことができます。

$ getent passwd | cut -d : -f 7 | sort | uniq -c | sort -nr | less
   1050 /bin/bash
    230 /bin/false
     27 /sbin/nologin
      1 /sbin/shutdown
      1 /sbin/halt
      1 /bin/sync
      1
(END)

シェル変数と環境変数

シェルは変数を持つことが出来ます。これをシェル変数と言います。現在シェルが保持している変数の一覧を表示するにはsetコマンドを使います。

$ set
BASH=/bin/bash
BASH_VERSINFO=([0]="2" [1]="05b" [2]="0" [3]="1" [4]="release" [5]="i386-redhat-linux-gnu")
BASH_VERSION='2.05b.0(1)-release'
~略~

次のようにすることでシェル変数FOOにfooという値を設定します。変数から値を取り出す方法については下にある変数展開の項を参照してください。

$ FOO=foo

シェル変数とは別に環境変数というものもあります。現在定義されている環境変数を取得するにはenvコマンドを使います。

$ env
PWD=/home
JAVA_HOME=/usr/local/java/j2sdk
LANG=en_US.UTF-8

環境変数を設定するには一回シェル変数に値を代入した後にexportします。

$ env | grep FOO
$ FOO=foo
$ export FOO
$ env | grep FOO
FOO=foo

シェル変数は環境変数と比べてシェルから起動したプロセスに値が引き継がれないという違いがあります。そのためシェル変数はその名の通りシェル固有の値を、環境変数は言語などグローバルな値を持たせるという使い分けがされます。

種類代表的なもの用途
シェル変数PS1シェルのプロンプト文字列を設定する。他のプロセスが参照する必要性がない。
環境変数LANGユーザーの使用言語を設定する。この値を見て処理を変えるプロセスがある。

制御構造

bashは単純なコマンドだけではなく制御文も受け付けます。制御構造を使うと定型的な処理を行うのが楽になります。
制御文には何種類かあるのですが対話的な環境で使うだけならばfor文だけ押さえておけば十分でしょう。
for文の文法は次のようになります。

for 変数名 in リスト
do
  コマンド
done

リストから一つずつ値を取り出して変数に代入した後コマンドを実行します。具体的な例を見てみましょう。

$ for i in a b c
> do
>   echo $i
> done
a
b
c

キーバインド

便利なキーバインドをいくつか紹介します。bashはエディタのemacsに似たキーバインドになっているのでここではemacs流の表記を用います。意味は次のようになります。

表記 意味
C-a Ctrl+a
C-A Ctrl+Shift+a(C-aとは区別されます)
C-a a Ctrl+aと押した後に続けてa
M-a ESC aまたはAlt+a

履歴

カーソル上下で過去に入力したコマンドの履歴を辿ることが出来ます。またインクリメンタルサーチを行うことが出来ます。インクリメンタルサーチとは文字を入力するたびに次候補を探す検索方法のことをいいます。

キー動作
C-r後方へのインクリメンタルサーチ
C-s前方へのインクリメンタルサーチ

インクリメンタルサーチ中は次のようなものが使えます。

キー動作
C-r入力されているキーワードで次の候補を後方検索
C-s入力されているキーワードで次の候補を前方検索
C-m(Enter) 見つかった候補を実行
カーソルの移動現在の候補の編集へ
C-g検索の中断

ただしデフォルトではC-sはフロー制御に使われており利用できません(押してしまって画面が固まってしまった場合はC-qを入力すると復帰します)。次のコマンドでこの処理が無効になり問題なく使えるようになります。

$ stty -ixon

キルリング

Windowsでいうカット&ペーストが出来ます。

キー動作
C-kカーソル位置から行末までを切り取りキルリング(kill ring)に入れます
C-uカーソル位置から行頭までを切り取りキルリングに入れます
C-yヤンク(yank)を行います
M-yキルリングに入っている次の要素をヤンクします。C-yまたはM-yの直後に使います。

キルリングとヤンクという耳慣れない語が出てきましたが、前者は履歴付きのクリップボード、後者は貼り付けのことと理解しておけばいいでしょう。

展開

bashはコマンド文字列を受け取った後に規則に従って展開を行ってから実行しています。

ブレース展開

カンマ区切りの文字列をブレースで囲んだものはブレース展開されます。

$ echo {a,b,c}
a b c

ブレースの前後に文字列があるとそれを反映した展開がなされます。

$ echo 0{a,b,c}1
0a1 0b1 0c1

ブレース展開はまとめてファイルやディレクトリを作成するときなどによく使われます。

$ find . -type d
.

$ mkdir -p a/b{0,1}/c{0,1}
$ find . -type d
.
./a
./a/b0
./a/b0/c0
./a/b0/c1
./a/b1
./a/b1/c0
./a/b1/c1

チルダ展開

チルダ展開は単語がチルダで始まっていた時に行われ、ユーザーのホームディレクトリへと展開されます。
またチルダの後にユーザー名を続けると指定したユーザーのホームディレクトリに展開されることになります。

$ whoami
user1

$ echo ~
/home/user1

$ echo ~user2
/home/user2

ファイル名展開

文字列中に*、?、[が含まれていた場合ファイル名展開が行われます。ファイル名展開はグロブ(glob)とも呼ばれます。
次のパターンを用いて存在するファイル、ディレクトリとのマッチングを行いマッチしたものに展開されます。

パターン効果
*0文字以上の任意の文字列にマッチ。
?1文字の任意の文字列にマッチ。
[...]ブラケットで挟まれている文字のうち任意の1文字にマッチ。正規表現におけるブラケットの解釈とほぼ同じです。
$ ls
a  adc.txt  aec.txt  b

$ echo *
a adc.txt aec.txt b

$ echo ?
a b

$ echo a[abcd]*
adc.txt

# マッチするものが無かった場合パターンはそのままの形で残ります
$ echo xyz*
xyz*

ドットファイルへのマッチは除外されるので、それらを対象にしたい場合は明示的に指定しなければなりません。

$ ls -a
.  ..  .a

$ echo *
*
$ echo .*
. .. .a

ファイル名展開に限らず展開全般に言えることですが自分の予想外の展開が行われる可能性があることに注意が必要です。

$ ls
a  ab  abc

$ cat ab
???

$ cat abc
aaaaaaaa

# 「?」という文字列でgrepをかけたはずが……
$ grep ? *
abc:aaaaaaaa

なぜファイルabc中のaaaaaaaaという行が出力されてしまったのでしょうか?どのように展開が行われたか確認してみます。

$ echo grep ? *
grep a a ab abc

「?」がカレントディレクトリ中の1文字のファイル、つまりaに展開されているのが分かります。そのためカレントディレクトリのすべてのファイルの中から「?」を含むものを探すはずが、「a」を含むものを探すという結果になってしまったという訳です。
これを防ぐには文字列を「"」または「'」で囲んでください。これにより展開を抑止することが出来ます。

$ grep '?' *
ab:???

変数展開

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

$ env | grep SHELL
SHELL=/bin/bash

$ echo $SHELL
/bin/bash

単純に値を取り出すだけではなくその際に文字列を加工することも出来ます。次のようなパターンがあります。

パターン効果
${変数名#word}変数から値を取り出し、その先頭部分がwordと一致した場合その部分を取り除きます。
${変数名%word}変数から値を取り出し、その後方部分がwordと一致した場合その部分を取り除きます。
${変数名//pattern/word}変数から値を取り出し、patternとマッチする部分をwordで置換します。

これを使ってカレントディレクトリにある拡張子がtxtなファイルの拡張子部分を取り除くには次のようにします。

$ ls
a.txt  b.txt  c.txt

$ for i in *.txt
> do
>   echo $i "-->" ${i%.txt}
>   mv   $i       ${i%.txt}
> done
a.txt --> a
b.txt --> b
c.txt --> c

$ ls
a  b  c

コマンド置換

コマンド置換はコマンドの出力結果に展開されます。「$(コマンド)」または「`コマンド`」の形を取ります。
次のコマンドはカレントディレクトリの中で「abc」を含むすべてのファイルの先頭10行を表示します。

$ head -10 $(grep -l abc *)

エイリアス

エイリアスとはその名の示す通りコマンド(+オプション)に対する別名を提供します。普段から組み合わせて使うオプションなどを定義しておくと便利です。
エイリアスを設定する時などにはaliasコマンドを使用します。

エイリアスの定義

ファイルの削除を行う前に常に確認を求めるようにしたいというような時は次のように定義します。

$ alias rm='rm -i'

こうしておくと、rmコマンドを使った時に「rm -i」と解釈されることになります。

$ rm a
rm: remove regular empty file `a'?

もっと複雑なものもエイリアスとして定義できます。

$ alias countprocs='ps ax --no-headers|wc -l'

エイリアスとして解釈が行われるのはあくまでコマンドとして使われたときだけです。
引数として渡されたときなどには何も行われません。

$ echo rm
rm

定義済みエイリアスの確認

aliasコマンドを引数を付けずに実行すると、定義済みのエイリアス一覧を見ることが出来ます。

$ alias
alias rm='rm -i'
alias countprocs='ps ax --no-headers|wc -l'

エイリアスの無効化

定義したエイリアスを無効化するにはunaliasコマンドを使います。

$ unalias countprocs

一時的に無効化する場合はコマンドの先頭に「\」を付けて実行します。上述のrmのように、既存のコマンド名と同名のエイリアスを定義している時に元のコマンドを呼び出すという使い方をします。

$ ls
a  b  c

$ rm a
rm: remove regular empty file `a'?

$ \rm b
$ rm c
rm: remove regular empty file `c'?

$ ls

スクリプトファイル

せっかく定義したエイリアスですがシェルを抜けるとすべて消えてしまいます。だからといってログイン時に毎回手動で定義するのも大変なので、シェルを起動したときに自動で設定されるようにします。
そのためには実行したいコマンドを並べたファイル(こういうファイルをシェルスクリプトと言います)を用意し、それを~/.bashrcとして配置してください。シェルスクリプトではあらゆるコマンドを実行できるので、エイリアスの定義以外にもいろいろな初期化作業を行えます。

~/.bashrcの他にもこのような使われ方をするファイルがあります。

ファイル 評価されるタイミング
~/.bash_profileログインシェルとして起動したとき
~/.bashrc 対話的に起動したとき
~/.bash_logout ログアウトのとき

前者2つの使い分けですが、~/.bash_profileで環境変数、~/.bashrcでシェル変数やエイリアスを定義するようにしておき~/.bash_profileから~/.bashrcを読み込むようにするのが定石です。

$ cat ~/.bash_profile
# Read ~/.bashrc
. ~/.bashrc

export PATH=$PATH:/usr/local/bin
export LANG=en_US.UTF-8

$ cat ~/.bashrc
alias rm='rm -i'

PS1='$ '
HISTFILESIZE=10000
HISTSIZE=10000

ジョブ

bashはパイプラインによってつながれた一連のプロセス群をジョブという単位で管理します。ユーザーはジョブに対してさまざまな制御を行うことが出来ます。

例としてdelaymkdirコマンドというものを考えます。次のシェルスクリプトをdelaymkdir.shの名前で保存し実行権限を与えてください。

#! /bin/sh

sleep 30
mkdir "$@"

これは30秒後にディレクトリを作成するというコマンドです。次のように実行すると30秒後にaという名前のディレクトリが
作成されます。

$ ./delaymkdir.sh a

しかしコマンドが終了するまで何も出来ないまま30秒も待っていなければならないというのは苦痛です。
コマンド実行時に末尾に「&」を付けておくとジョブをバックグラウンドで動かせるのでジョブ実行中も作業を行うことが出来ます

$ ./delaymkdir.sh b &
[1] 13634

$

とはいえ実際に動かしてみないとコマンドの実行時間が分からないということも往々にしてあります。ジョブ実行中にC-zを入力すると、そのようなすでにフォアグラウンドで起動しているジョブをバックグラウンドに送ることができます。

$ ./delaymkdir.sh c

[C-z]
[1]+  Stopped                 ./delaymkdir.sh c

$

実はこの時点ではジョブはバックグラウンドで動いているわけではありません。Stoppedと出力されているように停止(サスペンド)されているだけでこのまま待っていてもディレクトリが作成されることはありません。バックグラウンドで処理を続行するにはbgというコマンドを入力する必要があります。逆に(再び)フォアグラウンドに持ってくる場合はfgを使います。

$ bg
[1]+ ./delaymkdir.sh c &

$ fg
./delaymkdir.sh c

それぞれの関係を下図にまとめました。