【初心者向け】if文、for文、while文を書くときのテンプレート【もう迷わない】

Bash初心者のために、制御文（if文、for文、while文）の書き方をテンプレ化してみました。

ちなみに`IFS`の値は、こうなっていること前提です。

```bash:IFSの設定値（空白、水平タブ、LF）
$ printf "${IFS}" | od -An -tx1
 20 09 0a
```

```bash:bashのバージョン
$ bash --version
GNU bash, version 4.3.46(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```

#ふつうのフレンズたち
##if文のフレンズ
```bash:if文のフレンズ
# 条件部がコマンド（一般形式）
if command; then
    echo hoge
elif command; then
    echo fuga
else
    echo hogefuga
fi

# true, falseコマンド
if true; then
    echo hoge
fi

# testコマンド
if [ 1 -eq 1 ]; then
    echo hoge
fi

# 複合コマンド（[[]]）
if [[ 1 -eq 1 ]]; then
    echo hoge
fi

# 複合コマンド（算術式）
if ((1==1)); then
    echo hoge
fi
```

##for文のフレンズ
```bash:for文のフレンズ
# 直書きリストのイテレーション
for x in aaa bbb ccc; do
    echo "$x"
done

# 変数に格納された値をイテレーション
xs="aaa bbb ccc"
for x in $xs; do
    echo "$x"
done

# GLOB展開の結果をイテレーション
for x in *.log; do
    echo "$x"
done

# コマンド置換の結果を単語単位でイテレーション
for x in $(command); do
    echo "$x"
done

# ファイルを単語単位でイテレーション（コマンド置換）
for x in $(cat sample.log); do
    echo "$x"
done

# ファイルを単語単位でイテレーション（コマンド置換（上より高速））
for x in $(<sample.log); do
    echo "$x"
done

# 整数のイテレーション（ブレース展開）
for x in {1..10}; do
    echo "$x"
done

# 整数のイテレーション（seqコマンド）
for x in $(seq 1 10); do
    echo "$x"
done

# C言語風のイテレーション
for((i=0; i<10; ++i)); do
    echo "$i"
done

# 配列のイテレーション
xs=("aaa" "bbb" "ccc")
for x in "${xs[@]}"; do
    echo "$x"
done 
```
##while文のフレンズ
```bash:while文のフレンズ
# 無限ループ
while true; do
    echo hoge
done

# ファイル行単位のイテレーション（パイプライン）
cat sample.log | while read line; do
    echo "$line"
done

# ファイル行単位のイテレーション（リダイレクト）
while read line; do
    echo "$line"
done <sample.log

# ファイル行単位のイテレーション（プロセス置換）
while read line; do
    echo "$line"
done < <(cat sample.log)
```

#ワンライナーのフレンズたち

ふつうのフレンズたちを、すべてワンライナーにしてみました。

```bash:ワンライナーのフレンズたち
# if文ワンライナーのフレンズ
if true; then echo "$x"; fi
if [ 1 -eq 1 ]; then echo hoge; fi
if [[ 1 -eq 1 ]]; then echo hoge; fi
if ((1==1)); then echo hoge; fi

# for文ワンライナーのフレンズ
for x in aaa bbb ccc; do echo "$x"; done
for x in $xs; do echo "$x"; done
for x in *.log; do echo "$x"; done
for x in $(cat sample.log); do echo "$x"; done
for x in $(<sample.log); do echo "$x"; done
for x in {1..10}; do echo "$x"; done
for x in $(seq 1 10); do echo "$x"; done
for((i=0; i<10; ++i)); do echo "$i"; done
for x in "${xs[@]}"; do echo "$x"; done 

# while文ワンライナーのフレンズ
while true; do echo hoge; done
cat sample.log | while read line; do echo "$line"; done
while read line; do echo "$line"; done <sample.log
while read line; do echo "$line"; done < <(cat sample.log)
 
```

#おまけのフレンズたち

##最終行の末尾改行の有無によって、イテレーションの結果が変わる

（このフレンズは「[bashで意外だったこと、普段気になってるアレは何なんだ？を記録するノート](http://qiita.com/yz2cm/items/6819ccf58ced6ef40b2a#%E6%9C%80%E7%B5%82%E8%A1%8C%E3%81%AE%E6%9C%AB%E5%B0%BE%E6%94%B9%E8%A1%8C%E3%81%AE%E6%9C%89%E7%84%A1%E3%81%AB%E3%82%88%E3%81%A3%E3%81%A6%E3%82%A4%E3%83%86%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E7%B5%90%E6%9E%9C%E3%81%8C%E5%A4%89%E3%82%8F%E3%82%8B)」より転載したものです）

複数行のデータを読み込んで`while`等でイテレーションする際、最終行の末尾に改行文字が存在しない場合、最終行が無視されてしまいます。

```bash:
$ printf "aaa\nbbb\nccc" | while read line; do echo $line; done
aaa
bbb
$ printf "aaa\nbbb\nccc\n" | while read line; do echo $line; done
aaa
bbb
ccc
$ while read line; do echo $line; done < <(printf "aaa\nbbb\nccc")
aaa
bbb
$ while read line; do echo $line; done < <(printf "aaa\nbbb\nccc\n")
aaa
bbb
ccc
```

最終行末尾に改行のないファイルを`cat`してパイプにつなげてテキスト処理する場合など、大変危険。

##if true; then ～; fi

`if`文の条件部に`true`や`false`を書く場合、以下のように書くのが正しいです。

```bash:○正しい
if true; then
    echo hoge
fi
if false; then
    echo hoge
fi
```

以下のように書くと「文字列`"true"`（`"false"`）の長さは1以上である」という条件になってしまうため、恒真条件になります。`[[ string ]]`は、`[[ -n string ]]`と同じ意味になるためです。

```bash:×誤り
if [ true ]; then
    echo hoge
fi
if [[ false ]]; then
    echo hoge
fi
```

##複合コマンドの条件部に複合コマンドを書く
条件部にはリストを書けるため、`if文`や複合コマンドを書くこともできます。

```bash:
while if true; then echo hoge; fi; do
    echo fuga
done
```

だから何だ！

##for x; do ～; done

`for`文は、以下のような書き方も可能です（`in $xs`の部分がない！）

```bash:
for x; do
    echo "$x";
done
```

この場合、位置パラメータ`$1`～`$N`をイテレーションします。

```bash:
$ bash -c 'for x; do echo "$x"; done' -s aaa bbb ccc
aaa
bbb
ccc
```

```bash:
$ f(){ for x; do echo "$x"; done;}
$ f aaa bbb ccc
aaa
bbb
ccc
```

```bash:
$ set +f; set -- aaa bbb ccc; set -f
$ for x; do echo "$x"; done
aaa
bbb
ccc
```

##for文のC言語風のイテレーションについて

@akinomyogaさんより頂きましたコメントにあります、`for((;;)); do`の`do`の直前のセミコロンの省略についてです。

以下の`for`文ですが、

```bash:
for((i=0; i<10; ++i)); do
    echo "$i"
done
```

僕の環境では、これの`++i)); do`のセミコロン`;`をつけずに以下のように書いても正常動作しました。

```bash:
for((i=0; i<10; ++i)) do
    echo "$i"
done
```

また、ループ本体を`{}`で括って、以下のように書いても正常動作しました（むしろ、こちらの方が有名ですよね）。

```bash:
for((i=0; i<10; ++i)){
    echo "$i"
}
```

C言語風の`for`ループに関しては記法がゆるやかなんだなあ…と思い、念のため`man bash`を確認したところ、`for((;;)); do :; done`形式の記法しか記載が無いではないですか。

```:man_bash「Compound　Commands」より抜粋
for name [ [ in [ word ... ] ] ; ] do list ; done
    The list of words following in is expanded, generating a list of items.  The variable name is set to  each  element  of  this
    list  in  turn, and list is executed each time.  If the in word is omitted, the for command executes list once for each posi‐
    tional parameter that is set (see PARAMETERS below).  The return status is the exit status of the last command that executes.
    If the expansion of the items following in results in an empty list, no commands are executed, and the return status is 0.

for (( expr1 ; expr2 ; expr3 )) ; do list ; done
    First,  the arithmetic expression expr1 is evaluated according to the rules described below under ARITHMETIC EVALUATION.  The
    arithmetic expression expr2 is then evaluated repeatedly until it evaluates to zero.  Each time expr2 evaluates to a non-zero
    value,  list is executed and the arithmetic expression expr3 is evaluated.  If any expression is omitted, it behaves as if it
    evaluates to 1.  The return value is the exit status of the last command in list that is executed, or false  if  any  of  the
    expressions is invalid.
```

というわけなので、僕はマニュアルに従って`for((;;)); do :; done`形式で書くようにしたいと思います（@akinomyogaさんのコメント以後、本記事当該箇所の`for`文にも念のため`;`をつける修正をしました）。

なお、上記のマニュアルの抜粋にもある通り、`in`句の無い`for`文に関しては`do`直前のセミコロンは必須ではないようです。

```bash:
$ bash -c 'for x do echo "$x"; done' -s aaa bbb ccc
aaa
bbb
ccc
$ bash -c 'for x; do echo "$x"; done' -s aaa bbb ccc
aaa
bbb
ccc
```