2010-12-03 bash シェルスクリプト
bash シェルスクリプト
シェルスクリプトって書いては忘れ、書いては忘れの繰り返しだな。
シェルスクリプトではなく、コマンドとその例については、
id:kaishitaeiichi:20101116:1289914288
Hello, World!
$ echo '#!/bin/bash' >> hello.sh $ echo 'echo "Hello, World!"' >> hello.sh $ echo 'echo "Hello, World!"' >> hello.sh $ cat hello.sh #!/bin/bash echo "Hello, World!" echo "Hello, World!" $ chmod 744 hello.sh $ ./hello.sh Hello, World! Hello, World! $ echo $? 0
ファイルに、コマンドを書き並べて、実行権限を与えれば、
シェルスクリプトとして実行できる。
予備知識
使用するシェルを明記
シェルスクリプトファイルの先頭行で使用するシェルを明記する。
#!/bin/bash
権限
適切な権限を与える。
$ chmod 744 abc.sh $ ls -l abc.sh -rwxr--r-- 1 eiichi eiichi 33 2010-12-04 01:17 abc.sh
シェルスクリプトが実行できていても、シェルスクリプトを実行したユーザによって、ファイルのcpコマンドなど、シェルスクリプト中のファイルの入出力処理が失敗することがよくある。読み書きするファイルやディレクトリの、所有者、所有グループ、パーミッションの設定が悪いことが多い。
実行
/home/eiichi/abc.sh
./abc.sh
相対パスのときは、.をつけないとだめ。PATHに.を加えるのはセキュリティ上良くないらしい。
実行結果
シェルスクリプトというわけじゃないけど、"直前に"実行したコマンドの終了ステータスを、$?で確認できる。$?は特殊変数と呼ばれるものだ。
$ mkdir test-dir $ echo $? 0 $ mkdir test-dir mkdir: ディレクトリ `test-dir' を作成できません: File exists $ echo $? 1
シェルスクリプトの終了ステータスは、最後に実行したコマンドのlsの終了ステータスになる。
#!/bin/bash mkdir result-test touch result-test/a.txt touch result-test/b.txt touch result-test/c.txt cp result-test/d.txt result-test/e.txt ls -l result-test
cpが失敗するのだが、そこで処理が止まるわけではなく、lsが実行される。
eiichi@ubuntu-desktop:~/Programming/shellscripts$ ./result-test.sh cp: `result-test/d.txt' を stat できません: No such file or directory 合計 0 -rw-r--r-- 1 eiichi eiichi 0 2011-05-16 00:18 a.txt -rw-r--r-- 1 eiichi eiichi 0 2011-05-16 00:18 b.txt -rw-r--r-- 1 eiichi eiichi 0 2011-05-16 00:18 c.txt eiichi@ubuntu-desktop:~/Programming/shellscripts$ echo $? 0
result-test.shの終了ステータスは、最後に実行したコマンドのlsの終了ステータスになる。
今度は、set -eを指定してみる。
#!/bin/bash set -e mkdir result-test touch result-test/a.txt touch result-test/b.txt touch result-test/c.txt cp result-test/d.txt result-test/e.txt ls -l result-test
cpが失敗する。そこで処理が止まる。cpより後にある、lsは実行されない。
eiichi@ubuntu-desktop:~/Programming/shellscripts$ ./result-test-b.sh cp: `result-test/d.txt' を stat できません: No such file or directory eiichi@ubuntu-desktop:~/Programming/shellscripts$ echo $? 1
result-test-b.shの終了ステータスは、最後に実行したコマンドのcpの終了ステータスになる。
シェルスクリプトには、たくさんのコマンドをづらづらと書くことになる。場合によっては、その中の一部のコマンドが失敗することもある。シェルスクリプトは、途中で失敗したコマンドがあったとしても、残りのコマンドを実行して終了してしまう。
コマンドが失敗した段階で、シェルスクリプトを異常終了させたい場合は、
set -e
を指定すると良い。
成功したら0、失敗したら1というのが基本みたいなんだが、コマンドによっては、成功、失敗とはちがうんじゃない?というのもある。
test
$ test 1 -gt 2 $ echo $? 1 $ test 1 -lt 2 $ echo $? 0
$ echo "abcd" | grep bc abcd $ echo $? 0 $ echo "abcd" | grep bd $ echo $? 1
トラブルシューティング
サンプルがうごかない
このページで説明しているのは、bashのサンプル。sh, csh, tcshなどではうごかない例もたくさんある。
そして、自分がつかっているシェルがbashでない。それに気づいてない。自分がログイン直後にデフォルトで使用しているシェルはで確認できる。
$ cat /etc/passwd | grep 自分のユーザ名
また、
$ cat /etc/shells
で使用できるシェルを確認できる。
tcshに切り替えて、作業して、元のシェルに切り替えるには、
$ /bin/tcsh $ 何かの作業をする $ exit
デバッグ
bash -x ./abc.sh
お手軽だけど、どばっと表示内容が増えて、見づらい。とにかく、1行ずつ丁寧に読んでいくと、どこが期待通りになってないかわかる。
変数
変数に値を設定するとき
A = "abc"
のようにスペースをいれるとだめ。
A="abc"
とする
改行コード
LinuxのシェルスクリプトをWindows上で作成している場合は、要注意!
たとえば、Windows上で、Eclipseのプロジェクト内で、作成したシェルスクリプトを、Linux上にアップロードして実行すると、エラーがでることがある。改行コードがWindowsは\r\n(CR + LF)で、Linuxは\n(LF)のため。
TeraTermやPutty, Poderosaのようなターミナルアプリの設定によっては、改行で、\r(CR)が追加されることもあるので、注意。
bash -x スクリプトファイルパス、または、od -c スクリプトファイルパスなら、\rが混じっていることを確認できる。
ぜんぜんちがう話題だけど、改行を表現したいときは、$'\n'とかく。
文字と数値
シェルスクリプトでは、数字は、基本、文字扱い。数値としてあつかいたいとき、たとえば比較や計算などをしたいときは、注意が必要。
特に、数値としてあつかいたい場合、計算をしたい場合は、
- $((....))
X=1 Y=$(($X + 2)) echo $Y
- declare -i
declare -i X=1 declare -i Y Y=X+2 echo $Y
if
if["abc" = "$1"];then
のように、[と]のそばにスペースがないのはだめ。
if [ "abc" = "$1" ];then
とそばにスペースをいれて書く。
変数
値を設定
値を設定するには=をつかう。$はつけないことに注意
A='abc'
値を参照
値を参照するには、$をつける
$A
または、
${A}
変数に対する処理
変数に対して、ある処理をおこなうことができる。
文字列の長さ
$ A="abcd"
$ echo $A
abcd
$ echo ${#A}
4
文字列の削除
パターンにマッチした文字列を削除する。
前半分とか後半分とか、わりとざっくりした削除。
便利なような、不便なような。
$ A=/abcd/efgh/abcd/efgh
$ echo ${A%/efgh*}
/abcd/efgh/abcd
$ echo ${A%/*}
/abcd/efgh/abcd
$ echo ${A%%/efgh*}
/abcd
$ echo ${A#*abcd/}
efgh/abcd/efgh
$ echo ${A##*abcd/}
efgh
$ echo ${A##*/}
efgh
文字列の置換
$ A="abcd efgh abcd efgh"
$ echo ${A/efgh/ijkl}
abcd ijkl abcd efgh
$ echo ${A//efgh/ijkl}
abcd ijkl abcd ijkl
文字列から部分文字列を切り出す
${A:x:y}
x番目の文字からはじめてy文字を切り出す。
$ A="abcdefg"
$ echo ${A:0:4}
abcd
$ echo ${A:2:3}
cde
コマンド置換
シェルスクリプト中で、コマンドを実行して、その結果?返り値?をシェルスクリプト中で使用することができる。``や$()を使用する。
A=`pwd` B=$(ls)
変数Cには、testファイルの内容が設定される。
1行のときは良いけど、複数行の時は改行がなくなってしまうようだ。
testfile=test.txt C=$(cat $testfile) echo $C
配列
リストから配列をつくる
配列を生成するには、リストを()で囲む。
$ testArray=(a b c d)
$ echo ${testArray[0]}
a
$ echo ${testArray[1]}
b
$ echo ${testArray[2]}
c
$ echo ${testArray[3]}
d
とにかく、()に対して、リストを与えれば、配列にできるようだ。
$ numberArray=($(seq 10 13))
$ echo ${numberArray[0]}
10
$ echo ${numberArray[1]}
11
$ echo ${numberArray[2]}
12
$ echo ${numberArray[3]}
13
配列からリストをつくる
$ testList=${testArray[@]}
$ echo $testList
a b c d
配列でループ
A=(a b c d) A_LENGTH=${#A[@]} A_LAST_INDEX=$(($A_LENGTH - 1)) #indexを指定してループ for index in $(seq 0 $A_LAST_INDEX) do echo ${A[$index]} done #配列をリストにしてループする for e in ${A[@]} do echo $e done #文字列でなく数値としてあつかえるように変数を宣言 declare -i index=0 while [ $index -lt $A_LENGTH ] do echo ${A[$index]} index=$index+1 done
if
if [ "$1" ]; then echo "in if...." elif [ "$1" = "b" -a "$2" = "bb" -o "$1" = "c" ]; then echo "in elif...." else echo "in else...." fi
- [....]これは、test ....とおなじ。
- = 代入じゃなくて等しいかどうか
- -a and
- -o or
[と]はそばにスペースが必要なことに注意
else ifじゃだめで、elifとなるのが、忘れやすい
andとorは、-aと-oより、&&と||で書いたほうが、ほかのプログラム言語でもなじみある書き方だとおもう。シェルでは、&&と||は、コマンドを連続して実行するときに使うわけだけど、[]はtestコマンドとおなじだから、ここでも使うことができる。()でまとめることもできる。
if [ .... ] && ([ .... ] || [ .... ]); then
ためしに、結果をずらずら書くと、
eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && ([ 1 = 1 ] || [ 1 = 1 ]); echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && ([ 1 = 1 ] || [ 1 = 2 ]); echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && ([ 1 = 2 ] || [ 1 = 1 ]); echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && ([ 1 = 2 ] || [ 1 = 2 ]); echo $? 1 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && ([ 1 = 1 ] || [ 1 = 1 ]); echo $? 1 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && ([ 1 = 1 ] || [ 1 = 2 ]); echo $? 1 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && ([ 1 = 2 ] || [ 1 = 1 ]); echo $? 1 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && ([ 1 = 2 ] || [ 1 = 2 ]); echo $? 1
eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && [ 1 = 1 ] || [ 1 = 1 ]; echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && [ 1 = 1 ] || [ 1 = 2 ]; echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && [ 1 = 2 ] || [ 1 = 1 ]; echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 1 ] && [ 1 = 2 ] || [ 1 = 2 ]; echo $? 1 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && [ 1 = 1 ] || [ 1 = 1 ]; echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && [ 1 = 1 ] || [ 1 = 2 ]; echo $? 1 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && [ 1 = 2 ] || [ 1 = 1 ]; echo $? 0 eiichi@ubuntu-desktop:~/Programming/shellscripts$ [ 1 = 2 ] && [ 1 = 2 ] || [ 1 = 2 ]; echo $? 1
case
caseも他のプログラミング言語でもおなじみ。
下は、かんたんな、引数の解析をしている。
case-examples.sh
#!/bin/bash
while [ $# -gt 0 ]; do
echo "current number of arguments is $#"
case "$1" in
"--help"|"-h") echo "This is help!"; exit 0;;
"-a") echo "a=$2"; shift 2;;
"-b") echo "b=$2"; shift 2;;
*) echo "the others"; shift 1;;
esac
done
これを実行すると
eiichi@ubuntu-desktop:~/Programming/shellscripts$ ./case-examples.sh -a 1 -b 2 3 4 current number of arguments is 6 a=1 current number of arguments is 4 b=2 current number of arguments is 2 the others current number of arguments is 1 the others
eiichi@ubuntu-desktop:~/Programming/shellscripts$ ./case-examples.sh --help current number of arguments is 1 This is help!
forループ
bashのforループは、
Javaのfor文でいうなら
for(int i = 0; i < array.length; i++) { }
というタイプのfor文ではなくて、
for(A a : list) {
}
のようなタイプのいわゆるforeach文
phpなら、こんなやつ
foreach ( $array as $a ) {
echo "$a\n";
}
つまり、リストを与えてループ処理を実行する。
forの例
リストをハードコーディングして、ループ処理を実行
for i in a b c d do echo $i done
eiichi@ubuntu-desktop:~/Programming/shellscripts$ ./for.sh a b c d
リストをseqコマンドで生成して、ループ処理を実行
for i in $(seq 1 4) do echo $i done
eiichi@ubuntu-desktop:~/Programming/shellscripts$ ./for.sh 1 2 3 4
とにかくリストを与えることさえできれば、forに適用できる。
1行で書くなら、下のようにする。
for i in $(seq 1 4); do echo $i; done
lsコマンドでファイル名のリストを取得して、ループ処理を実行
for i in $(ls *.txt) do echo $i done
eiichi@ubuntu-desktop:~/Programming/shellscripts$ ./for.sh a.txt b.txt c.txt
forをつかわなくても、|をつかえば、結果としてループ処理とおなじことはできる場合もある。|をつかうほうがいかにもシェルっぽい。
$ ls *.txt | xargs echo a.txt b.txt c.txt
whileループ
whileの例
whileは、配列のループのところに書いた例のように
while [....] do .... done
という書き方ができる。
使いどころがありそうな、例としては、ファイルを1行ずつ読み込むというものがある。
#!/bin/bash LINE_LIST="" while read LINE do echo $LINE LINE_LIST="$LINE_LIST $LINE" done < "list.txt" echo $LINE_LIST
あとは、特定の条件をみたすまで、sleepしたいときとかかなあ。
関数
関数の返り値の取得は、けっこう独特。
- returnの返り値は、終了ステータスとなる。
- 関数の呼び出し元で$?で取得する。
- 終了ステータスでない返り値がほしいときは、
#関数定義 function mycp { local INPUT_FILE_PATH="$1" local OUTPUT_DIR="$2" cp -p $INPUT_FILE_PATH $OUTPUT_DIR } #関数実行 mycp "test.txt" "test_dir" #終了ステータスは、関数の最後で実行されたコマンドの実行結果になる。 echo $? #関数定義 mycp2() { mycp "$1" "$2" if [ $? = 0 ]; then return 0; else return 1; fi } #関数実行。ふつうのコマンド呼び出しと同じ。 mycp2 "test.txt" "test_dir" #終了ステータスは、returnで指定された値になる。 echo $? #結果ステータスではない戻り値を取得したいときは、 #echoで出力して function mydatetime { echo $(date '+%Y%m%d%H%M%S') } #$(....)で関数を実行して、結果を取得する。 #mydatetime関数を実行すると、mydatetime内のechoが #コンソールに出ない代わりに、変数に設定される。 CURRENT_DATE_TIME=$(mydatetime) echo $CURRENT_DATE_TIME
その他
javaコマンドの実行結果
System.exit(x);
で指定できる。
System.exit(10);と指定すれば、
java xxxx echo $? 10
System.exit(x);で明記しなければ、
正常終了した場合は、
java xxxx echo $? 0
例外終了した場合は、
java xxxx echo $? 1
参考
"set -e": 実行コマンドが0以外の終了コードの場合、シェルスクリプトを終了する - 元RX-7乗りの適当な日々
コマンドのエラーを必ず検出するための、set -eか!
なるほど。ああー、もっとはやく知っていれば。でも、エラー時にエラーが発生した箇所でログ出力もしたい。そのためには、やっぱりコマンド実行のたびに$?するしかない。どうしよう。
2010-12-01 bashでコマンドの履歴を検索する
bashでコマンドの履歴を検索する
- Ctrl + r
- コマンドの履歴を検索する
- Ctrl + rをたたいた後、前に実行したコマンドの一部を入力すると、それが表示される。
- Ctrl + rを叩くたびに、より古い履歴を検索する
- Ctrl + s
- Ctrl + sを叩くたびに、より新しい履歴を検索する
- Ctrl + sは通常、入力stopとしてはたらいてしまうため、Ctrl + sを利用する際は、.bashrcなどに以下を追記しておくとよい。
- stty stop undef
- Ctrl + e
- コマンドを選択する。ただし、選択するだけで、実行しない

