while
test が true の間実行
while test; do
;; some code
done
until
test が false の間実行
while test: do
;; some code
done
for
それぞれのワードがVARに入る。ワードのクオートは不要
for VAR in WORDS; do
;; some code
done
例えば次のようなコマンドの実行。ls
の実行結果を一つづつループで回している。
$ for i in $(ls); do echo $i; done
answer
bin
hello_world.sh
hw
hw.h
logfile
ls.txt
output.sh
tn.sh
variables
ちなみに、ダブルクオートで変数をくくると1つの変数として見なされる。
$ for i in hello world tsuyoshi; do echo $i; done
hello
world
tsuyoshi
$ for i in "hello world tsuyoshi"; do echo $i; done
hello world tsuyoshi
Sample
サンプルは、引数を指定すると、該当の拡張子の前に第3引数の拡張子を挿入してくれるプログラム。
ちなみに、echo mv ...
のパートはデバッグテクニックらしく、これで一旦実行して、実際に思った通りだったら、echo
を外して実行する。特にforが該当の拡張子だけで回るのはカッコいい。
forsample
# !/bin/bash
if [[ $# -ne 2 ]]; then
echo "Two parameters requires"
fi
for f in *"$1"; do
base=$(basename "$f" "$1")
echo mv "$f" "${base}$2.$1"
done
実行
実行時のtouch の使い方もカッコいい
$ touch file{1..10}.txt
$ touch image{1..5}.img
$ ls
file1.txt file3.txt file6.txt file9.txt image3.img
file10.txt file4.txt file7.txt image1.img image4.img
file2.txt file5.txt file8.txt image2.img image5.img
$ ./forsample txt bak
mv file1.txt file1.bak.txt
mv file10.txt file10.bak.txt
mv file2.txt file2.bak.txt
mv file3.txt file3.bak.txt
mv file4.txt file4.bak.txt
mv file5.txt file5.bak.txt
mv file6.txt file6.bak.txt
mv file7.txt file7.bak.txt
mv file8.txt file8.bak.txt
mv file9.txt file9.bak.txt
C-Style Loop
for (( INIT; TEST; UPDATE )); do
;; some code
done
counter
# !/bin/bash
for (( i=0; i<10; ++i )); do
echo "number ${i}"
done
実行
$ ./counter
number 0
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9
ワンライナーでやろうと頑張ってみてうまくいかなかったが、do の後に;
をつけていたのを削除するとうまくいった。
$ for (( i=0; i<10; ++i )); do echo "number ${i}"; done
number 0
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9
drawline
サンプルが面白かったので、そのまま写経してみた。
# !/bin/bash
if [[ ! $1 ]]; then
echo "need line length argument" >&2
exit 1
fi
if [[ $1 =~ ^[0-9]+$ ]]; then
length="$1"
else
echo "Length has to be a number" >&2
exit 2
fi
char="="
if [[ $2 ]]; then
char="$2"
fi
line=
for (( i=0; i<length; ++i )); do
line="${line}${char}"
done
printf "%s\n" "$line"
exit 0
実行
$ ./drawline 10
==========
$ ./drawline 10 --
--------------------
ここでポイントは、最後の printf "%s\n" "$line"
でこれが printf "$line\n"
だと Substitution なので、最初の文字が-
の場合、オプションと認識されてうまくいかない。フォーマットを使うことで回避できている。
Break and Continue
- break: ループをやめる
- continue: 現在のイテレーションをスキップして、次から始める
他の言語と同じ。for, while, until で使える。次のサンプルは、ファイルの中をサーチして、該当の行が来たら表示するもの。
mysearch
# !/bin/bash
if [[ ! $1 ]]; then
echo "First argument missing" >$2
exit 1
fi
flag=
while read -r; do
if [[ $REPLY =~ $1 ]]; then
echo "$1 found: $REPLY"
flag="done"
break
fi
done
if [[ ! $flag ]]; then
echo "$1 not found."
fi
exit 0
data.txt
This is the text.
You need to find a text.
It might be foo.
Also might be bar.
But it is crosstrek.
Keep on loving Subaru.
See you soon.
Regards,
Tsuyoshi
実行
$ ./mysearch crosstrek < ./data.txt
crosstrek found: But it is crosstrek.
$ ./mysearch wrx < ./data.txt
wrx not found.
例によって元のサンプルにセンスを感じたので写経。$REPLY
の使い方が渋い
他の Pre-defined の変数はこちら。9.1. Internal Variables。
デフォルトの変数を使うことで、シンプルに感じる。
# !/bin/bash
if [[ ! $1 ]]; then
echo "First argument missing" >&2
exit 1
fi
found=""
while read -r; do
if [[ ! $found ]]; then
if [[ $REPLY =~ $1 ]]; then
found="yes"
else
continue
fi
fi
echo "$REPLY"
done
exit 0
実行
最後の行が表示されないが、それはもうすぐわかるらしい。
$ ./stripto crosstrek < data.txt
But it is crosstrek.
Keep on loving Subaru.
See you soon.
Regards,
case
case WORD in
PATTERN1)
some code;;
PATTERN2)
some code;;
:
esac
他の言語と同じ
Sample
animals
# !/bin/bash
case $1 in
tiger)
echo "Super cool!";;
carp)
echo "So so";;
giant)
echo "Boo!.";;
*)
echo "I don't think about it";;
esac
実行
$ ./animals tiger
Super cool!
$ ./animals giant
Boo!.
$ ./animals star
I don't think about it
サンプル2
もうすこし複雑なパターンマッチも可能な様子
filetype
# !/bin/bash
case $1 in
*.png|*.jpg|*.gif)
echo "$1 is image";;
*.cs|*.java|*.js|*.sh)
echo "$1 is programming language source code";;
*)
echo "Don't ask me, I don't know";;
esac
exit 0
実行
$ ./filetype some.jpg
some.jpg is image
$ ./filetype Program.cs
Program.cs is programming language source code
$ ./filetype some.txt
Don't ask me, I don't know
Command Groups
{}
で括られたコマンド。複数のコマンドを一つのステートメントに入れる。I/O のリダイレクトをグループに適用できる。if や while ループに適用できる。リターンステータスは、最後のコマンド実行の結果になる。
{cmd; cmd2; cmd3;}
というスタイル。セミコロンで新しい行を表現する。ブレースの周りにはスペースが必要。セミコロンで終了すること。
|| and &&
論理演算ではない用途でも使う。コマンドの実行結果によって、後続のコマンドの実行を決める。
&&
先行したコマンド実行が成功した時のみ実行する。例えば次のはディレクトリ作成が成功した時のみ、そのディレクトリに移動する。
$ mkdir newdir && cd newdir
||
直前のコマンドが失敗した時のみ、次のコマンドを実行する。次のコマンドは、引数がない時のみ、エラーメッセージを表示する。
[[ $1 ]] || echo "missing argument">&2
例えば次のスクリプトは常に異常終了する。$1があったら、それはそれで正常終了だし、echoでエラーメッセージを表示しても、表示自体は成功するので、常に、exit 1
になる。
[[ $1 ]] || echo "missing argument" >&2 && exit 1
こういう時には、コマンドグルーピングを使う
[[ $1 ]] || { echo "missing argument">&2; exit 1;}
サンプル
先ほどのサンプルを改造する。検索した文字列のある行以降を表示する。前は、最後の行が表示されない問題があった。その原因はwhile read -r; do
で最後のEOFを判断しているが、最後の行がTsuyoshiEOF
となっている場合は、ここでtrueとなるため、最後の行が表示されない。read -f
が false になった時のみ、[[ $REPLY ]]
を評価する。もし、他に文字列が含まれていたらループを実行を続行する。
他には、コマンドグループを使って、文字列チェックを一業で記述している。
# !/bin/bash
[[ $1 ]] || { echo "First argument missing" >&2; exit 1; }
found=""
while read -r || [[ $REPLY ]]; do
if [[ ! $found ]]; then
if [[ $REPLY =~ $1 ]]; then
found="yes"
else
continue
fi
fi
echo "$REPLY"
done
exit 0
実行
ちゃんと最後の行が表示されている。
$ ./stripto crosstrek < data.txt
But it is crosstrek.
Keep on loving Subaru.
See you soon.
Regards,
Tsuyoshi
invincible:forloop ushio$ ./stripto
First argument missing