シェル

シェルそのものもインタプリタであり,制御構造や変数を持つ。

シェル変数

変数定義は

変数=

の形式で行なう。イコールの前後に空白は許されない。 シェル変数は慣習的に小文字。定義したシェルのみが持つ。

定義したシェル変数の値の参照は

$変数
${変数}

いずれかで行なう。単語区切りが明確なときのみ { } は不要。

シェル関数

一連の手続をまとめて関数化できる。シェル関数の定義は

関数名() {
  …定義本体…
}

の形式で行なう。関数への引数は $1, $2, $3, ... で受け取る。$* はすべての引数を表す。

# 定義する
foo() {
 echo 123: $1 $2 $3
 echo all: $*
}
# 呼び出す
foo a b c d e f g
123: a b c
all: a b c d e f g

グロッビング

ファイル名に対するパターンマッチングをグロッビングという。 正規表現とは規則が違う。

?

任意の1字にマッチ

*

0字以上の任意の文字列にマッチ

[文字クラス]

文字クラス のいずれかに1字にマッチ。

zsh拡張として次のパターンも使える。

**/

ディレクトリを再帰的に検索

<整数1-整数2>

数値的に整数1以上 整数2以下の文字列にマッチする。 上限のみ,下限のみでもよい。たとえば,カレントディレクトリに 15個のファイル,file1.rb file2.rb ... file15.rb がある場合,

file<4-12>.rb

は,file4.rb file5.rb ... file12.rb の9個のファイル

file<-9>.rb

は,file1.rb file2.rb ... file9.rb の9個のファイル

file<8->.rb

は,file8.rb file9.rb ... file15.rb の8個のファイル にマッチする。

もちろん存在しないファイル名は出てこない。

パターン1~パターン2

パターン1にマッチするものから, パターン2にマッチするものを除外する。 たとえば,上述の file1.rb file2.rb ... file15.rb の15個のファイルがある場合,

file??.*~*15*

と指定すると,"file" の後ろに任意の2字が来て, そのあとピリオドと任意文字列が来るファイル名すべてをまず選び, そこから途中に "15" という文字列が現れるものを 除外するので file10.rb file11.rb file12.rb file13.rb file14.rb がマッチする。

コマンド置換

`cmdline`(バッククォート)

Rubyのバッククォート 同様,内部のコマンド cmdline を起動した結果の文字列に置換される。

$(cmdline)

バッククォートと同様,コマンドを起動した結果の文字列に 置換される。バッククォートと違いネスト可能。

自分が差出人のメイルから,送信先となっている頻度が 一番多い人にメイルを送る手順を例に示す。

cd ~/Mail/inbox
grep -l "Return-Path:.*$USER" *
(マッチするファイル名一覧が出る)
grep '^To:' $(grep -l "Return-Path.*$USER" *)
(自分が差出人のファイルから To: ヘッダの行を抽出)
grep '^To:' $(grep -l "Return-Path.*$USER" *) | \
  ruby -pe 'sub(/.*[ <](.+@[^>]*)>?/,"\\1")'
(送信先のアドレス一覧が出る)
grep '^To:' $(grep -l "Return-Path.*$USER" *) | \
  ruby -pe 'sub(/.*[ <](.+@[^>]*)>?/,"\\1")' | \
  sort | uniq -c | sort -nr | head -1 | awk '{print $2}'
(頻度1位の人のアドレスが出る)
echo 1位です! | nkf -j | \
  Mail -s congratulations $(grep '^To:' $(grep -l "Return-Path.*$USER" *) | \
  ruby -pe 'sub(/.*[ <](.+@[^>]*)>?/,"\\1")' | \
  sort | uniq -c | sort -nr | head -1 | awk '{print $2}')

ブレース展開

中括弧 { } の内部にカンマで区切った文字列を列挙すると それらを開いた文字列に展開する。前後に文字列をつけると それらと結合した上で展開する。

echo {a,b,c}
a b c
echo file-{a,b,c}
file-a file-b file-c
echo hoge.rb{,bak}
hoge.rb hoge.rb.bak
echo cp Ruby/kadai1/hogehoge.rb{,.bak}
cp Ruby/kadai1/hogehoge.rb Ruby/kadai1/hogehoge.rb.bak

zsh拡張として数値範囲を指定した展開ができる。

echo {1..20}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
echo file{1..20}
file1 file2 file3 file4 file5 file6 file7 file8 file9 file10 file11
file12 file13 file14 file15 file16 file17 file18 file19 file20
(空のファイルを作るためにtouchコマンドを用いる)
touch file{1..999}
(lsで確認後消去)
ls
rm file{1..999}

制御構造

あるコマンドの実行が成功したとき(終了値0のとき),失敗したとき で分岐したり,繰り返し処理を行なったりできる。

cmdline1 && cmdline2

cmdline1 の実行が成功したときのみ cmdline2 を実行する。

cd
touch myprogram.rb && chmod +x myprogram.rb
(ホームディレクトリには作れるので chmod +x myprogram.rb される)
rm myprogram.rb
touch /myprogram.rb && chmod +x /myprogram.rb
(ルートディレクトリには作れないので chmod されない)
cmdline1 || cmdline2

cmdline1 の実行が失敗したときのみ cmdline2 を実行する。

if cmdline1; then 〜 elif cmdline2; then 〜 else 〜 fi

cmdline1 の実行が成功したときに thenに続くブロックを評価する。 いずれのコマンドも失敗したときは else ブロックを評価する。

#!/bin/sh
if touch hogehoge.rb
then
  echo ./hogehoge.rb 作れました
elif touch /tmp/hogehoge.rb
  echo /tmp/hogehoge.rb 作れました
else
  echo 失敗しました。やめ。
  exit
fi

コマンドの部分には条件判断を行なうだけのコマンドを 使うことが多い。典型的には /bin/test コマンドで これはファイルの存在を確かめたりできる。Rubyの test関数は 外部コマンド testに由来する。

#!/bin/sh
if /bin/test -f hogehoge.rb
then
  echo hogehoge.rb ファイル,あります。
else
  echo hogehoge.rb ファイル,ありません。
fi

条件式に見えやすいよう,test コマンドには [ という名前のハードリンクが張られているので, これを用いて書き換えると以下のようになる。

#!/bin/sh
if [ -f hogehoge.rb ]; then
  echo hogehoge.rb ファイル,あります。
else
  echo hogehoge.rb ファイル,ありません。
fi

test の代わりに [ で 呼び出したときは行の終わりに ] を付ける。

while cmdline; do 〜; done

cmdline が成功を返す間ブロックを評価し続ける。 cmdline の部分には test コマンドや, 常に成功する /bin/true コマンドを用いることが多い。

#!/bin/sh
while true; do
  echo 誰か止めて〜
  sleep 1
done
for 変数 in 語群; do 〜 ; done

語群 (を展開した結果)を先頭要素から順に 変数 に代入しつつブロックを繰り返す。

次の例はカレントディレクトリに含まれる *.rb ファイルすべてのバックアップファイルを作成する。

for f in *.rb; do
cp $f $f.bak
done

入出力

シェルは標準入出力を処理するフィルタとしても振る舞える。 シェルが直接標準入力を読むには read、 標準出力に送るには echo を用いる。

read 変数 ...

標準入力から1行読み込み、変数 に代入する。 変数を2個以上指定した場合は空白文字でフィールド分割した 各フィールドを先頭の変数から順次代入する。フィールドの方が 多い場合は最後の変数に残りすべてが代入される。

read x
foo bar baz
x -> "foo bar baz"
read x y
foo bar baz
x -> "foo"
y -> "bar baz"
read x y z
foo bar baz
x -> "foo"
y -> "bar"
z -> "baz"

フィールド区切りを空白以外に変更するにはシェル変数 IFS に区切り文字を列挙する。CSVファイルを 読み取って処理する例を示す。

score.csv

山田太郎,50,70,20
公益太郎,90,80,70
飯森花子,91,79,72
鶴岡一人,60,60,40
酒田三吉,52,70,80
三川一二三,12,34,99
cat score.csv | while IFS=, read name math eng jp
do
  sum=`expr $math + $eng + $jp`
  echo $name さんは合計 $sum 点です。
  if [ $sum -lt 200 ]; then
    echo "*** $name さんは赤点です!! ***"
  fi
done


目次