;
)で区切って複数記すことはできる。
shell> printf "abc"; printf "def" abcdef
# echo "この行は処理されません。"
縦棒(|)で挟んで2つのシェルコマンドを記すと、1つ目のシェルコマンドの出力を2つ目のシェルコマンドの入力として渡すことができる。2つ目のコマンドが受け取った入力を扱うには、readで標準入力を読めばいい。
【例】 $ cat test.sh #!/bin/bash #標準入力の内容を変数inputに代入 read input echo $input:$input $ echo 'hoge' | bash test.sh hoge:hoge
参考:15
【書式】 変数名=値 【例】 $ echo $HOGE $ export HOGE=foo $ echo $HOGE foo
変数名や値と =
との間は空けないこと。定義は関数の内外を問わずスクリプト内全体で有効(全てグローバル変数)。
変数を消去するにはunset
コマンドを用いる。
【書式】 unset 変数名 【例】 $ echo $HOGE foo $ unset HOGE $ echo $HOGE $
変数名の前に$は付けない。
クォート記号を含む値を変数に定義し、変数を介してコマンドの引数を指定するとうまくいかないらしい。クォートで囲む値の中身だけを別途変数に定義し、クォート記号は直接記述するようにすればうまく行った[16]。
$ cat sync_ng.sh #!/bin/sh OPT='-avz -e "ssh -i /home/user/.ssh/id_rsa"' HOST="user@hoge.com" PATH="/home/user/foo.txt" echo "rsync "${OPT} ${HOST}:${PATH} ${PATH} rsync ${OPT} ${HOST}:${PATH} ${PATH} $ ./sync_ng.sh rsync -avz -e "ssh -i /home/user/.ssh/id_rsa" user@hoge.com:/home/user/foo.txt /home/user/foo.txt Unexpected remote arg: user@hoge.com:/home/user/foo.txt rsync error: syntax or usage error (code 1) at main.c(1213) [sender=3.0.6] # コマンドをechoすると正しいようだが、エラー終了してしまう $ cat sync_ok.sh #!/bin/sh OPT="-avz" AUTHKEY="/home/user/.ssh/id_rsa" HOST="user@hoge.com" PATH="/home/user/foo.txt" echo "rsync "${OPT}" -e ""${AUTHKEY}" ${HOST}:${PATH} ${PATH} rsync ${OPT} -e "${AUTHKEY}" ${HOST}:${PATH} ${PATH} $ ./sync_ok.sh rsync -avz -e /home/user/.ssh/id_rsa user@hoge.com:/home/user/foo.txt /home/user/foo.txt receiving incremental file list foo.txt sent 14 bytes received 43 bytes 16.29 bytes/sec total size is 42596 speedup is 747.30まとまった分量の文字列出力や変数代入を行う際、ヒアドキュメントを使うと便利かもしれない33。
$ cat test.sh #!/bin/bash HENSU=$(cat << EOS 徒然なるままに日暮らし 硯に向かひて 心にうつりゆくよしなしごとを そこはかとなく書きつくれば あやしうこそものぐるほしけれ。 EOS ) echo "$HENSU" $ bash test.sh 徒然なるままに日暮らし 硯に向かひて 心にうつりゆくよしなしごとを そこはかとなく書きつくれば あやしうこそものぐるほしけれ。
echo
コマンドは単純な文字列出力に、printf
コマンドは指定した書式で文字列を出力するのに使える。
【書式】 echo オプション 文字列 【返り値】 成功→0 エラーあり→0より大きな値
printf 書式 文字列
書式 | 内容 | 例・備考 |
---|---|---|
\n |
改行 |
# printf abc\ndef abc def |
%s %nums |
指定引数の中身(文字列) |
# name="Albert"; printf "My name is %s.\n" $name My name is Albert. # name="Albert"; printf "My name is %4s.\n" $name My name is Albert. # name="Albert"; printf "My name is %8s.\n" $name My name is Albert. # name="Albert"; printf "My name is %-8s.\n" $name My name is Albert .指定引数を文字列として出力する。複数の引数を指定した場合、 %s は登場順に1番目の引数、2番目の引数...と参照する。"s"の前に数値numを記せば、少なくともnum文字の幅が確保される。文字列長がこれより短ければ右詰で余った左側は空白が補完される(例:"%5s" →" abc" 5文字幅)。numの前にハイフンを記せば左詰となる。
|
%d %numd %strnumd |
指定引数の中身(数値) |
# i=1; printf "The value of \$i is %d.\n" $i The value of $i is 1. # i=1; printf "The value of \$i is %5d.\n" $i The value of $i is 1. # i=1; printf "The value of \$i is %05d.\n" $i The value of $i is 00001.指定引数を数値として出力する。複数の引数を指定した場合、 %s は登場順に1番目の引数、2番目の引数...と参照する。"d"の前に数値numを記せば、num文字分だけ幅を確保する(例:"%5d"→" 123" 5桁幅)。更に、数値の前に"0"を記せば上位空桁は0で埋められる(指定しない場合は空白で埋められる)。
|
文字コードで出力文字を指定する方法34
$ echo -e "\x41"
A
参考文献・サイト
複数の変数に格納された値を結合するには、単に複数の変数名を続けて記述すれば良い。
例:str1に格納された文字列と、str2に格納された文字列を結合したものをstr12に格納して標準出力に出力する。
shell> str1="hogehoge" shell> str2="fugofugo" shell> str12=$str1$str2 shell> echo $str12 hogehogefugofugo
変数に文字列を連結する際は、変数名と連結する文字列を区別するため、変数名は{...}で囲む。
例:str1に格納された文字列の後に、"aheahe"を連結したものをstr3に格納して標準出力に出力する。
shell> str1="hogehoge" shell> str3="${str1}aheahe" shell> echo $str3 hogehogefugofugo
参考サイト:複数の変数を連結する(ITpro シェル・スクリプト・リファレンス)
配列変数名=(要素1 要素2 要素3 ...)
配列変数名[インデックス1]=値1 配列変数名[インデックス2]=値2 ...
なお、配列キー(インデックス)に使えるのは整数(非負整数?)のみ
配列名+=(値)
配列変数名をそのまま指定すると、最初の要素だけが返る。
配列変数全体を取得するには変数名[*]
または変数名[@]
と指定。
配列変数の個別要素を呼び出すには変数名[配列番号]
と指定。未定義の番号を指定すると空が返る。
配列変数内の要素数は#変数名[*]
。
以下にこれらの事例。
shell> a=(1 2 c d 5) shell> echo $a 1 shell> echo ${a[3]} d shell> echo ${a[*]} 1 2 c d e shell> echo ${#a[*]} 5
【例】拡張子が".html"であるファイル名を配列変数に代入し、各要素、個数を表示する $ files=(`ls *.html`);echo ${files[*]};echo ${#files[*]} index.html inquiry.html products.html 3
【例】 array1=(a b c) array2=(d e f) array3=(${array1[@]} ${array2[@]}) echo "${array3[@]}" → a b c d e f
$ cat test.sh #!/bin/sh array=(north south east west) array_copyok=(${array[*]}) array_copyng=$array echo ${array[*]} echo ${array_copyok[*]} echo ${array_copyng[*]} $ ./test.sh north south east west north south east west north
【例】ファイルリストをバージョン順に並べ替え $ cat test.sh #!/bin/sh array=(hoge.3 hoge.2 hoge.10 hoge.11 hoge.1) array_asc=($(for item in ${array[*]};do echo $item;done | sort -V)) echo ${array[*]} echo ${array_asc[*]} $ ./test.sh hoge.3 hoge.2 hoge.10 hoge.11 hoge.1 hoge.1 hoge.2 hoge.3 hoge.10 hoge.11
空白を含む要素があった場合、ループ処理で空白が要素の区切りと判断されてしまい意図した動作にならない場合がある(環境による?)。少なくともMac 10.11のTerminal(GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15))では以下のようになった。
$ cat test.sh #!/bin/sh STRS=("This is a pen." "This is an apple." "This is a pooh doll") for STR in ${STRS[*]} # 繰り返し処理→for do echo $STR done $ ./test.sh This is a pen. This is an apple. This is a pooh doll.
whileを使って全ての配列キーを参照させて処理するようにすれば、空白を要素区切りとしてみなされることなく処理できた。
$ cat test.sh #!/bin/sh STRS=("This is a pen." "This is an apple." "This is a pooh doll") i=0 while [ $i -lt ${#STRS[*]} ] # 条件を満たす間繰り返す→while do echo ${STRS[$i]} i=`expr $i + 1` # i を一つ増やす演算を行う→expr done $ ./test.sh This is a pen. This is an apple. This is a pooh doll.
「全配列要素を出力し、grepを使ってチェックしたい値でフィルタリングし、wlで語数を数え、結果が0なら存在しない、1以上なら存在する」などのチェック方法がある31
$ cat test.sh #!/bin/bash array=(red blue green yellow purple) value='blue' echo "array: "${array[@]} echo "value: "$value if [ `echo ${array[@]} | grep -o $value | wc -w` -eq 0 ] then echo "The value '$value' was not found in the array." else echo "The value '$value' was found in the array." fi $ bash test.sh array: red blue green yellow purple value: blue The value 'blue' was found in the array.
普通に「a=1+1」のように記述しても演算結果は代入されない("1+1"という文字列が$aに代入される)。演算結果を代入するには expr コマンドを使う必要がある[3]。あるいは$((計算式))
。
$ expr 1 + 1 # 演算子の前後には空白を入れる 2 $ expr 1+1 1+1 $ echo $((1 + 1)) 2 $ echo $((1+1)) # こちらの書式は演算子の前後には空白を入れなくても機能する 2
なお小数は扱えないので、小数を含む演算を行いたい場合はbc
を使う。bc
はexpr
とは異なり、パイプを使うなどして演算内容を渡す。
$ expr 10 + 0.1 expr: non-numeric argument $ echo $((10+0.1)) -bash: 10+0.1: syntax error: invalid arithmetic operator (error token is ".1") $ echo "10 + 0.1" | bc 10.1
bcコマンドで小数点以下の桁数を指定するにはscaleを用いる。scaleが働くのは割り算で割り切れない場合などに限られるよう。
$ echo "scale=3;10 + 0.1" | bc 10.1 # 10.100 とはならない $ echo "scale=1;10 + 0.001" | bc 10.001 # 10.0 とはならない $ echo "scale=1;10/3" | bc 3.3 $ echo "scale=3;10/3" | bc 3.333
有効桁数はlengthで得られる。
$ echo "10+0.1;length(10 + 0.1)" | bc 10.1 3 $ echo "10+0.001;length(10 + 0.001)" | bc 10.001 5
正規表現を使った文字列判定も可能[7]。返り値は以下の通り。
部分表現 "\( ... \)" の有無 | マッチ | マッチしない |
---|---|---|
あり | 最初の部分表現にマッチした文字列 | ヌル文字列 |
なし | マッチした文字列の文字数 | 0 |
【書式】 expr 文字列 : 正規表現 expr match 文字列 正規表現 【例】 $ expr "hogefugafoo" : "hoge" 4 # 先頭の部分一致はマッチしたと判定される $ expr "hogefugafoo" : "fuga" 0 # 先頭以外の部分一致はマッチしたと判定されない $ expr "hogefugafoo" : ".*fuga.*" 11 # 前後に0文字以上の任意文字列を示す".*"を付ければ文字列全体がマッチ $ cat test.sh #!/bin/sh if expr "$1" : ".*\.txt" >/dev/null; then echo $1." seems a text file." else echo $1." seems not a text file." fi $ ./test.sh hoge.txt hoge.txt seems a text file. $ ./test.sh hoge.bin hoge.bin seems not a text file.
なお、この正規表現には自動的に先頭一致(^
)の意味で解釈されるため、明示的に^
を正規表現の先頭に付加すると、「unportable BRE」というエラーが表示される。但し、処理は正常に行われるみたい8
【書式】 expr index 文字列 文字セット
文字列の中に文字セット中のいずれかの文字が見つかれば、その最初に見つかった位置(先頭が1)を返す。いずれの文字も見つからなければ 0 を返す。
【例】 $ expr index "hoge" "abc" 0 # みつからなかった $ expr index "hoge" "abcde" 4 $ expr index "hoge" "abcdg" 3 $ expr index "hoge" "abcdh" 1
【書式】 expr length 文字列 【例】 $ expr length "hoge" 4
【書式】 expr substr 文字列 開始位置 長さ
文字列の開始位置番目から、最大長さ文字の部分文字列を返す。開始位置や長さに自然数以外の値を指定した場合はヌル文字列を返す。
【例】 $ expr substr "Conguratulations" 6 5 ratul
書式:for 変数名 in リスト名 do コマンド done
例:配列変数「val」の内容を出力
$ cat test.sh #!/bin/sh val=(a b c d e) for loop in ${val[*]} do echo $loop done $ chmod +x ./test.sh $ ./test.sh a b c d e
なお、配列要素に空白を含む場合、リスト名部分はダブルクォートで囲む(そうしないと要素が空白で分割され空白の前後で別々の要素とみなされてしまう)
$ cat test.sh #!/bin/sh val=("foo hoo" "hello youtube") for loop in ${val[*]} do echo $loop done $ chmod +x ./test.sh $ ./test.sh foo hoo hello youtube
例2(拡張子指定によるファイル名一括変換)
for nm in *.txt
do
mv $mm $[nm%.txt].doc
done
…でいいのかな?
【例3】2011で始まるファイル・ディレクトリが増えてきたので 2011ディレクトリを作ってこの中に移動 $ mkdir 2012 $ ls -d 2012?* 20120105 20120210 20120314 20120418 20120626 20120815 20120924 20121018 20121107 20120110 20120213 20120316 20120425 20120629 20120821 20120925 20121019 20121108 ... $ for file in `ls -d 2012?*'`;do mv $file 2012;done $ ls 2012 20120105 20120210 20120314 20120418 20120626 20120815 20120924 20121018 20121107 20120110 20120213 20120316 20120425 20120629 20120821 20120925 20121019 20121108 ...
【書式】 while 条件文 do 処理内容 done
条件文にはtestコマンドが使われることが多い。
【例】1から10までの値を出力する $ cat test.sh #!/bin/sh i=1 while [ $i -le 10 ] # $i が10以下の間は処理を繰り返す do echo $i i=`expr $i + 1` # 演算にはexprコマンドを使う done $ chmod +x test.sh $ ./test.sh 1 2 3 4 5 6 7 8 9 10 # 以下のように記せば同じ内容をコマンドライン上で直接実行できる $ i=1;while [ $i -le 10 ];do echo $i;i=`expr $i + 1`;done
パイプを使うと子プロセルでシェルが呼び出され、子プロセルが終わると変数は元に戻る。従って、パイプを使ってwhileを呼び出すと、whileループ内で定義した変数は、ループ外には反映されない。
$ cat ./test.sh #!/bin/sh count=0 ls -l | while read line;do count=$(($count + 1)) echo $count done echo "----" echo $count $ ./test.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 ---- 0 ←whileループの外ではcount変数の値は元の0のまま
【例】 $ i=0;while [ $i -lt 12 ];do date "+awstats%m%Y.www.example.org.txt" --date "2014/04/01 $i month";i=`expr $i + 1`;done awstats042014.example.org.txt awstats052014.example.org.txt awstats062014.example.org.txt awstats072014.example.org.txt awstats082014.example.org.txt awstats092014.example.org.txt awstats102014.example.org.txt awstats112014.example.org.txt awstats122014.example.org.txt awstats012015.example.org.txt awstats022015.example.org.txt awstats032015.example.org.txt 【例】 $ i=0;while [ $i -lt 12 ];do printf "%02d¥n";i=`expr $i + 1`;done 01 02 03 04 05 06 07 08 09 10 11 12
Mac OS X(10.4.11)でniftyのHPコンテンツUpload用FTPサイトにアクセスすると、最初のget,put,lsなどのftpコマンド実行時、反応が返ってくるまで1分程度かかる→passiveモードを指定すればこの問題は起こらなくなった。 それを待って次の処理をするのは扱いにくい(待っている間他のことをしていると、返答後アイドル状態が300秒続いて接続が切られてしまう)。そこでシェルスクリプトを利用。
#!/bin/sh HOST_NAME="(FTPサーバのホスト名)" USER_NAME="(FTPサイトのログインID)" PASSWORD="(FTPサイトのログインパスワード)" LOCAL_DIR="(ローカルのディレクトリ)" REMOTE_DIR="(FTPサーバのディレクトリ)" ftp -n ${HOST_NAME} << _EOF_ user ${USER_NAME} ${PASSWORD} lcd ${LOCAL_DIR} cd ${REMOTE_DIR} put file1 put file2 put file3 ... bye _EOF_
参考サイト:
上記事例は個別にアップロードするファイルを指定する例だが、
find
コマンドなどを使って条件にあったファイル一覧を取得し、それをアップロードするようにしたい。例えば最近3日以内に修正されたカレントディレクトリ内のhtmlファイル(サブディレクトリは検索しない) find *.html -mtime -3 -maxdepth 0
など。
できたみたい。以下は3日以内に修正されたhtmlファイルおよびcssファイルをアップロードするスクリプト。
HOST_NAME="(FTPサーバのホスト名)" USER_NAME="(FTPサイトのログインID)" PASSWORD="(FTPサイトのログインパスワード)" LOCAL_DIR="(ローカルのディレクトリ)" REMOTE_DIR="(FTPサーバのディレクトリ)" cd ${LOCAL_DIR} FN_HTML=(`find *.html -mtime -3`) FN_CSS=(`find *.css -mtime -3`) ftp -n ${HOST_NAME} << _EOF_ user ${USER_NAME} ${PASSWORD} lcd ${LOCAL_DIR} cd ${REMOTE_DIR} prompt mput ${FN_HTML[*]} mput ${FN_CSS[*]} bye _EOF_
${変数名[*]} とすると配列変数の各要素を空白で区切ってつないだ1つの文字列として得られる。 ftpのpromptコマンドは、mputなどのコマンドで各ファイルの処理を都度尋ねるか、問い合わせせずに全部対象として処理するかを切り替える。
「if 条件;then ...;else ...;fi
」で条件分岐の記述を行うことができる。なお条件の指定にはtest
コマンド(またはその略記法)を用いる。else文を使うと、条件に当てはまらなかった場合の処理も記述できる。当てはまらない場合を更に分岐するには「elif」を使う9。
【書式】 if 条件 then 条件に適合した場合の処理内容 elif 条件 then 条件に適合した場合の処理内容 else 条件に適合しなかった場合の処理内容 fi 【例1】 if [ "$point" -ge 70 ] then echo "Passed!" else echo "Not Passed." fi コマンドラインに一連のコマンドとして入力するには $ point=75;if [ "$point" -ge 70 ];then echo "Passed.";else echo "Not Passed.";fi Passed. $ point=65;if [ "$point" -ge 70 ];then echo "Passed.";else echo "Not Passed.";fi Not Passed. 【例2】自分自身の行数が3行を越えているかどうか判定 $ cat test.sh #!/bin/sh THRE=3 WL=`wc -l $0 | gawk '{print $1}'` if [ $WL -gt $THRE ]; then echo "Over ("$WL" > "$THRE")" else echo "Not Over ("$WL" <= "$THRE")" fi $ ./test.sh Over (8 > 3)
test
コマンドおよび略記法については以下の通り。真(true)の場合の返り値は0、偽(false)の場合の返り値が1。
testコマンド | 略記法 | 説明 |
---|---|---|
test -e ファイル |
[ -e ファイル ] |
ファイルが存在(exist)すれば真 |
test -f ファイル |
[ -f ファイル ] |
ファイルが存在し通常ファイル(file)であるなら真 |
test ! -f ファイル |
[ ! -f ファイル ] |
ファイルが存在しないなら真 |
test -x ファイル |
[ -x ファイル ] |
ファイルが存在し実行可能(executable)であるなら真 |
test -d ファイル |
[ -d ファイル ] |
ファイルが存在しディレクトリ(directory)なら真 |
test -s ファイル |
[ -s ファイル ] |
ファイルが存在しサイズが0でなければ真 |
test -r ファイル |
[ -r ファイル ] |
ファイルが存在し読み取り可能(readable)であれば真 |
test -w ファイル |
[ -w ファイル ] |
ファイルが存在し書き込み可能(writable)であれば真 |
test ファイル1 -nt ファイル2 |
[ ファイル1 -nt ファイル2 ] |
ファイル1が存在し、ファイル2より新しい(newer than)なら真 |
test ファイル1 -ot ファイル2 |
[ ファイル1 -ot ファイル2 ] |
ファイル1が存在し、ファイル2より古い(older than)なら真 |
test -n 文字列 test 文字列 |
[ -n 文字列 ] [ 文字列 ] |
文字列が空でなければ真
$ cat test.sh #!/bin/bash STR1="hoge" STR2="" echo -n "STR1 ($STR1) ... " if [ -n "$STR1" ] then echo "not null" else echo "null" fi echo -n "STR2 ($STR2) ... " if [ -n "$STR2" ] then echo "not null" else echo "null" fi $ ./test.sh STR1 (hoge) ... not null STR2 () ... null |
test -z 文字列 |
[ -z 文字列 ] |
文字列が空であれば真
lsの結果を使ってディレクトリが空かどうかの判定ができる30
$ ls hoge.txt $ cat test.sh #!/bin/bash if [ -z "$(ls -A $1)" ]; then echo "Empty" else echo "Not Empty" fi $ ./test.sh . Not Empty $ mkdir foo $ ./test.sh ./foo Empty |
test 文字列1 = 文字列2 |
[ 文字列1 = 文字列2 ] |
文字列1と文字列2が一致していれば真 |
test 文字列1 != 文字列2 |
[ 文字列1 != 文字列2 ] |
文字列1と文字列2が一致しなれば真 |
test 文字列 =~ 正規表現 |
[ 文字列 =~ 正規表現 ] |
文字列が拡張正規表現に適合したら真[18]
正規表現を直接test文に書く場合、正規表現パターンは引用符などで囲まない。
$ cat test.sh #!/bin/bash STR1="abcde" STR2="cdeab" REGEXP1="^.*bcd.*$" REGEXP2="^(abcde|fghij)$" # 角かっこは二重にしないとうまく動作しなかった if [[ $STR1 =~ $REGEXP1 ]] then echo "hit" else echo "no hit" fi if [[ $STR2 =~ $REGEXP1 ]] then echo "hit" else echo "no hit" fi if [[ $STR1 =~ $REGEXP2 ]] then echo "hit" else echo "no hit" fi if [[ $STR2 =~ $REGEXP2 ]] then echo "hit" else echo "no hit" fi $ ./test.sh hit no hit hit no hit
パターンに適合した部分は
$BASH_REMATCH 配列変数で参照できる29。
$ cat test.sh #!/bin/bash STR="--option=hoge" PATTERN="^--([0-9A-Za-z]+)=(.*)$" echo "Argument: $STR" if [[ $STR =~ $PATTERN ]] then echo "An option was specified." echo " ${BASH_REMATCH[1]} = ${BASH_REMATCH[2]}" fi $ bash test.sh Argument: --option=hoge An option was specified. option = hoge |
test 数値1 -eq 数値2 |
[ 数値1 -eq 数値2 ] |
「数値1=数値2」(equal)なら真 |
test 数値1 -ne 数値2 |
[ 数値1 -ne 数値2 ] |
「数値1≠数値2」(not equal)なら真 |
test 数値1 -gt 数値2 |
[ 数値1 -gt 数値2 ] |
「数値1>数値2」(greater than)なら真 |
test 数値1 -ge 数値2 |
[ 数値1 -ge 数値2 ] |
「数値1≧数値2」(greater or equal)なら真 |
test 数値1 -lt 数値2 |
[ 数値1 -lt 数値2 ] |
「数値1<数値2」(less than)なら真 |
test 数値1 -le 数値2 |
[ 数値1 -le 数値2 ] |
「数値1≦数値2」(less or equal)なら真 |
条件を論理結合する方法は以下の通り。
testコマンド | 略記法 | 説明 |
---|---|---|
test ! 条件式 |
[ ! 条件式 ] |
(not)条件式が偽であれば真 |
test 条件式1 -a 条件式2 |
[ 条件式1 -a 条件式2 ] |
(and)条件式1と条件式2の両方が真であれば真
$ cat test.sh #!/bin/sh if [ $# -eq 1 -a $1 = "-h" ];then echo "引数 -h だけが指定されました。" else echo "引数は "$#" 個指定されました。" fi $ chmod +x test.sh $ ./test.sh ./test.sh: line 2: [: too many arguments 引数は 0 個指定されました。 $ ./test.sh -h 引数 -h だけが指定されました。 $ ./test.sh -p 引数は 1 個指定されています。 $ ./test.sh -h -p 引数は 2 個指定されています。 |
test 条件式1 -o 条件式2 |
[ 条件式1 -o 条件式2 ] |
(or)少なくとも条件式1か条件式2のどちらか一方が真であれば真 |
-a
は -o
より優先順位が上。この優先順位を変更するには ( ) によるグルーピングを用いる。-a
や -o
の場合は括弧の直前にバックスラッシュを記してエスケープ処理を行う必要がある。また、&& や || で記述することもできる。この場合、エスケープ不要[15]。
$ cat test.sh #!/bin/sh A="" B="" # ($A が空文字ではない ことはない)かつ($B が空文字ではない ことはない) if [ ! -n "$A" -a ! -n "$B" ];then # =if [ -z "$A" -a -z "$B" ];then echo "All empty" else echo "No" fi $ ./test.sh All empty
expr
を用いると正規表現を使った条件判定が、case
を使うとワイルドカードによる条件判定が記述できる13。expr
についてはリンク先参照のこと。case
については下記参照。
【書式】 case "変数名" in パターン1) 処理内容1 ;; パターン2) 処理内容2 ;; ... *) # 上記いずれの条件にも当てはまらなかった場合(if文のelseに相当)14 処理内容x ;; esac
*
a*
=aで始まる文字列)?
?b*
=2文字目がbある文字列)[文字クラス]
[A-Z]*
=大文字で始まる文字列)
|
yellow|red|pink
=yellow,red,pinkいずれかの文字列)
basename
は指定したパス文字列からファイル名部分を取り出す。最後のスラッシュより後の文字列を取り出す操作に相当する。第2引数として拡張子(サフィックス)を指定すると、ファイル名の末尾から拡張子部分を除去する。
$ basename '/home/user/hoge.txt' hoge.txt $ basename '/home/user/hoge.txt' '.txt' hoge 【例】拡張子が.gzであれば(ファイルの末尾から.gzを除去すると値が変わるのであれば)解凍を行う if [ "`basename $FILE`" != "`basename $FILE '.gz'`" ];then gzip $FILE fi 参照:if、test、gzip
一方、dirname
はパス部分を取り出す。最後のスラッシュ以降を削除した文字列を返す。
$ dirname '/home/user/hoge.txt' /home/user
スクリプトで自身をパス、ファイル名を取得すると相対パスになっている。
[test.sh] #!/bin/sh echo dirname $0 $ ./test.sh ./test.sh
【例】 $ cat test.sh #!/bin/sh echo $(cd $(dirname $0);pwd)"/"`basename $0` $ ./test.sh /home/user/test.sh
read
は指定した入力から1行読み取る。-uオプションの指定がない場合は標準入力から、-uオプションの指定がある場合は-uオプションで指定したファイルディスクリプタから読み込む25。
【書式】 read オプション 受領変数名...
-a 配列変数名
-d 行末文字
-e
readline
を用いる。
-n 読み込み文字数
-p
-r
-s
-t 秒
-u ファイルディスクリプタ
受領変数名
【例1】ファイルリストを出力 $ cat ./test1.sh #!/bin/sh ls -l | while read line; do echo $line done $ ./test1.sh 合計 69044 -rw-rw-r-- 1 user user 258 7月 12 16:25 test.txt -rwxrwxr-x 1 user user 136 12月 2 14:46 test1.sh -rwxrwxr-x 1 user user 136 12月 2 14:46 test2.sh -rwxr-xr-x 1 user user 10230 11月 1 16:30 test.jpg 【例2】ファイルリストを出力(readを使わず同じ内容を実装したもの) [test2.sh] #!/bin/sh for file in `ls`; do echo $file done $ ./test2.sh test.txt test1.sh test.jpg
以下の方法でテキストファイルの内容を読み取って各行に処理を行うことができる26。
BUFIFS=$IFS # 変数IFSの値をバックアップ IFS= # 変数IFSの値を空にする exec 3< 入力ファイル名 # ファイルを入力とするファイルディスクリプタ3番を実行 while read FL 0<&3 # 3番の入力・エラーを0番(標準入力)にリダイレクトし、各行 $FL に代入 do 処理内容 done exec 3<&- # 3番の入力を標準入力に戻す IFS=$BUFIFS # 変数IFSの値を書き戻す
確認プロンプトを表示することにも使える27。
$ cat test.sh #!/bin/sh read -p "本当に実行していいですか?[Y/n] " -n 1 -r echo # プロンプトの後に改行 # 角括弧は二重にしないとエラーになる。正規表現の角括弧で条件節が終わってしまうため? # なお二重角括弧はbashビルトインというだけで一重と基本的な違いはないらしい[22] if [[ $REPLY =~ ^[Yy]$ ]] then echo "あ〜あ、やっちゃった!" elif [[ $REPLY =~ ^[Nn]$ ]] then echo "よくぞ考え直した!" fi $ ./test.sh 本当に実行していいですか?[Y/n] y あ〜あ、やっちゃった! $ ./test.sh 本当に実行していいですか?[Y/n] n よくぞ考え直した!
参考文献・サイト:
スクリプト実行時指定した引数を引用するには、$数値
と記す。数値には最初の引数を1番として、何番目の引数かを記す。
【スクリプト内容】(test.sh) #!/bin/sh echo $1/$2 【実行内容】 shell> test.sh hogehoge fugafuga hogehoge/fugafuga
その他、既定変数も含め以下に記す[11]。
変数 | 内容 |
---|---|
$# |
引数の個数 |
$* |
引数全てを含む配列。$* も $@ も挙動は同じ?
$ cat test.sh #!/bin/bash echo '$* = '$* echo '"$*" = '"$*" echo '$@ = '$@ echo '"$@" = '"$@" $ ./test.sh hoge foo $* = hoge foo "$*" = hoge foo $@ = hoge foo "$@" = hoge foo |
$@ |
|
$0 |
スクリプト自身のファイル名 |
$数値 |
数値番目の引数の値 |
$$ |
スクリプト自身のプロセスID(PID) |
$! |
スクリプトが最後に呼び出したバックグラウンドプロセスのプロセスID(PID) |
$? |
最後(直前)に実行したコマンドの終了コード(戻り値) |
bash, kshで最後の引数を得るには ${@: -1}
を参照すればよい32
sleep
コマンドは引数に指定した時間だけ停止して待つ。サスペンド中もカウントを続け、フォアグラウンドに来たときに時間が残っている場合は、再び停止して待つ。
【書式1】 sleep 数値単位 【書式2】標準出力に使用方法のメッセージを出力して正常終了する sleep --help 【書式3】標準出力にバージョン情報を出力して正常終了する。 sleep --version
出展:man sleep (GNU Shell Utilities 2.1 18 June 2002)
exit
は指定した数値のステータスでシェル(スクリプト)を終了させる。引数の指定を省略した場合の既定値は、最後に実行したコマンドの終了ステータス。EXIT信号のトラップはシェルが終了する前に行われる。
exit 数値
スクリプト内でにおける関数の定義するには以下のように記す12。関数の引数を関数内で参照する方法はスクリプトの場合と同様(→引数、既定変数)。関数は呼び出すよりも前に定義しておく必要あり。
【書式】 # 定義 関数名() { 処理内容 } # 呼び出し(引数なし) 関数名 # 呼び出し(引数あり) 関数名 引数1 引数2 ... 【例】 #!/bin/sh sub_sv1() { echo "This is server1." } sub_not_sv1() { echo "This is not server1." } if [ "`hostname`" = "server1" ];then sub_sv1 else sub_not_sv1 fi
なお、シェルスクリプトの関数が返す値は、関数の終了ステータス(問題なく終了すれば0、何らかの問題があれば0以外)。数値以外の
関数の引数として配列を直接指定することはできないが、回避方法は存在する。
【方法1】shiftでずらして一つずつ受領していく?22。
【方法2】変数名を渡し、変数を評価して値を得る関節参照の手法を使う23。
$ cat test.sh function add_parenthesis() { local arrayname=$1 eval ref=\"\${$arrayname[*]}\" for item in ${ref[*]} do echo "<$item>" done } dnabases=( "adenine" "guanine" "thymine" "cytosine" ) # 引数には$をつけない(変数そのものではなく変数名を文字列で指定する) add_parenthesis dnabases $ ./test.sh <adenine> <guanine> <thymine> <cytosine>
return
コマンドを関数定義の中で使用した場合、指定した終了ステータスを返して関数処理を終了させる。終了ステータスの指定を省略した場合の既定値は、関数内で最後に実行されたコマンドの終了ステータス。
If used outside a function, but during execution of a script by the . (source) command, it causes the shell to stop executing that script and return either n or the exit status of the last command executed within the script as the exit status of the script. If used outside a function and not during execution of a script by ., the return status is false. Any command associated with the RETURN trap is executed before execution resumes after the function or script.
(man return, GNU Bash-3.0 2004 Apr 20 BASH_BUILTINS(1))
return 終了ステータス
変数に格納されたコマンド文字列を実行するには eval を使う。
【書式】
eval 引数 ...
【例】master.txtのコピーを10個つくる $ cat ./makecopy.sh #!/bin/sh i=1 while [ $i -le 10 ] do cmd="cp -f master.txt copy"$i".txt" echo $cmd eval $cmd i=`expr $i + 1` done $ ls makecopy.sh master.txt $ ./makecopy.sh cp -f master.txt copy1.txt cp -f master.txt copy2.txt cp -f master.txt copy3.txt cp -f master.txt copy4.txt cp -f master.txt copy5.txt cp -f master.txt copy6.txt cp -f master.txt copy7.txt cp -f master.txt copy8.txt cp -f master.txt copy9.txt cp -f master.txt copy10.txt $ ls makecopy.sh master.txt copy1.txt copy2.txt copy3.txt copy4.txt copy5.txt copy6.txt copy7.txt copy8.txt copy9.txt copy10.txt
参考文献・サイト
exec
は指定したコマンドを実行する。
コマンドが何らかの理由で実行できなかった場合、execfailのシェルオプションが有効になっていない限り非対話的なシェルは終了する。execfailが有効であった場合、シェルはfailureを返す。ファイルが実行できなかった時、対話的シェルはfailureを返す。コマンドを指定しなかった場合、あらゆるリダイレクトが現在のシェルで有効となり、0のステータスを返す。リダイレクトにエラーがあった場合、1のステータスを返す。
exec コマンド コマンド引数
-l
login
コマンドが実行される際行われていることである。
-c
-a
【例】 $ echo test.sh #!/bin/bash CMD="ls" # -n オプションがあるとコマンド自体を表示、ないとコマンド実行結果を表示 if [ "$1" = "-n" ] then echo $CMD else exec $CMD fi $ ./test.sh hoge.php hoge.txt hoge.log test.sh $ ./test.sh -n ls
RHEL 5 や CentOS 5 の起動スクリプトに以下の記述を行っておくと、 プロセスIDの記録などの自動的に行ってくれるなどコードの記述が楽になる。
# Source function library. . /etc/rc.d/init.d/functions # Source networking configuration. . /etc/sysconfig/network
以下はこれを用いた事例
#!/bin/bash # # foo This script takes care of starting and stopping # foo daemon # # Source function library. . /etc/rc.d/init.d/functions # Source networking configuration. . /etc/sysconfig/network RETVAL=0 # 返り値初期値 prog="foo" # 表示用プログラム名 progpath="/usr/sbin/foo" # プログラムの絶対パス CONF=`ls /etc/foo.conf 2>/dev/null` # 設定ファイルの絶対パス(存在しなければ空) # 起動用サブルーチン start() { # プログラムが存在しかつ実行可能である or ステータス4で終了する [ -x "$progpath" ] || exit 4 # 設定ファイル名が空 and ステータス6で終了する [ -z "$CONFS" ] && exit 6 # プログラム名、設定ファイル名(拡張子除去)を出力 echo -n $"Starting $prog for "`basename $CONF .conf`": " # プログラムをデーモンとして起動(書式はプログラムにより異なる) daemon $progpath $CONF # 前行のデーモン起動処理の返り値を返り値に代入 RETVAL=$? # 返り値が0(正常起動)ならlockファイル作成(ファイル名は表示用プログラム名) [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog # 改行 echo # 返り値を返す return $RETVAL } # 停止用サブルーチン stop() { # プログラム名を出力 echo -n $"Shutting down $prog: " # プログラム名を停止 killproc $prog # 前行のデーモン起動処理の返り値を返り値に代入 RETVAL=$? # 改行 echo # 返り値が0(正常終了)ならlockファイル削除 [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog # 返り値を返す return $RETVAL } # 引数による条件分岐 case "$1" in start) start ;; stop) stop ;; restart|reload) stop start RETVAL=$? ;; condrestart) if [ -f /var/lock/subsys/$prog ]; then stop start RETVAL=$? fi ;; status) status $prog RETVAL=$? ;; *) echo $"Usage: $0 {start|stop|restart|condrestart|status}" exit 2 esac exit $RETVAL
【例】ただ実行日時をログに出力するだけのスクリプト $ cat timestamp.sh #!/bin/sh LOG="/var/log/"`basename "$0" | sed -e 's/\.sh$/.log/'` echo `date "+%Y-%m-%d %H:%M:%S %Z(%z)"` >>${LOG} $ ./timestamp.sh $ cat /var/log/timestamp.log 2012-02-13 15:49:22 JST(+0900)
【例】 $ cat remove_old.sh #!/bin/sh # 第1引数に指定したファイルを処理対象とする LOG=$1 # 最大行数(これを越えると古い上部の行を削除する) MAXLINES=100 # 指定ファイルの行数 LINES=`cat $1 | wc -l` # 最大行数より多い if [ ${LINES} -gt ${MAXLINES} ];then # 末尾指定行を一時ファイルに書き出し tail -n ${MAXLINES} ${LOG} >${LOG}".tmp" # 元のファイルに上書き mv -f ${LOG}".tmp" ${LOG} echo "The number of lines (${LINES}) reachs the limit (${MAXLINES}) to remove the upper part." # 最大行数以下 else echo "The number of lines (${LINES}) doesn't reach the limit (${MAXLINES})." fi $ cat test.txt | wc -l 99 $ ./remove_old.sh test.txt The number of lines (99) doesn't reach the limit (100). $ ls -l >>test.txt $ cat test.txt | wc -l 105 $ ./remove_old.sh test.txt The number of lines (105) reachs the limit (100) to remove the upper part. $ cat test.txt | wc -l 100