01 月 11 日(木)

シェル

シェルの基本的な機能について理解し,活用しよう. シェルを使いこなせば, 複雑な操作や大量の操作を 楽々と実行できるようになる.

今回は,おぼえるべき事項が多くあるが, 後で大いに楽するために,ちょっとだけ頑張っておこう.

操作効率

ユーザがほんの少し操作するだけで, コンピュータに多くの仕事を指示できる場合, 「操作効率が高い」という. シェルの威力を実感してみよう.

準備作業

問題

ファイルの表示順序がなんだか変なので, 次のように,ファイル名を修正したい:

$  ls
01.txt  02.txt  03.txt ... 09.txt 10.txt

どうすればよいか?

素人の解決策

これは一般人でも,ファイルマネージャ (Wind◯ws の「エクスプローラ」や Mac◯S☓ の「ファインダ」等)で GUI 操作「名前の変更」を 何度もひたすら繰り返せばできますね? でも,進んでやりたがる人はいますか?

または,この授業に参加している学生ならば, 文字端末(Linux の「端末」や MacOSX の「ターミナル」等)で Unix コマンド「mv」を 何度もひたすら繰り返せばできますね? しかし,実行しないこと!!

$  mv  1.txt  01.txt
$  mv  2.txt  02.txt
$  mv  3.txt  03.txt
...
$  mv  9.txt  09.txt

...こんな方法だと, 「ユーザがコンピュータに使われている」 ようなものだ.

まあ,この問題ではファイルは10個しかないから, 奴隷とまでは思えないかもしれないが, 1,000個とか100,000,000個とかだったらどうするんだ?

要するに,とても面倒だし時間もかかるので, もっと楽に素早く済ませるべきだ.

玄人の解決策

コンピュータにコマンド「mv」を繰り返させる. シェル「bash」の場合:

$  for  i  in  ?.txt
>  do
>    mv  $i  0$i
>  done

または,一行で:

$  for  i  in  ?.txt ; do  mv  $i  0$i ; done

これなら, 「ユーザがコンピュータを使っている」 と言えるだろう.

コマンドラインが複雑になってはいるが, もしファイル数が増えたとしても, ユーザの手間はほとんど変わらない. 要するに,とても効率的.

なお,「for」や「?」等の意味については,後日説明予定. 今は気にしなくてよい.


シェルの概要

OS とユーザとのインタフェース(仲介)として働くプログラムが シェル(shell)である. プログラムを実行したり, そのプログラムの入出力ファイルを指定したりするとき, ユーザからの命令は,まず,シェルによって解釈され, シェルが OS を制御する. ユーザが OS を直接に制御しているわけではないのだ.

前期のプログラミング言語II でも 少しだけ説明済み

Unix では,操作方法や機能の異なるさまざまな種類のシェルを利用できる. bashtcshzsh,等が有名. 本科目では,bash(Bourne-again shell;ばっしゅ)を利用する.

最近のシェルは,特に操作効率を向上するために, 便利な機能をたくさんもっている. シェルの種類によって操作方法や機能は異なるが, ほとんどのシェルには,次のような機能がある:

これらの機能の一部については,諸君はすでに, 当然のことのように利用してきているハズだ. (へー,これってシェルの機能だったんだー.)

bash の活用

準備

まず,端末内で動作中のシェルが何であるか確認しよう:

$  ps  $$
  PID TTY      STAT   TIME COMMAND
 3634 pts/0    Ss     0:00 -bash

もし,別のシェルだった場合bash を起動:

$  bash				# bash の起動
記号「#」から行末までは,コメントなので入力不要. ちなみに,シェルの終了コマンドは,exit だが..., 今は終了しないこと. この実験では,すべての作業を bash で実行する.

bash が起動されている場合, 特別なシェル変数 BASH および BASH_VERSION に, シェルのプログラムファイル名およびバージョン名が, それぞれセットされているハズだ. これらの変数の内容を表示してみよう:

$  echo  $BASH			# プログラムファイル名の確認
/bin/bash

$  echo  $BASH_VERSION		# バージョン名の確認
4.1.2(1)-release		# (異なっていても気にしないでよい)

ここで,「echo」は文字列を標準出力するための組み込みコマンドだ. また,「$」は変数を展開する(変数から内容を取り出す)ための演算子だ.

補完

補完機能を試してみよう:

$  ech[Tab]  $B[Tab]_V[Tab]O[Tab]	# 長ったらしいコマンド echo $BASH_VERSION が少ない打鍵数で完成!
[Tab] のところでは [Tab] キーを打つこと.

コマンド名,変数名の他,ファイル名も補完できる. 長いコマンドラインを入力するとき,この機能は非常に便利だ. 今後,最大限に活用しよう.

シェル変数,文字列,コマンド履歴

他のシェル変数も試してみよう. なお,シェルの変数には,C言語にあったような型の違いはない. シェル変数は,すべて文字列型である. ここでは,文字列の区切りやクォートの規則をおぼえよう.

なお,文字列を表わす記号「"」について, シェルでは,省略できる場合が多いが, 省略できない場合もあり,紛らわしい. 注意しよう.

まず,変数への文字列(単語)の代入:

$  tmp=hoge		# 代入では,'=' の前後の空白は NG
$  echo  tmp = $tmp
tmp = hoge
ここで「変数名」と「$変数名」との違いに注意せよ.

以下,同じようなコマンドの繰り返しなので, 上下カーソルキーなどで履歴を活用し, 能率的に入力しよう.

続いて,空白入りの文字列を代入してみよう:

$  tmp=hoge hoge	# これは間違い
-bash: hoge: command not found

$  tmp="hoge hoge"	# これが正解
$  echo tmp = $tmp
tmp = hoge hoge

$  tmp='hoge hoge'	# これも正解
$  echo tmp = $tmp
tmp = hoge hoge

次は,変数の展開方法のバリエーション:

$  echo  tmp = $tmp	# 変数を展開して表示
$  echo  tmp = ${tmp}
$  echo  "tmp = $tmp"

$  echo  'tmp = $tmp'	# 変数を展開しない
$  echo  tmp = \$tmp

各自で実行結果を比較してみよう. 記号 $ { } " ' \ の意味がわかっただろうか?

特に,エスケープ文字 \ は, シェルの様々な機能を無効化するために,よく利用される. (あー,C言語の文字列でも同様だったなー.) 上の例では,演算子 $ を無効化してしている.

特殊文字について,いろいろ試してみよう:

$  echo  "	# 終わらない → もう一個 " を入力
$  echo  '	#   〃   → もう一個 ' を入力
$  echo  \	#   〃   → もう一個 [Enter] を入力

$  echo  \"
$  echo  \'
$  echo  \\

$  echo  "\"
$  echo  '\'

$  echo  "'"
$  echo  '"'

$  echo  "\'"
$  echo  '\"'
$  echo  "\""
$  echo  '\''

さらに深く,記号 " ' \ の意味がわかっただろうか?

エイリアス置換,設定ファイル

既存のコマンドやコマンドの組み合わせに対して, 自由にエイリアス(別名)を定義できる. よく使う長いコマンドを短くエイリアスしておくと便利だ.

Bash のエイリアスは,Cでのマクロに相当.

たとえば,以前の実験で, 次のような長ったらしいコマンドを何度も使っていた:

$  cc -Wall -lncurse -lm  game.c
$  cc -Wall -lncurse -lm  game.c  -o game

こんな場合には,次のようにエイリアス定義:

$  alias ccc='cc -Wall -lncurses -lm'

しておけば,ちょっと短いコマンドライン:

$  ccc game.c		# cc -Wall -lncurses -lm  game.c  が実行される
$  ccc game.c -o game	# cc -Wall -lncurses -lm  game.c  -o game

...だけで済む訳だ.

既存のコマンドと同じ名前のエイリアスも定義できる. これは,あるコマンドでは,いつでも同じオプションを使いたい, というような場合に便利だ.

たとえば,ls コマンドの本来の標準動作では:

$  ls
a.out    source.c    subdir

これだと結果が非常にわかりづらいので, 標準動作を次のように変更したい: (すでに設定されているかもしれない.)


$  alias ls='ls -F --color'
$  ls		# 本来の ls ではなく,エイリアス ls -F --color が実行される
a.out*   source.c   subdir/		# わかりやすーい


$  \ls		# エイリアスの ls ではなく,本来の ls を実行
a.out    source.c    subdir		# わかりづらっ

$  /bin/ls		# これでも本来の ls を実行
...
ここでも,エスケープ記号 \ が出てきた.

エイリアスや変数の設定は,シェルを終了する(端末を閉じる)と消滅してしまうが, 毎度毎度,シェルを起動するたびに手動で設定するのは面倒だ. そこで,設定ファイル(初期化ファイル) ~/.bash_profile および ~/.bashrc を利用しよう. これらのファイルにコマンドを記述しておくと, シェル起動時に自動的に設定されることになる.

~/.bash_profile の内容:(ログインシェル起動時実行したい設定)
source  ~/.bashrc		# ~/.bashrc を実行
または
.  ~/.bashrc		# ~/.bashrc を実行

~/.bashrc の内容:(サブシェル起動時に実行したい設定)

alias ccc='cc -Wall -lncurses -lm'
alias ls='ls -F --color'
...

これで,次回のシェル起動時から,これらのエイリアスが有効になる.

いちいち,シェルを起動しなおすのが面倒なときは, 次のようにすればよい:

$  source  ~/.bashrc
または
$  .  ~/.bashrc

すでに設定されているエイリアスを確認するには:

$  alias		# すべてのエイリアスを確認
alias ccc=...
alias ls=...
...

$  alias ls		# ls を確認
alias ls=...

エイリアスを解除するには:

$  unalias ls		# エイリアス解除

$  alias ls		# 確認
bash: alias: ls: not found


関数

bash の関数はエイリアスの強化版であり, 引数付きのコマンドを定義できる. 例えば:

$  function  backup
>  {
>     cp $1 $1.org
>  }

ここで $1 は最初の引数を意味する特殊な変数である.

$  echo "hello world" > hello.txt

$  ls
hello.txt

$  cat hello.txt
hello world

$  backup hello.txt
cp hello.txt hello.txt.org

$  ls
hello.txt hello.txt.org

$  cat hello.txt.org
hello world

なお,関数についても,必要に応じて, 設定ファイル ~/.bashrc に記述しておくとよい.

履歴置換,コマンド行編集

過去のコマンドを再度入力するには,上下カーソルキーの他, 次のような方法もある:

$  history | less	# コマンド履歴をリスト表示
    ...
     3  echo $BASH_VERSION
    ...
    25  tmp=hoge
    ...
    50  history | less

$  !!		# 直前のコマンドへ置換 → history | less を実行

$  !3		# 3番目のコマンドへ置換 → echo $BASH_VERSION を実行

$  !-2		# 2つ前のコマンドへ置換 → 何でしょう?

$  !e		# 最近実行された e で始まるコマンドへ置換 → 何かな?

$  mkdir  totally-idiot-very-long-name-directory-you-should-not-mkdir
$  cd  !$	# 直前のコマンドの最後の引数へ置換→ totally-... を再利用
	# 再利用せず,cd  totally-... なんて再度打つのは鬱...

要するに, 履歴置換(!なんとか)によって, 過去のコマンドや引数を再利用できる. これらをうまく利用すれば, コマンドラインでのタイピング労力を大幅に節約できる. (キーを押す回数だけでなく,ミスタイプも削減できる.)

なお,上の例の最後に作ったディレクトリを消すには, 次のようなコマンドラインが必要になる: (まだ,実行しないこと!!

$  cd  ..
$  rmdir  totally-idiot-very-long-name-directory-you-should-not-mkdir

これを,できるだけ少ないタイプ数で実行してみよう.

まず,1行目(cd)については,文字数が少ないので直接入力が早い. 2行目(rmdir)については,次のように操作する方法がある:

もちろん,mkdir した直後であれば,次の方法が良い:

$  cd  .. ; rmdir !$

以上のように,履歴や行編集の機能をうまく利用すれば, コマンドラインの入力は非常に楽になる.

履歴をより有効に活用するには, コマンドラインの書き方やコマンドの実行順序にも注意する必要がある.

ジョブ制御

新しいウィンドウを開くようなプログラムを実行したとき, コマンドラインが使えなくなって,困った経験はないだろうか? そんなときには...:

$  kturtle		# 新規ウィンドウなアプリ(kturtle)をフォアグラウンドジョブ(表の処理)として実行

ls			# この時,シェルはコマンド入力を受け付けていない.

^Z			# [Ctrl]+[Z] → kturtle を中断

$  ls		# コマンド入力の受け付けが再開されている.
...

$  bg		# 中断中の kturtle をバックグラウンドジョブ(裏の処理)として再開
[1]    kturtle &	# kturtle のジョブ番号が1になった.
$  ls		# コマンド入力が受け付けられている.
...

$  gedit &	# 別のアプリ(gedit)をバックグラウンドで実行
	# もし gedit が無い場合,emacs など,
	# 新規ウィンドウを開くプログラムならなんでもOK
[2]    gedit &	# ジョブ2になった.

$  jobs		# ジョブの一覧を表示
[1]-  実行中                    kturtle
[2]+  実行中                    gedit

$  kill %1	# ジョブ1を終了

$  kill %2	# ジョブ2を終了

bash のマニュアル

シェルの組み込みコマンド,特殊変数,履歴,などの詳細は, オンラインマニュアルに記載されている:

$  man bash

Web で読みたければ... bash 日本語マニュアル -- JM Project


練習問題

  1. 文字列「'!!'」を表示するためのコマンドラインを書け. (引用符「'」も表示することに注意.)
  2. 履歴と行編集を使って効率よく(少ないタイプ数で) コマンドを実行する方法について研究せよ.
  3. 例:プログラミング作業で使う一連のコマンドの効率的な入力方法

    $  mkdir MmDd		# (本日の作業用ディレクトリを作れ)
    $  cd MmDd			# 直前と同じ引数
    
    $  vi Source.c		# (何かバグ入りのCプログラムを作れ)
    $  cc Source.c		# 直前と同じ引数
    コンパイルエラー
    
    $  vi Source.c			# 以前と同じコマンド
    $  cc Source.c -lncurses -Wall	# 以前と同じコマンドへの追記
    ...
    
    $  ./a.out
    
  4. 便利なエイリアスや関数の設定について研究せよ.
  5. どんなエイリアスや関数があれば便利か? 実際に作成し試用しよう.

  6. bash のマニュアルを利用して, 今回の実験内容に関連する機能について調査せよ.
  7. このウェブページに書かれていないが重要と思われる機能があるか?など


(c) 2018, yanagawa@kushiro-ct.ac.jp