今回も bash を活用し, コンピュータを更に効果的に操作するための技を修得しよう.
作業の前に,bash のマニュアルを開いておこう:
シェルの変数は,基本的にはすべて文字列として取り扱われる. 確かめてみよう:
$ var=100 $ echo $var + 100 100 + 100 # 数値 200 ではなく,文字列 "100 + 100" $ echo $var"100" # 文字列 "100100" になる $ echo ${var}100 # 同上 $ echo $var"$var" # 同上 $ echo $var100 # var100 という名前の変数になってしまう # この変数は未定義なので,空文字列 $ echo $var'$var' # 文字列 "100$var" になる $ echo ${var}\$var # 同上 $ echo $var\$var # 同上
なお,特殊記号 $ { } " ' \ については, 前回理解したハズだ.
bash のデータ型は基本的には文字列なのだが, 数式の文字列を数値として計算することもできる.
$ echo $(( 1 + 2 )) # たし算 1 + 2 の計算結果を表示 3
たし算 + の他にどんな演算子が利用できるか? bash マニュアル の「算術式展開」および「算術式評価」 の項目を参照せよ.
ワードリスト(単語の羅列)を代入すれば, シェル変数を配列のように使うことも可能だ:
$ list=( scheme C java ) # 配列として代入 $ echo ${list[0]} # 要素0 $ echo ${list[1]} # 〃 1 $ echo ${list[2]} # 〃 2 $ echo ${list[*]} # すべての要素(ワードリスト) $ echo ${list[@]} # 〃 $ echo ${list} # 何かな?(ありがちなまちがい) $ echo $list # 〃 $ echo $list[*] # 〃
変数と配列のサイズを調べるには:
$ echo ${#list[0]} # 要素0 の文字数 $ echo ${#list[1]} # 〃1 の〃 $ echo ${#list[2]} # 〃2 の〃 $ echo ${#list[*]} # 配列の要素数
配列の添字を省略すると,どうなるか?:(ありがちなまちがい)
$ echo ${list} # 変数 list ...だが実体は配列要素 list[0] $ echo ${#list}
これで,「変数とは要素数 1 の配列である」ってことも推理できるハズだ.
配列を削除するには:
$ unset list[1] # 配列要素の削除 $ echo ${list[*]} # ↑ の確認 $ unset list # 配列全体の削除 $ echo ${list[*]} # ↑ の確認
以下,配列についてのありがちな間違いと混乱の更なる例を紹介しておく:
$ pig=boo foo woo # エラー.前回も紹介. $ pig="boo foo woo" # これは要素数1の配列(3ではない!) $ echo ${pig[0]} # ↑ の確認 $ echo ${pig[1]} # 〃 $ echo ${pig[2]} # 〃 $ echo ${#pig[*]} # 〃 $ breakfast=( "an apple" "a slice of bread" "a cup of coffee" ) # これは3要素の配列 $ echo ${breakfast[0]} # ↑ の確認 $ echo ${breakfast[1]} # 〃 $ echo ${breakfast[2]} # 〃 $ echo ${#breakfast[*]} # 〃
とにかく混乱しやすいので,充分理解できるまでは, 何か操作したら,必ず結果を確認しよう.
コマンドの実行結果をワードリストとして再利用できる:
$ file=(`\ls /bin`) # コマンド \ls /bin の実行結果が変数 file に代入される $ echo ${#file[*]} $ echo ${file[0]} $ echo ${file[1]} $ ...
ここで,記号 ` はバッククォートだ, シングルクォート ' ではない. また,\ls の \ は,前回説明の通り, ls のエイリアス置換を抑止するための指定だ.
グロブパターンと呼ばれる特殊な文字列は, 指定されたパターンにマッチするファイル名の集合を ワードリストに変換してくれる... 要するに,これを利用すると, 複数のファイルに対する処理を一度にまとめて記述できる. とても便利な機能だ.
次の一連のコマンドについては, 適当なディレクトリ(たとえば,C言語のディレクトリ)へ移動してから, 試してみよう:
$ ls Makefile ReadMe.txt boo.c foo.c woo.h $ ls *.c # .c で終わるファイル名 boo.c foo.c $ ls *.??? # 3 文字の拡張子をもつファイル名 ReadMe.txt $ echo [A-Z]* # 大文字で始まるファイル名 Makefile ReadMe.txt $ file=(*.c) # file=( boo.c foo.c ) に置換される $ echo ${#file[*]} $ echo ${file[0]} $ echo ${file[1]}
特殊パターン文字の説明については, bash マニュアルの「パターンマッチング」 の項目を参照せよ.
パス名置換を活用すれば, 複数のファイル名のすべてを明示する必要がなくなるので, 作業効率が向上する. ただし,ファイルの命名規則がお粗末だと,うまく利用できない. 今後,自作のファイル名については, パターンマッチを活用しやすくできるように, 規則を意識して行こう.
シェルコマンドはプログラミング言語としても使える. 既存のコマンドを組み合わせて,新しいコマンドを作り出せる.
まず,List 1 のようなスクリプトファイルを作ってみよう. これは,簡単な入出力の例だ.
#!/bin/bash # ↑ 1行目は特別なコメント行なので省略不可,他のコメントは省略可 # あいさつスクリプト 標準入力版 echo -n "Enter your name > " # echo -n:改行せずに表示 read name # 標準入力を変数 name に代入 echo # 改行を表示 echo "Hello, ${name}." echo "I am ${BASH}." # 末尾に必ず空行
記号 # はコメントの開始であり,行末までは無視される. (削除してよい.)
ただし,1 行目のコメントだけは, このスクリプトを実行するために必要なものだ. (削除・変更しないこと.) このスクリプトがシェルプログラム /bin/bash によって 解釈・実行されることを指定している.
そして,スクリプトの最後の行では必ず改行すること. 改行しないと,最後のコマンドが実行されない場合がある.
スクリプトを実行するには,まず, ファイルに実行可能な属性を与えておく必要がある:
$ chmod +x hello1.bash
では,実行しよう:
$ ./hello1.bash Enter your name > Kitty Hello, Kitty. I am /bin/bash.
次は,コマンドライン引数を使ってみよう. コマンドライン引数の値は,位置パラメータ $0,$1,…,$9 として利用できる. List 1 を改造して List 2 のスクリプトを作成しよう.
#!/bin/bash # あいさつスクリプト コマンドライン引数版 name=$1 # コマンドライン引数を name に代入 echo "Hello, ${name}." echo "I am ${BASH}."
では,実行属性を与えてから,実行してみよう. もちろん,実行時には引数を与えること:
... $ ./hello2.bash Kitty Hello, Kitty. I am /bin/bash.
bash には, C言語と同様な制御構造 if,for,while がある. ただし,Cとは違うところもあるので注意が必要だ. マニュアルの「シェルの組み込みコマンド」,「条件式」 等を参照せよ.
List 2 では,引数なしで実行した場合の動作が変だ. コマンドライン引数 $1 を使う前に, 次のエラーチェック処理を追加しよう:
... if [ $# -lt 1 ] # if ( argc < 1 ) then # { echo "引数が必要です" exit 1 fi # } ...
条件式に使われる論理演算子は,Cとはかなり異なる. マニュアルの「条件式」 に説明がある. (ちなみに,-lt は“less than”,つまり, 不等号「<」を意味する.)
その他の条件式についても試してみよう. List 3 を作成しよう.
#!/bin/bash # ファイルの種類の判定 ... # コマンドライン引数のチェック file=$1 echo -n "$file は" if [ -f $file ]; then echo "ファイルです."; fi if [ -d $file ]; then echo "ディレクトリです."; fi if [ ! -e $file ]; then echo "存在しません."; fi exit 0
実行例:
$ ./file.bash file.bash file.bash はファイルです. $ ./file.bash /usr/bin /usr/bin はディレクトリです. $ ./file.bash notexist.txt notexist.txt は存在しません.
ワードリスト中の各要素について反復処理するために, 非常に便利な制御構造 for-in文がある. マニュアルの「シェルの組み込みコマンド」 を参照せよ.
定数列に対して繰り返す例:
$ sum=0 $ for i in 10 20 30 40 # i = 10, 20, 30, 40 について繰り返す > do # { > sum=$(( $sum + $i )) # sum = sum + i > done # } $ echo $sum # 10 + 20 + 30 + 40 の計算結果
複数のファイルに対して繰り返す例:
$ for file in * > do > echo -n "$file は " > if [ -f $file ]; then echo "ファイルです"; fi > if [ -d $file ]; then echo "ディレクトリです"; fi > done
List 4 は,複数のファイルに対して, バックアップコピーを作成するためのシェルスクリプトの例だ. コピーのファイル名には,元のファイル名に拡張子 .org が追加される.
#!/bin/bash # 引数に指定されたファイルのバックアップコピーを作る for file in $* do # if [ ! -e $file ]; then continue; fi # 存在しないファイルを無視 echo "$file -> $file.org" \cp $file $file.org done
なお,$* はコマンドライン引数の羅列 $1 $2 $3 ... に展開される.
このスクリプトの使い方:
$ ls *.c boo.c foo.c $ ./backup.bash *.c boo.c -> boo.c.org foo.c -> foo.c.org $ ./backup.bash woo.c # 存在しないファイルの場合 cp: cannot stat `poo.c': そのようなファイルやディレクトリはありません # エラー # if 文を有効化(行頭の「#」を削除)すると... $ ./backup.bash woo.c # エラーを回避 $ ls *.c* boo.c boo.c.org foo.c foo.c.org
複数のテキストファイルを連結するためのシェルスクリプト mycat.bash を作成せよ. ただし,各ファイルの先頭に区切り文字列(ファイル名と記号列)を挿入すること. また,存在しないファイルが指定された場合,無視すること.
$ ls *.txt # 適当にテキストファイルを用意しておくこと dessert.txt drink.txt fluit.txt $ cat dessert.txt icecream pie pudding $ cat drink.txt cocoa coffee tea $ cat fluit.txt apple banana grape $ ./mycat.bash dummy.txt drink.txt fluit.txt notexist.txt dessert.txt ******************** drink.txt ******************** cocoa coffee tea ******************** fluit.txt ******************** apple banana grape ******************** dessert.txt ******************** icecream pie pudding
内部コマンド for-in と 外部コマンド cat を利用すれば簡単.
問題の意味がわからなければ, 通常の cat コマンドによる連結結果と比較してみよう.