配付資料

計算機科学概論 シェルスクリプト (2015 年 10 月 23 日)
山本 光晴
1
Bourne シェル
この資料では C シェルのプロンプトを「% 」で表し、 Bourne シェル (以下 sh) のプロンプトを「$ 」で表すこと
にする。「> 」は継続行があるときの 2 次プロンプトである。 sh を起動するには、 C シェルのプロンプトから「% sh」。
終了するときは「$ exit」。
コマンドの実行
コマンドの起動・リダイレクション・パイプは C シェルとほぼ同様。ただし、 sh の実装によっては
• ジョブ制御やヒストリ置換 (「% !!」など) がない。
• 「~」はホームディレクトリの絶対パス名に展開されない。 ($HOME を用いる。)
ということもある1 。また、「$ 環境変数=値 . . . コマンド名 引数 . . . 」という形式がある。
$ LANG=C PAGER=cat man ls
$ echo $PAGER
変数置換
変数への代入は「変数名=値」。「=」の周辺に空白を入れてはならない。「$変数名」や「${変数名}」がその変
数の値で置き換わる。
$ foo=FOO; foobar=FOOBAR; echo $foobar ${foo}bar
変数を環境変数として扱いたい (シェルから実行されるコマンドに変数を引き継ぎたい) ときは「$ export 変数
名」を実行する。
コマンド置換
「‘コマンド‘」がコマンドの出力結果で置き換わる。出力結果の末尾の連続する改行は取り除かれる。
$ expr 2 + 3
$ echo 2 + 3 = ‘expr 2 + 3‘
sh の扱うデータは文字列のみ
→数値を扱うとき (数値演算・比較) は、外部コマンドの力を借りなければならない。
終了ステータス
コマンドが正常に終了したかどうかを表す値。 0 が正常終了、 0 以外が異常終了。「$?」は最後に実行した (バッ
クグラウンドでない) コマンドの終了ステータスに置換される。コマンドによっては 0 が真、 0 以外が偽を表す。
$
$
$
$
test 2 -lt 3; echo $?
[ 2 -gt 3 ]; echo $?
foo=2; bar=3; test $foo -lt $bar && echo "$foo < $bar"
foo=2; bar=3; [ $foo -gt $bar ] || echo "not \c"; echo "$foo > $bar"
上で、「[」はコマンド名、「]」は引数の一つなので、そのように解釈されるように適宜空白を入れる。
制御構造
for
if
与えられた語の列に関する繰り返し。 while, until 条件による繰り返し。
条件による分岐。
case
パターンマッチによる分岐。
(条件. . . コマンドの終了ステータスが 0(真) か 0 以外 (偽) か。)
一般形: ([ . . . ] は省略可、「;」は 1 個以上の改行で置き換え可能)
• for NAME [ in WORDS . . . ] ; do COMMANDS ; done
• while TEST-COMMANDS ; do CONSEQUENT-COMMANDS ; done
1 Mac
OS X の sh は実は bash(Bourne Again SHell) なので、上記のことはできてしまう。
6
• until TEST-COMMANDS ; do CONSEQUENT-COMMANDS ; done
• if TEST-COMMANDS ; then CONSEQUENT-COMMANDS ; (次行に続く)
[ elif MORE-TEST-COMMANDS ; then MORE-CONSEQUENTS ;] . . . (次行に続く)
[ else ALTERNATE-CONSEQUENTS ;] fi
• case WORD in [ PATTERN [ |PATTERN ] . . . ) COMMANDS ;;] . . . esac
$ for f in apples bananas cherries
> do
>
echo "I like $f."
> done
$
$
$
>
>
$
x=100; y=24
if [ $x -gt $y ]; then w=$x; x=$y; y=$w; fi
while [ $x -ne 0 ]; do
w=‘expr $y % $x‘; y=$x; x=$w
done
echo $y
$
$
>
>
>
>
>
>
>
$
month=2; year=2000
case $month in
2) case ‘expr $year % 4‘,‘expr $year % 100‘,‘expr $year % 400‘ in
0,0,0) ndays=29 ;; 0,0,*) ndays=28 ;;
0,*)
ndays=29 ;; *)
ndays=28 ;;
esac ;;
[469]|11)
ndays=30 ;;
[13578]|1[02]) ndays=31 ;;
esac
echo $ndays
変数置換・コマンド置換とブランクの解釈・ファイル名の生成
変数置換 ($変数名) とコマンド置換 (‘コマンド‘) においては、それぞれの置換の後に
• 空白や改行 (ブランク) による語への分割。
• 「*」や「?」や「[. . . ]」によるファイル名の生成。
が行われる2 。ただし、「". . . "」の内部ではブランクの解釈・ファイル名の生成は行われない3 。
$
$
$
$
$
$
2
foo="a
b *"
set | grep foo
echo $foo
echo "$foo"
echo ‘ls‘
echo "‘ls‘"
準備
1. 自分のホームディレクトリにbinというディレクトリを作成し、~gmituhar/math/lecture/cs/bin/showargs
を~/bin にコピーする。あるいは~/bin/showargs から~gmituhar/math/lecture/cs/bin/showargs にシ
ンボリックリンクを張ってもよい。
2. ~/.cshrc の「source /g/admin/etc/Cshrc」の次の行に、以下の内容を付け加える。
set path = ( ~/bin $path )
3. % source ~/.cshrc を実行する。
2 代入の右辺や、
case ∼ in の「∼」部分では二重引用符の有無に関わらず行われない (語への分割に意味がないため)。
sh の後継シェルではコマンド置換を「$(. . . )」と書けるものもある。
3 このように変数置換とコマンド置換では類似点が数多くあるため、
7
これにより、~/bin がコマンドの検索パスに加わり、このディレクトリに置いた実行ファイルがコマンド名のみで
(絶対パス名を指定しなくとも) 実行可能となる。 (次回のログインからは特別な操作は不要。)
注) C シェルはコマンドを検索パスから探すときに毎回ファイルの検索を行うのではなく、効率を上げるために検
索結果を保存しておいて、それを利用するようになっている。そのため、~/bin に新しく置いた実行ファイルはうま
く実行できないかもしれない。そのような場合は、% rehash というコマンドを実行する。
3
シェルスクリプト
シェルに与えるコマンドの列を記述したもの。最初の行を「#!/bin/sh」とし、さらにファイルを実行可能にして
おけば、 C シェルなどの他のシェルからも実行できる。例えば、次のような場合に利用される。
• プログラミング言語を持ち出すまでもない作業。
• 決まりきった手順の作業。
• ちょっとした前処理をしてからのコマンドの実行。
• コマンドを組み合わせて使う場合。
• 出来合いのコマンドを自分が使いやすいように変更する場合。
• 通常のプログラミング言語が使用できないとき。
引数の処理
シェルスクリプトに与えられた引数は、「$n」 (n=1 ∼ 9) で、最初の 9 個まで参照できる。「$0」はコマンドの
パス名に、「$#」は引数の数に置換される。
「$*」や「$@」は「$1 $2 . . . 」、「"$*"」は 「"$1 $2 . . . "」、「"$@"」は 「"$1" "$2" . . . 」と同様にそれ
ぞれ置換される。
argstest:
#!/bin/sh
echo ’$*’;
echo ’$@’;
echo ’"$*"’;
echo ’"$@"’;
add:
#!/bin/sh
sum=‘expr $1 + $2‘
echo "$1 + $2 = $sum"
$ ./add 2 3
showargs
showargs
showargs
showargs
$ ./argstest "a b" ’c
$*;
echo
$@;
echo
"$*"; echo
"$@"
d *’
「set」を使えば、直接$1, $2, . . . に値を設定することも可能。 C シェルの「set」とは違うので注意。
$ set first second third
$ echo $# $3 $2 $1
オプションの処理
「shift」で実行前の$2 の内容が実行後の$1 に, 実行前の$3 の内容が実行後の$2 に, . . . と、$n の内容が一つず
つずれる。 (実行前の 10 番目の引数が実行後の$9 になる。) $# の値も 1 つ減る。
greet:
#!/bin/sh
msg=Hello
case $1 in
-m) msg="Good morning";
-a) msg="Good afternoon";
-e) msg="Good evening";
-n) msg="Good night";
-g) msg=$2;
shift;
esac
name=${1:-‘whoami‘}
echo "$msg, $name."
$
$
$
$
shift
shift
shift
shift
shift
;;
;;
;;
;;
;;
./greet
./greet "Mr. Monkey"
./greet -m everybody
./greet -g "Ciao"
8
makebak
makebak: (参考書)
#!/bin/sh
for f in $*
do
cp $f $f.bak
done
makebak: (クオートを使用)
#!/bin/sh
for f in "$@"
do
cp "$f" "$f.bak"
done
(以下を実行する前に、makebak中の「cp . . . 」を「echo cp . . . 」に変更しておき、 2 つのmakebakの違いを確認
すること。)
$ ./makebak "a b" ’c
d *’
ファイルがシェルスクリプトであるかどうかは、fileコマンドでわかる。/usr/local/bin, /opt/local/bin,
/usr/bin などにあるファイルからシェルスクリプトを探し、どのような処理を行っているかを見てみよ。
4
シェルが扱うデータ
Pascal が整数、実数、配列、レコードなどの多様なデータを扱うことができるのに対して、 Bourne シェルが直接
扱うことのできるデータは文字列のみである。また、文字列に対して Bourne シェルが行うことのできる操作は基本
的には連結 (単に並べるだけ) とcaseによるパターンマッチのみである。「23」という文字列を数値 23 とみなしてそ
れに 1 を加える、などという操作はexprなどの外部のコマンドの力を借りなければできない。
5
シェルスクリプトの作成・デバッグのヒント
• 最初は#!/bin/sh のところを#!/bin/sh -n にしておく。
→ コマンドの実行はされないが構文の誤りは検出される。
• コマンドの実行をするところはechoを付けておく。
→ コマンドの実行はされないがどのコマンドが実行されるかが出力される。
• オンラインマニュアル (manコマンド) を活用する。
特にtest, expr, sh
6
問題
問題 2
showargsと同じ働きをするシェルスクリプトshowargs.shを作成せよ。 (もちろん、showargsの存在を
仮定してはならない。) 引数を 10 個以上与えた場合や、「% ./showargs.sh "a b" ’c
d *’」の
ように実行した場合にもshowargsと同じ動きをするように作ること。
問題 3a
パス名の列を引数とし、各々のパス名が通常のファイルを表すパス名ならば読み出し許可を、ディレク
トリを表すパス名ならば読み出し許可と実行許可 (検索許可) を付与するコマンドmakereadableをシェ
ルスクリプトで作成せよ。許可が与えられる対象を-u(所有者)、-g (グループ)、-o(他人)、-a(全て) と
いうオプションで指定できる (「-u -g」などのように複数指定可) ようにすること。オプションを指定
しなかった場合は「chmod +r ファイル名」や「chmod +rx ディレクトリ名」と同じ動きをするものと
する。オプションの解析はプリントのようにcaseを用いてもよいし、 sh の組み込みコマンドのgetoptsを
用いてもよい。getoptsを使用する場合は検索するなどして自分で調べよ。なお、chmodの+Xオプショ
ン (大文字) は用いてはならない。
ヒント: testコマンド
問題 3b
(選択問題: 必ずしもやらなくてもよい) 3a のプログラムに、 3a と同じ操作をディレクトリに対して再
帰的に行うオプション「-R」を付加せよ。なお、「.」で始まるファイル名・ディレクトリ名については
操作の対象にしてもしなくてもよい。 (操作の対象にする場合は「.」と「..」の取り扱いに十分注意す
ること。)
問題 3b のコマンドは、例えば~/public_html 以下のファイルを全て他の人にも読めるようにしたい場合に便利で
ある。 (ただし、本来はこのような作業はfindコマンドを使用して行う。)
% makereadable -a -R ~/public_html
提出期限・場所
問題 2,3 の提出期限を 11 月 30 日 (月) 17:00 とする。
提出場所は、理学部 2 号館 5F 510 前のレポート提出箱。
9