satoshiabe.jp : Introduction to BASH

HOME > DOCUMENTS > PROGRAMMING > BASH > Introduction to BASH

Introduction to BASH

Updated : 2006/05/26
Created : 2006/01/19

まえがき

スクリプトを作成する

変数

flow control

builtin command

command substitution

Arrays

User-defined function

算術式展開

パラメータの展開

exit status

ヒアドキュメント

サンプルスクリプト

リンク

まえがき

ここは、bash に関する自分用の備忘録と、スクリプトに組み込める機能を紹介するページ。 sh が実装している機能と重複するため、bash でしか実行できない機能については注記する。

スクリプトを作成する

sh や bash でスクリプトを作成するには、基本的に、スクリプトで処理させたい内容を順番に書いていけばよい。 行末に ; などの文字を入力しなければならないプログラミング言語も存在するが、shell/bash においては必要ない。 基本的に、改行が区切り文字となる。 ただし、; を使用することにより、1 行に複数のコマンドを記述することもできる。

% vi alice #!/usr/local/bin/bash echo "1" echo "2" echo "3" echo "4"

; を使用した例。

% vi alice #!/usr/local/bin/bash echo "1"; echo "2"; echo "3"; echo "4"

1 行目には sh か bash への path を指定すること。 尚、*Linux では /bin/sh が /bin/bash へのシンボリックリンクとなっている。 知らず知らずのうちに bash の機能を実行していると思われる。 FreeBSD において bash への path は /usr/local/bin/bash であるため、明示的に指定しなければならない。

# shell を指定する場合 #!/bi/sh # FreeBSD で bash を指定する場合 #!/usr/local/bin/bash # *Linux で明示的に bash を指定する場合 #!/bin/bash

作成したスクリプトには、chmod コマンドで実行権限を付与してから実行する。 この後、本ページのスクリプトは、全て実行権限が付与されていることを想定する。

% chmod 755 alice % ./alice 1 2 3 4 %

変数

変数は、いわゆる「いれもの」の概念で良い。 いまいち理解できない場合、「何か情報を代入できる」と覚えておけば良いだろうか。 変数に保持されている情報は、基本的に文字列型として処理される。 使用する前に変数を宣言しなくて良い。

変数の宣言 variable= 変数の初期化 variable=/home/you

「環境変数」と呼ばれる変数もある。 HOME や PATH などが該当する。 env コマンドを実行すると、環境変数が表示される。 環境変数は、すべて大文字で構成されている。

% env REMOTEHOST=192.168.1.10 HOST=puma.example.com SHELL=/bin/bash TERM=xterm GROUP=satoshi USER=satoshi HOSTTYPE=FreeBSD PAGER=more FTP_PASSIVE_MODE=YES PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin MAIL=/var/mail/satoshi BLOCKSIZE=K PWD=/home/satoshi/ EDITOR=vi SHLVL=2 HOME=/home/satoshi OSTYPE=FreeBSD VENDOR=intel MACHTYPE=i386 LOGNAME=satoshi _=/usr/bin/env

flow control

if statement

if 文の構文は以下のとおり。

if expression then statement . . . elif expression then statement . . . else statement . . . fi

if 文を用いることにより、「ファイルが存在する場合、command を実行する。ファイルが存在しない場合、エラーを表示して終了する。」といったような処理を実現できる。

サンプルを実行してみるテスト。 $1 は、スクリプトの第 1 引数なので、サンプルでは、その第 1 引数を変数 file に代入している。 -e は、ファイルテストで、ファイルが存在すれば true となり、続く echo コマンドが実行される。

% ls hoge hoge % % cat alice #!/usr/local/bin/bash file=$1 if [ -e $file ] ; then echo "$file is here" done % % ./alice hoge hoge is here %

ファイルテストは種類が多いので公式のドキュメントで確認しておくこと。 頻繁に使用するオプションをいくつか挙げておく。

-f file file が存在しかつ通常ファイルならば真 -r file file が存在しかつ読み込み可能ならば真 -x file file が存在しかつ読み込み可能ならば真 -n string string の長さが 0 でなければ真 -z string string の長さが 0 ならば真 arg1 -eq arg2 arg1 が arg2 と等しければ真 arg1 -ne arg2 arg1 が arg2 と等しくなければ真 arg1 -lt arg2 arg1 が arg2 より小さければ真 arg1 -le arg2 arg1 が arg2 以下であれば真 arg1 -gt arg2 arg1 が arg2 より多ければ真 arg1 -ge arg2 arg1 が arg2 以上であれば真

サンプルをもうひとつ。

% cat alice #!/usr/local/bin/bash file=$1 if [ ! -e $file ] ; then echo "File does NOT exist." fi if [ ! -r $file ] ; then echo "File unreadable." fi if [ ! -x $file ] ; then echo "File unexcutable." fi % % chmod 000 file % ./alice file File unreadable. File unexcutable. %

for statement

for 文を使用したスクリプトのサンプルでは、たいてい

for name [ in word ] do statement . . . done

上記の例を見かけるが、実は下記の構文が存在することを最近まで知らなかった。 インクリメント演算子 ++ も ( とうぜんデクリメント演算子 -- も ) 存在する。

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

ひとつめの例。

% cat alice #!/usr/local/bin/bash for i in `ls *.txt` do echo "file $i is here" done %

ふたつめの例。

% cat alice #!/usr/local/bin/bash for ((i=0; i<10; i++)) ; do echo "the number is $i" done % % ./alice the number is 0 the number is 1 the number is 2 the number is 3 the number is 4 the number is 5 the number is 6 the number is 7 the number is 8 the number is 9 %

ひとつめの例をコマンドラインで実行する場合の例。 慣れてくると、わざわざスクリプトを作成しなくても実行できるようになるはず。

% for i `ls *.zip` ; do unzip $i ; done

while statement

while 文を使用したサンプル。 -lt は、less than を意味していて、下の例では、「$i が 10 より少なかったら真(true)」を確認する test コマンドだ。

% cat alice #!/bin/sh ### 変数 i を初期化する i=0 ### while ループに突入 while [ $i -lt 10 ] ; do echo $i ### i をインクリメントする i=$(($i+1)) # (($i++)) という書き方でも良い done %

テキストを処理するスクリプトを書く場合、任意のファイルを読み込んで処理することになるが、while 文と read コマンドを使用して、指定したファイルの各行を読み込める。

% cat alice #!/usr/local/bin/bash ### 変数の初期化 file=/etc/passwd ### while ループでファイルを読み込む while read line ; do echo $line done < $file %

until statement

while 文は、条件式の終了ステータスが false になるまで実行される。 until 文は、条件式の終了ステータスが true になるまで実行される。

#!/bin/sh n=0 until [ $n -ge 10 ] ; do echo $n n=$(($n + 1)) done

builtin command

exit [n]

exit コマンドを使用することにより、任意のタイミングでシェルを終了できる。 n を指定することにより、終了ステータスを指定できる。 尚、n を省略した場合、最後に実行したコマンドの終了ステータスとなる。

#!/bin/sh n=0 while true; do n=$(($n + 1)) echo "I can count to $n" if [ $n -eq 10 ] ; then exit 0 fi done

break [n]

break コマンドを実行することにより、for, while, until, select のループから脱出できる。 この break は、ループから脱出するだけで、exit のようにスクリプトを終了させたりしない。 n を指定した場合、n ブロック分だけ break する。 n を指定する場合、1 以上にすること。 ループの最中に break コマンドを実行した場合、exit status は、0 となる。

#!/bin/sh echo "hoge" n=0 while true; do n=$(($n + 1)) echo "I can count to $n" if [ $n -eq 10 ] ; then break fi done echo "fuga"

read

read コマンドを使用することにより、標準入力から入力された文字列を変数に格納できる。

read variable

read コマンドを使用することにより、標準入力から入力された文字列を変数に格納できる。

#!/usr/local/bin/bash ### メッセージを表示して echo -n "Enter some words: " ### 変数に格納 read answer ### 表示 echo -e -n "Your answer is: $answer\n"

command substitution

コマンド置換 (command substitution) を使用することにより、コマンドをコマンドの実行結果と置換できる。 ふたつの書き方が存在するが、`` は shell と bash で、$() は bash で対応している。

`command` または $(command)

コマンド置換を使用して、ls コマンドの実行結果を変数 result に代入している例。

result=`ls -l *.txt` または result=$(ls -l *.txt)

Arrays

bash では 1 次元配列を扱える。 sh は、この機能を実装していないため、配列を使用する際には、shell スクリプトの 1 行目で bash への path を指定すること。

代入

array[i]=hoge または name=(value1 value2 value3 ... valueN)

展開

${array[i]}

サンプルを実行してみるテスト。

% cat alice #!/usr/local/bin/bash ### 代入 for ((i=0; i<=9; i++)) ; do array[$i]=$(($i+100)) done ### 展開 for ((i=0; i<=9; i++)) ; do echo ${array[$i]} done % % ./sample 100 101 102 103 104 105 106 107 108 109 %

コマンドラインで実行してみた例。

% array=(hoge foga foo bar) % for ((i=0; i<${#array}; i++)) ; do echo "$i ${array[$i]}" ; done 0 hoge 1 foga 2 foo 3 bar %

少し実用的そうな例。 ふたつのファイルに入力されている各行をふたつの配列に読み込み、fileA に存在している行が fileB に存在しているかを確認するスクリプト。

#!/usr/local/bin/bash fileA=$1 fileB=$2 sort $fileA > $fileA.$$ && mv $fileA.$$ $fileA sort $fileB > $fileB.$$ && mv $fileB.$$ $fileB n=0 while read line ; do arrayA[n]=$line ((n++)) done < $fileA n=0 while read line ; do arrayB[n]=$line ((n++)) done < $fileB for ((x=0; x<${#arrayA[@]}; x++)) ; do flag=false for ((y=0; y<${#arrayB[@]}; y++)) ; do if [ ${arrayA[x]} == ${arrayB[y]} ] ; then flag=true fi done if [ $flag == "false" ] ; then echo ${arrayA[x]} fi done

User-defined function

[ function ] name () { list; }

まったく同じ処理を後で繰り返し実行する場合、関数を定義してしまう。 コードがすっきりしてメンテナンスしやすくなる。

% cat alice #!/usr/local/bin/bash ### 関数の定義 ans() { echo -n "You need one more BEER?[y/n]: " read answer } ### 関数の実行 ans ### 変数 answer の内容をチェック if [ $answer = "y" ] ; then echo "You have already gotten drunk!" fi % % ./alice You need one more BEER?[y/n]: y[Enter] You have already gotten drunk! %

算術式展開

shell/bash が扱う変数のデータ型は文字列だ。 つまり

% var=3 % echo $var 3 % echo $var + 4 3 + 4

となる。 しかし以下の構文を使用すると、算術演算することができる。

$((var + n)) または $(($var + n))

% var=3 % echo $var 3 % echo $var + 4 3 + 4 % echo $((var + 4)) 7 % echo $(($var + 4)) 7

パラメータの展開

知っていると地味に役立つ場合がある、パラメータ展開。 たとえば cut や awk を使用してセパレータを指定した文字列の切抜きを実行するときに、パラメータ展開で事足りる。

${parameter##word} ${parameter#word} ${parameter%%word} ${parameter%word}

環境変数 $PATH を使用した例。

% echo $PATH /sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/home/satoshi/bin % % echo ${PATH} /sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/home/satoshi/bin % % echo ${PATH#*/} sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/home/satoshi/bin % % echo ${PATH##*/} bin % % echo ${PATH%:*} /sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin % % echo ${PATH%%:*} /sbin %

以下はなかなか知られていない(と思う)。

% echo ${PATH} /sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/home/satoshi/bin % % echo ${PATH/\/bin/\/BIN/} /sbin:/BIN/:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/home/satoshi/bin % % echo ${PATH//\/bin/\/BIN/} /sbin:/BIN/:/usr/sbin:/usr/BIN/:/usr/games:/usr/local/sbin:/usr/local/BIN/:/usr/X11R6/BIN/:/home/satoshi/BIN/ %

exit status

shell は、コマンドを正常に終了すると終了ステータスとして 0 を、ある特殊な変数に格納する。 その変数は $? だ。 つまり、この変数 $? の結果を確認することによって、直前のコマンドが正常に終了したのか、そうでないのかを確認できる。

サンプルのスクリプトは、grep コマンドでの例。 grep コマンドでマッチングが成功すると、$? には 0 が格納されるため、その結果を test コマンドでチェックしてその後の動きを変化させる。

% cat alice #!/usr/local/bin/bash ### 変数の初期化 file=/etc/passwd ### grep コマンドを実行 cat $file | grep -i '^root' 1> /dev/null ### 終了ステータスをチェック if [ $? -eq 0 ] ; then echo "I found you..." else echo "Where are you now!?" fi % % ./alice I found you... %

ヒアドキュメント

<<[-]word here-document delimiter

ヒアドキュメントは、まとまった処理を簡単に記述できる。 word に指定する文字列をクォーティングすると、変数が展開されなくなる。

何か連続で表示したいとき echo を連続して書いても良いが...

% cat alice #/bin/sh echo "111" echo "222" echo "333" echo "444" echo "555"

ヒアドキュメントを使用するkと尾により、すっきり内容を書ける。 尚、word に指定する文字列をクォーティングすると、変数が展開されなくなる。

% cat alice #/bin/sh cat << EOF 111 222 333 444 555 EOF

サンプルスクリプト

実行者に文字列を入力させる

ときには、スクリプトの実行者に何か文字列を入力させたいとき ([Yes|No] など) がある。 そんなときは read コマンドを使用すると直後に指定した変数に、ユーザが入力した文字列が設定される。 Yes|No を判断したい場合、if 文などを使用して処理すれば良い。

% cat alice #!/usr/local/bin/bash ### -n を指定すると改行しない echo -n "Enter your answer: " ### 入力した文字列は answer に格納される read answer ### 変数の内容を echo する echo "Your answer is $answer!" % % ./sample Enter your answer: apple Your answer is apple! %

ユーザが入力した文字列をチェックする

変数に格納されている文字列をチェックする方法をひとつ。 変数 string に大文字小文字を問わないアルファベット文字が入力されているか、を確認したい。 こういうケースでは、期待する文字が入力されているか、を確認する方法がある。 あるいは、期待する文字以外の文字に着目した処理もできる。 今回はその例をスクリプトにしてみる。 まず、変数からアルファベットを削除して残った値をチェックする。 仮にアルファベットだけ入力されているなら、変数の中身は空になるはずである。 今回は sed で正規表現の文字クラスを使用している。 続いて、test -z で変数の中身をチェックしている。

% cat alice #!/usr/local/bin/bash ### 質問を表示する echo -n "Enter some aphabets: " ### 変数に確認する read string ### アルファベットを削除して変数 checked に格納する checked=`echo $string | sed -e 's/[a-zA-Z][a-zA-Z]*//'` ### test コマンドで確認する if [ -z $checked ] ; then echo "Ok" else echo "You did NOT enter only alphabet." fi % % ./alice Enter some aphabets: apple Ok % ./alice Enter some aphabets: app1e You did NOT enter only alphabet. %

誰が実行したのかを確認する

スクリプトによっては特定のユーザに実行してもらいたい場合がある。 あるいは、root では実行させない、という場合があるかもしれない。 そんなときには、id コマンドの結果を利用すると良い。 サンプルでは、ユーザが satoshi でなかった場合、exit コマンドでスクリプトを終了させる処理が実行される。

% cat alice #!/bin/sh ### id コマンドでユーザ名を変数 user に格納する user=`id | sed -e 's/uid=[0-9][0-9]*[(]\(..*\)[)] gid=..*/\1/'` ### 変数 user に格納されている文字列が satoshi でなかったら exit する if [ ! $user = "satoshi" ] ; then echo "Who are you!?" exit fi % % ./alice %

ソート

bash を使用して sort コマンドのようなスクリプトを作成してみる実験。 尚、このスクリプトは引数の数を変更できない。 引数の数の変更に対応するためには、引数の数に応じて、引数の値を配列に代入する部分を作成しなければならない。

#!/bin/bash args=5 if [ $# -ne $args ] ; then echo "You must specfy five arguments. Aborting" exit 1 fi num[1]=$1 num[2]=$2 num[3]=$3 num[4]=$4 num[5]=$5 tmp=0 for ((x=1; x<${#num[@]}; x++)) ; do for ((y=$(($x+1)); y>1; y--)) ; do if [ ${num[$(($y-1))]} -gt ${num[y]} ] ; then tmp=${num[$(($y-1))]} num[$(($y-1))]=${num[y]} num[y]=$tmp fi done done for ((z=1; z<=${#num[@]}; z++)) ; do echo ${num[z]} done

5 つの引数を与えて実行すると、ソートして表示される。

% ./alice 9 8 7 6 5 5 6 7 8 9 %

平均点を求める

平均点を求めるスクリプトを作成してみた。 これは、AWK や Perl を使用した方がもっと楽そうなのだが、あえて bash で作成してみる。

処理する対象のファイルを作成した。 各行の得点の合計し、平均点を生成する。

% cat score name exam1 exam2 exam3 exam4 exam5 ichiro 10 20 30 40 50 jiro 22 33 44 55 66 saburo 33 44 55 66 77

このように作成した。 set コマンドをこのように実行すると、続く変数に格納されているリストが位置パラメータとして設定される。 shift コマンドを実行し、更に位置パラメータを操作している。

% cat get_avg #!/usr/local/bin/bash avg() { num01=$1 num02=$2 num03=$3 num04=$4 num05=$5 total=0 total=$(($num01 + $num02 + $num03 + $num04 + $num05)) echo "average is: $(($total / 5))" echo "" } n=0 while read line ; do ((n++)) if [ $n == 1 ] ; then continue fi set -- $line echo $1 shift avg $1 $2 $3 $4 $5 done < $1

このように実行する。

% ./get_avg score ichiro average is: 30 jiro average is: 44 saburo average is: 55

再帰処理

再帰処理をするスクリプトを作成してみた。 引数にディレクトリを指定して実行してみると動作を確認できる。

#!/bin/sh arg=$1 if [ $# -ne 1 ] ; then echo "Not valid argument" && exit 1 ; fi if ! [ -d $arg ] ; then echo "Not a directory" && exit 1 ; fi arg=`echo $arg | sed 's!/$!!'` echo ${arg##*/} tab="" rec() { start=$1 cd $start tab="${tab}\t" for line in `ls` ; do if [ -d $line ] ; then echo -e "${tab}${line}/" rec $line cd .. tab=${tab%??} else echo -e "${tab}${line}" fi done } rec $arg

リンク

Manpage of BASH (linux.or.jp)

Advanced Bash-Scripting Guide (tldp.org)

Email to Satoshi ABE