そのシェルスクリプトもうちょっとシンプルに書けそう Tips集（Golf/シェル芸ではない）


[Shell Script Advent Calendar 2015](http://qiita.com/advent-calendar/2015/shell-script) 4日目 の投稿です。

以前から自分用にメモしていたものを文字起こししました。

# :zero: はじめに

仕事でシェルを使い始めて3年くらい経ちました。
途中、python や ruby でスクリプト作ったり、ちょっと zsh に浮気したりしましたが、なんだかんだで今も Bash を使うことが多いです。

この3年間、スーパーシェル芸人(@ebanさん)にご教授頂いたり、Golfしたり（@ebanの影響）、シェル芸勉強会に参加したり（@ebanの影響）してきました。
そんな3年間のまとめとして、シェルスクリプト初めましてだった3年前の私に向けたTips集を書いてみました。

# :exclamation: 趣旨

各項目ごとに、まず初心者（過去の私がやってた）あるある実装を例示して、その次に、より良さげな実装を例示する構成としています。

# :computer: 実行環境

* OS
	* Mac OS X Yosemite
	* Mac OS X El Capitan (途中から)
* Shell 
	* bash 4.3
		* brew で bash 入れたので 4.3 になってます。
		* 特に手入れしていなければ、お手元の Mac OS X は、3.2 あたりと思いますのでご注意を。

他の環境では検証できていません。だいたい動くと思いますが...。

# :notebook: Bashの変数であるある
## 変数の初期化

シェルと触れ合ったばかりの私は、いつもこんな感じで実装してましたね。

```bash:分かりやすいあるある例
# 引数があればそれを、無ければデフォルト値で初期化
if [ -z "$1" ]; then
    hoge="default"
else
    hoge=$1
fi
```

いえ、悪いわけではないです。他の言語と似た形なので、誰にでも分かりやすいと思います。

ただですね、Bashの変数には便利な機能があります。
そんな便利機能の一つ、`:-` を使うと、このif文は不要です。

```bash:初期化なんて簡単
hoge=${1:-default}
```

一発ですね。え、暗号めいていて初見さんお断りじゃないかって？
でも`:-` 以外に、`:+` や `:?` など、本当に便利なんですよ。たくさんあるんです。使いたいんです。
覚えておいて損のない便利さだと思いますよ。

参考) http://qiita.com/bsdhack/items/597eb7daee4a8b3276ba

### 補足) `"` は必要？

@magicant さんから
クォート漏れ、細かいミスの指摘・修正の編集リクエストをいただきました。ありがとうございます！
(チェック不足でした...:sweat:)

例えばクォート漏れの指摘の一つは、
`[ -z $1 ]` → `[ -z "$1" ]`
というものでした。

`$1` が、`"hoge"` とか `123` なら、`""` がなくても動きます。
ただ、空白のみや、`-r hoge` のような文字列だと厄介なことになります。

```bash:$1と"$1"
[ -z $1 ] && echo '1) $1 is zero length'
[ -z "$1" ] && echo '2) $1 is zero length'
```

```bash:↑スクリプトを色んな引数で試してみる
# 空文字
$ bash a.sh ""
1) $1 is zero length
2) $1 is zero length

# 空白のみ
$ bash a.sh " "
1) $1 is zero length

# 空白たくさんでも？
$ bash a.sh "       "
1) $1 is zero length

# -から始まると？
$ bash a.sh "-b hoge"
a.sh: 2 行: [: -r: 二項演算子が予期されます
```

最後の例だと `[ -z -r hoge ]` と展開されてから実行されることになります。
`"` で囲んでいれば `[ -z '-r hoge' ]` と、文字列として扱われるので問題ないのです。

面倒がらず `""` でちゃんと囲まないとですね。

## 変数に値があった場合のみオプションを追加する

```bash:分かりやすいあるある例2
if [ -n "${a_val}" ]; then
    a_opt_val="-a ${a_val}"
fi
something_cli ${a_opt_val} sub_cmd
```

`${a_val}` に何か文字が入っていたら、`a_opt_val="-a ${a_val}"` という変数を定義してるんですね。とても分かりやすいです。

でも、`:+` を使えばこのif文は不要です。

```bash:変数に値があったらオプションにする
a_opt_val="hoge"
something_cli ${a_opt_val:+-a "${a_opt_val}"} sub_cmd
# この時、${a_opt_val:+-a "${a_opt_val}"}は -a hoge となる。
```

`a_opt_val="-a hoge"` ではなく、`a_opt_val="hoge"` にしている理由ですか？
変数 a_opt_val の値を気軽に使い回せるからです。

## 変数の未定義とTypoに気付きたい

変数とTypoは人類永遠の課題ではないでしょうか。
Typoをしないプログラマは、神の手（とキーボード）を持っているか、あるいはTypoしないようプログラムされた存在（ロボット）以外に存在しないのではと思います
。
そのくせ、Typoは恐怖の処理を招きます。

```bash:恐怖のTypo
tmp_dir="myTmp"
rm -rf ${tmo_dir}/* # => Don't pleeeeeeeease!!
```

そんなのエディターやIDEに任せるべき？
それも一つの道でしょう。私達が真に戦うべき相手はTypoなんかじゃないはずです。

とはいっても、Typoじゃなくても、実装ミスや勘違いで変数の未定義・空文字となってしまうことはありますよね。

### 変数の未定義チェック

```bash:if文じゃないと不安だったあの頃
if [ -z "${forgetful_var}" ]; then
  echo "forgetful_var is not defined!!"
  exit 1
fi
```

やっぱり `if [ -z ${var} ]` の構成は分かりやすいですからね。
でも、変数が出てくる度に全部 `if` でチェックするつもりですか？

```bash:関数を覚えたあの頃
function check_defined() {
  if [ -z "${1}" ]; then
    echo "Not defined!!"
    exit 1
  fi
}

check_defined ${forgetful_var_1}
check_defined ${fargetful_var_2}
check_defined ${forgetful_var_3}
```

なるほど、関数化すればif文は１つになりますね。
ただ、これって、どの変数が未定義だったのか分かりにくいですよね。
だってどの変数が未定義でも `"Not defined!!"` って出力されるんですから。

ともあれ、おもむろに `set -u` しましょうか。

```bash:未定義な変数を使った段階でエラー
set -u
forgetful_var_1="hoge"
forgetful_var_2="hoge"
forgetful_var_3="hoge"

echo ${forgetful_var_1}
echo ${fargetful_var_2}
echo ${forgetful_var_3}
```

```bash:実行結果
hoge
a.sh: 行 7: fargetful_var_2: 未割り当ての変数です
```

単純明快ですね。
参考) http://qiita.com/m-yamashita/items/889c116b92dc0bf4ea7d#-u

え、例えば変数 `forgettable`は、未定義でも問題ないようにしたい？
一旦 `set +u` するか、`${forgettable:+${forgettable}}` なら、`forgettable`が未定義でもエラーになりません。

```bash:未定義検知を一時的に無効にする
set -u
str="hoge"
echo ${str}

set +u
echo ${forgettable}
set -u
```
```bash:検知されない書き方にする
set -u
str="hoge"
echo ${str}
echo ${forgettable:+$forgettable}
```

こんな感じです。

おっと、未定義だけでなく空文字も許したくない、ですか？
`set -u` は未定義しか検知しませんからね。
そんな時は `:?` を使うとお手軽です。

```bash:特定の変数のみチェック
should_be_defined=
: ${should_be_defined:?} #=> 空文字も許さない
```

```bash:実行結果
a.sh: 行 2: should_be_defined: パラメータが null または設定されていません
```

`:` コマンドという何もしないコマンドと組み合わせれば、もはやこれ以上ないシンプルさですね。

#### 補足) shellcheck

シェルスクリプトにおける、いわゆるLintのようなツール `shellcheck` というものがあります。
`brew install shellcheck` でさくっとインストールできます。
これを使うと、未定義だけでなく、未使用な変数の検出をしてくれます。

```bash:shellcheck流してみる
$ cat a.sh
#!/bin/bash
echo "${no_defined_var}"
no_use="var"

$ shellcheck a.sh 

In a.sh line 2:
echo "${no_defined_var}"
      ^-- SC2154: no_defined_var is referenced but not assigned.


In a.sh line 3:
no_use="var"
^-- SC2034: no_use appears unused. Verify it or export it.
```

他にも色々検出してくれますが、あまり詳しくないので紹介のみに留めます。

**2015/12/06 追記**

@b4b4r07 さんから
typoの指摘・修正いただきました。ありがとうございます。
(shellcheckのtypoがちらほら...)


## 変数の変数

変数に慣れてくると、こんな事したくなりますよね。

```bash:変数の変数やりたい（失敗）
mark_base="@(^ ^)@"
markA=mark_base #=> markA="mark_base" と同じ意味です
echo ${markA}
 #=> @(^ ^)@ と出力したい（実際は mark_base と出力される）
```

そういえば、bashの変数って `${parameter}` でしか値を取得できないんでしたね。

でも他の言語なら、変数を変数に代入するのって普通のことじゃないですか。

```ruby:例えばRubyなら？
mark_base = "@(^ ^)@"
markA = mark_base
p markA
 #=> "@(^ ^)@"
```

ググったら `eval` で頑張る方法が出てきましたか？
ああー、なるほど確かにできますね。

```bash:変数の変数eval版
mark_base="@(^ ^)@"
markA=mark_base
echo `eval echo '$'$markA`
 #=> "@(^ ^)@"
```

なんとか出来ました。でも、あまり気持ちのいい感じしませんね。それに `eval` は難しいです。
いっそ `declare -A` で連想配列を作った方が分かりやすいでしょう。

```bash:連想配列でがんばる版
declare -A marks
marks=(
["mark_base"]="@(^ ^)@"
)
markA=mark_base
echo ${marks[${markA}]}
 #=> "@(^ ^)@"
```

シンプルさからは遠く、ちょっと大げさな感じですね。
しかし、やっぱり変数の変数って難しいのでしょうか。
と思ったら、`${!var}` というのがあるんですよ。

```bash:eval使わなくていい版
mark_base="@(^ ^)@"
markA=mark_base
echo ${!markA}
 #=> "@(^ ^)@"
```

ちなみに man には次の説明しか書いてないです。

> ${parameter}
> (中略)
> If the first character of parameter is an exclamation point (!), it introduces a level of variable indirection.  

`${!parameter}` と分かりやすく区切られていないんです。これは気付きませんね。
man ですら探し辛い書き方なので、使う時はコメントアウトを添えておくと（数カ月後の自分に）親切かもしれませんね。

# grep あるある
## 特定のプロセスを確認
### コンソールで試すがままなgrep

```bash:初心者あるある例
% ps aux | grep jenkins | grep -v grep | grep java
yasuhiroki      24501   0.0  3.9  6421828 654108   ??  S    11:22PM   1:16.61 /usr/bin/java -Dmail.smtp.starttls.enable=true -jar /usr/local/opt/jenkins/libexec/jenkins.war
```

1. jenkinsでgrepしたら
1. grep自身のプロセスが出てきたので-vで取り除き
1. jenkinsのつくプロセスが他にもあったから
1. とりあえず　java でもう一回 grep

そんな感じで気付いたらパイプの連続です。
いえ気持ちは分かります。試しながらパイプで繋いでいれば、こうなりますよね。
でも、さすがに余計な処理が多すぎますね。

まず、grep自身のプロセスの取り除くには、ちょっと正規表現を使うだけで達成できます。
↓は良く見かける例です。

```bash:grep自身のプロセスを取り除くサンプル
ps aux | grep "ps au[x]"
```
```bash:実行例
# grep "ps aux" も検出しちゃう
$ ps aux | grep "ps aux"
yasuhiroki       5948   0.0  0.0  2457428    460 s000  U+   11:49PM   0:00.00 grep ps aux
root             5947   0.0  0.0  2437344   1008 s000  R+   11:49PM   0:00.00 ps aux

# []付けるだけで取り除ける
$ ps aux | grep "ps au[x]"
root             5949   0.0  0.0  2436320   1000 s000  R+   11:50PM   0:00.01 ps aux
```

`grep "ps au[x]"` という文字列は、正規表現 `"ps au[x]"` ではマッチしないですからね。

さて、今回欲しいのは、javaコマンドで jenkins.warを引数にしているプロセスなのでしたね。
では、そのように書きましょう。

```bash:正規表現
% ps aux | grep "java.*jenkins\.war"              
yasuhiroki      24501   0.0  3.9  6421828 654112   ??  S    11:22PM   1:16.72 /usr/bin/java -Dmail.smtp.starttls.enable=true -jar /usr/local/opt/jenkins/libexec/jenkins.war
```

java の `grep` も、`grep -v` も不要になりました。
`jenkins\.war` が地味にミソです。`jenkins.war` としてしまうと、`grep` 自身のプロセスも引っかかってしまいます。
考えるのが面倒なら、`java.*jenkins\.wa[r]` と、とりあえず最後の文字を `[]` で囲んでしまいましょうか。

そういえば、どうして jenkins のプロセスを探していたのです？

### 特定のコマンドのプロセス番号が知りたい

```bash:初心者よくあるやつ
% ps aux | grep "java.*jenkins\.war" | awk '{print $2}'
24501
```

pgrep を使いましょう。

```bash:pgrepで一発
% pgrep -f "java.*jenkins\.war"
24501
```

ああ、シンプル。

### 特定のコマンドの引数を知りたい

これはまあ、`grep` で良いような気がしますね。
ちょっと正規表現修正して `-o` すれば済みます。

```bash:grepのオプション使おう
# コマンド名 java を残して良いなら
$ ps aux | grep -o "java.*jenkins\.war.*"
java -Dmail.smtp.starttls.enable=true -jar -Xms512m -Xmx2048m -XX:MaxPermSize=128m -Dfile.encoding=utf-8 /usr/local/opt/jenkins/libexec/jenkins.war --httpPort=8080

# コマンド名 java を残したくないなら（引数だけにしたいなら）
$ ps aux | grep -o "java.*jenkins\.war.*" | cut -d' ' -f 2-
-Dmail.smtp.starttls.enable=true -jar -Xms512m -Xmx2048m -XX:MaxPermSize=128m -Dfile.encoding=utf-8 /usr/local/opt/jenkins/libexec/jenkins.war --httpPort=8080
```

一応、代替案も考えてみましたが、これだ！ というものがありませんでした。

```bash:うーん
# Linuxなら取れるんだけど、OSXでは取れなくて...
cat /proc/$(pgrep -f 'java.*jenkins')/cmdline
```

**2015/12/13 追記**

@heliac2000 さんから、`pgrep -l` を使う手もあると教えていただきました。

`ls -l` と同じような意味(Long Output, Long Format)なので覚えやすいですね。

```bash:pgrepで取れる
$ pgrep -fl 'java.*jenkins' | cut -d' ' -f3-
-Dmail.smtp.starttls.enable=true -jar -Xms512m -Xmx2048m -XX:MaxPermSize=128m -Dfile.encoding=utf-8 /usr/local/opt/jenkins/libexec/jenkins.war --httpPort=8080
```

## 特定のファイル名を探す

```bash:要するにfindを使いこなせていなかったあの日
# カレントディレクトリを捜索
ls -l | grep 999

# もっと深い階層にあったかもと思って再帰的に検索
ls -lR | grep 999
```

`ls` と `grep` の組み合わせ。よくやってました。
確かに `grep` は何にでも応用できますからね。最強です。
しかして、`ls -lR` だと、どの階層のファイルか分かり辛くないですか？

```bash:どのディレクトリにあるのやら？
$ ls -lR | grep 999
-rw-r--r--  1 yasuhiroki  staff  4 12  2 02:20 1999.txt
-rw-r--r--  1 yasuhiroki  staff  4 12  2 02:20 2999.txt
-rw-r--r--  1 yasuhiroki  staff  4 12  2 02:20 3999.txt
-rw-r--r--  1 yasuhiroki  staff  7 12  2 02:20 4999.txt
```

ファイルの検索は `find` の仕事ですよ。

```bash:findの仕事
find . -name "*999*"
```

```bash:実行結果例
$ find . -name "*999*"
./tmp_dir/1999.txt
./tmp_dir/2999.txt
./tmp_dir/3999.txt
./tmp_dir/4999.txt
```

これでファイルの一覧がパス付きで取れました。

お目当てのファイルを探し出しましたけど、何か、続けてやりたいことがあったのでは？

### 特定の名前を持つファイルサイズ取得

```bash:やっぱりfindの仕事
$ find . -name "*999*" -ls
2368802        8 -rw-r--r--    1 yasuhiroki       staff                   4 12  2 02:20 ./tmp_dir/1999.txt
2369802        8 -rw-r--r--    1 yasuhiroki       staff                   4 12  2 02:20 ./tmp_dir/2999.txt
2370802        8 -rw-r--r--    1 yasuhiroki       staff                   4 12  2 02:20 ./tmp_dir/3999.txt
2371802        8 -rw-r--r--    1 yasuhiroki       staff                   7 12  2 02:20 ./tmp_dir/4999.txt
```

4,7 のある行がサイズです。
ううん...。分かりにくいですね。

```bash:findでexec使う
$ find . -name "*999*" -exec du -h {} \;
4.0K	./tmp_dir/1999.txt
4.0K	./tmp_dir/2999.txt
4.0K	./tmp_dir/3999.txt
4.0K	./tmp_dir/4999.txt
```

`du` コマンドで知りたい情報だけを得るようにしてみました。
ちょっとコマンドが長くなってしまいましたが。単位付きで分かりやすいですね。

でも気を付けてください。`du` コマンドのサイズって、ブロック単位なんですよ。
良く考えてくださいね。
知りたいのはファイルが消費する容量のサイズ？
それともファイルに含まれるバイト数？

バイト数なら `wc` を使えば良いですね。

```bash:findでexec使う(wc版)
$ find . -name "*999*" -exec wc -c {} \;
4	./tmp_dir/1999.txt
4	./tmp_dir/2999.txt
4	./tmp_dir/3999.txt
7	./tmp_dir/4999.txt
```

#### 補足 `find -exec +` の方が適切なシーン

@fumiyasさんから、`find -exec +` を[教えていただきました](http://qiita.com/yasuhiroki/items/fad876db9e5505fceb03#comment-784579bbb302b9613762)。

知らなかったので慌てて man を読んだところ、可能な限り引数にまとめて渡してコマンドを実行する、とのことです。

つまり今回の例だと、

`find . -name "*999*" -exec wc -c {} +` とすれば、
`wc -c ./tmp_dir/1999.txt ./tmp_dir/2999.txt ./tmp_dir/3999.txt ./tmp_dir/4999.txt` 

を実行することになり、起動するコマンドが１つになります。ようするに `xargs` と似たような感じですね。（`man` にも、`xargs` と似たような振る舞いをすると書いてありました）

また、このケースだと、 `wc` を使うより `stat` コマンド、もしくは `find -printf` を使うほうがより効率的とのことです。

```bash:statを使う場合
# BSD版 stat
$ find . -name '*999*' -exec stat -f '%z %N' {} +
4 ./tmp_dir/1999.txt
4 ./tmp_dir/2999.txt
4 ./tmp_dir/3999.txt
7 ./tmp_dir/4999.txt

# GNU版 stat
$ find . -name '*999*' -exec gstat -c '%s %n' {} +
4 ./tmp_dir/1999.txt
4 ./tmp_dir/2999.txt
4 ./tmp_dir/3999.txt
7 ./tmp_dir/4999.txt
```

### 特定の名前を持つファイルのパーミッションを変更したい

項目を分けましたが、実装方法はサイズを調べる時と変わりないですね。

```bash:find便利
find . -name "*999*" -exec chmod 755 {} \;
```

## ファイル内に hogehoge が含まれていたら条件分岐したい

```bash:初心者あるあるgrepとifその1
if [ "`grep 'hogehoge' hoge.txt`" ]; then
  echo "found!"
fi
```

`grep` して検索文字が含まれる行があれば、確かに条件式は真になりますね。
`grep` で抽出した行が複数あっても、`""` で囲んでいるから大丈夫ですね。

でも、grepしたい検索文字が空白だったらどうしましょう。
検索したい文字が `'` とか `"` とか含まれていたら？

いえ、実はそのまま空白や`"`を検索文字列に書けば動きます。
でも気になりますよね。あれっ大丈夫なんだっけ、って。
それに `'` を検索したい時は流石に、考えないといけませんね。

エスケープの参考)
http://qiita.com/cocodrips/items/bb3640a9834c8978d48a


なるべく、クォートは減らすに越したことはないですよ。

```bash:初心者あるあるgrepとifその２
grep 'hogehoge' hoge.txt
if [ $? = 0 ]; then
  echo "found!"
fi
```

終了コードを使ったのですね。
これなら、`grep` は `grep` の記述のみに集中すれば良いので、余計な心配しなくて良いですね。クォートも必要最小限で済んでます（実は `'` も不要です）。

でもこれって、`[ $? = 0 ]` をあちこちに書く未来が見えますね。
大したことないですって？ `[ $? = 0]` としてしまって、syntaxエラーになったことありませんか？

ところで `if` の後は `[]` が続くなんて決まりありましたっけ？

```bash:ifとgrepをまとめてしまう
if grep 'hogehoge' hoge.txt; then
  echo "found!"
fi
```

`if` が条件分岐に使用するのは終了コードなのです。だから、素直にコマンドを書いてしまえば良いんです。

この記述なら、
`if` `grepコマンドの終了コードが0なら` `echo "found!"` する
と、読んだ通りの条件分岐になりますね。

grepの出力が邪魔になる？ `/dev/null` に突っこんでもいいですけど、`-s`と`-q` オプションを使った方が良いですよ。エラー出力も隠せますからね。

ついでに、この `if` ってそもそも `if` にする必要あるんでしょうか？

```bash:&&使って一行に
grep -sq 'hogehoge' hoge.txt && echo "found!"
```

`&&` は 左辺のコマンドの終了コードが 0 だった時に右辺を評価します。
`if` も `[]` も消えて、 本当に処理したいコマンド `grep` と `echo` だけに集中できますね。

`hogehoge` が無かった時の処理ですか？

```bash:hogehogeが無かった時
grep -sqv 'hogehoge' hoge.txt && echo "not found!"
grep -sq 'hogehoge' hoge.txt || echo "not found!"
```

`grep -v` か `||` で良いですね。
もし `||` を使う場合は、 ファイル `hoge.txt` が存在しない時も `echo "not found!"` が実行されることに気を付けてくださいね。
どちらを使うべきかは、実装したい処理によりますから。

あー、やっぱり三項演算したいですか。ちょっと工夫が必要なんです。
`grep -sq 'hogehoge' hoge.txt && echo "found!" || echo "not found!"`
って、`if`文で表すと↓になるんですよ。。

```bash:三項演算できてるようでちょっと違う
if grep -sq 'hogehoge' hoge.txt; then
  if ! echo "found!"; then
    echo "not found!"
  fi
else
  echo "not found!"
fi
```

`echo "found!"` が失敗した時も（Typoもあり得ますね）、`echo "not found!"` が出力されてしまうんです。


## 変数の中に特定の文字があるか調べたい

```bash:echo+grep
having_hoge_var="@(^ ^)@ hoge!"
echo ${having_hoge_var} | grep -sq "hoge" && echo "OK!"
```

grep で調べるために、echo で変数の値を標準出力にはきだしているんですね。

でも、`echo ${having_hoge_var} | grep -sq "hoge"` って長くないですか？
`[[ ]]` という `[ ]` の拡張を使うと、すっきりします。

```bash:[[]]でマッチ比較
having_hoge_var="@(^ ^)@ hoge!"
[[ ${having_hoge_var} =~ "hoge" ]] && echo "OK!"
[[ ${having_hoge_var} == *hoge* ]] && echo "OK"
```

どちらでも "OK" です。

# 整数の計算あるある
## expr コマンドを使ってしまう

```bash:だってググると出てくるんだもの
i=`expr $i + 1`
```

「シェルスクリプト 計算」でググると出てきますものね。あー、`bc` コマンドも出てきますね。
なんだか、Bashってコマンド使わないと四則計算できないなんて不便ですね。

でも本当に不便なのは、もっと便利な方法があるのにゆるい検索では知ることができないWebの世界だと思いますよ。

### ループでインクリメントしたい

```bash:exprが見つかったから...
i=0
while [ $i -lt 10 ]
do
  i=`expr $i + 1`
  echo $i
done
```

1~10を、インクリメントしながら表示しようとしたらこんな感じですか。
何度見ても、`expr $i + 1` って何だか格好悪いですよね。

`(())` を使えばすっきりです。
インクリメントしたいなら、見た目もパフォーマンスも圧倒的にこちらが良いです。

```bash:変数iをインクリメント
((i++))
```

Rubyですら使えない `i++` ができるんです。

```bash:さっそく使う
i=0
while [ $((i++)) -lt 10 ]
do
  echo $i
done
```

`(())` だけだと、何も出力されずただ実行されるのみです。
`$(())` とすれば、計算結果の数値を得られるので、そのまま比較に使えるわけです。
やりたいのって、こういう事でしたよね？

でも、もうちょっと見覚えのある書式にしたいですよね。
それに `(())` は、足し算しかできないわけじゃないんですよ？

```bash:もっとすっきりしたループ
i=0
while (( i++ < 10 ))
do
  echo $i
done
```

`[ ]` の中で使うとリダイレクトになってしまう比較演算子 `<` も、`(())` なら使えます。

`for` では、もっと見覚えのある形にできますよ。

```bash:CやJavaっぽいforになる
for ((i=1; i <= 10; i++))
do
  echo $i
done
```

さらに `for` であれば `do ~ done` すら使わずに済むんです。

```bash:もっとCやJavaっぽいforになる
max_cnt=10
for ((i=1; i <= max_cnt; i++))
{
  echo $i
}
```

見慣れた書式で安心できますね。
ちなみに 1~10を一行ごとに出力するなら、forを使う以外にも `echo` と `tr` や `grep` で（シェル芸の範疇になりそうなので省略）

# while read あるある

ファイルから一行ずつ読み込んで、あれやこれやってしますよね？
そんな時に使うといったら、`while read` でしたね。

## csvを読み込む

```bash:泥臭くがんばりました
cat data.csv | while read line
do
  _1=`echo ${line} | cut -d',' -f1`
  _2=`echo ${line} | cut -d',' -f2`
  _3=`echo ${line} | cut -d',' -f3`
  
  echo "Column 1: $_1"
  echo "Column 2: $_2"
  echo "Column 3: $_3"
done
```

`cut` で `,` 区切りにして、1つずつフィールドを読み取って...
大変ですね。列が20とか100とかなっても、このまま頑張りますか？
頑張りたくないですよね。

ようするに`,`でsplitすれば良いんですよね？

```bash:read時の区切り文字変えてみる？
cat data.csv | while IFS=',' read _1 _2 _3
do  
  echo "Column 1: $_1"
  echo "Column 2: $_2"
  echo "Column 3: $_3"
done
```

デフォルトの区切り文字は空白文字なので、',' では区切ってくれません。
なので↑のようにすれば、`read` してる時だけ、区切り文字を `,` した上、各フィールドの値を 各変数に代入できます。
色々と省略できてますね。

IFSを変更する時は、元の値を保持しておいて（`IFS_ORI=$IFS`とかして）、あとでIFSを元に戻さないと大変な目にあいますけど、この書き方なら不要です。

ただ、列数が増れば増えるほど辛くなることに変わりはないですね。
配列にすればもう少し扱いやすいでしょう。

```bash:splitして配列に
cat data.csv | while read line
do
  cols=(`echo $line | tr ',' ' '`)
  for ((i=0; i < ${#cols[@]}; i++)) {
    echo "Column $((i+1)): ${cols[$i]}"
  }
done
```

---

### `read -a` で配列化はもっとシンプルに

**2015/12/07 追記**
@eban さんのブログで、`read -a` を使えばもっとシンプルになると指摘されました。
> http://jarp.does.notwork.org/diary/201512a.html#20151204


```bash:readのaオプション使えば配列化が一発で
cat data.csv | while IFS=, read -a cols
do
  for ((i=0; i < ${#cols[@]}; i++)) {
    echo "Column $((i+1)): ${cols[$i]}"
  }
done
```

`,` 区切りの配列にできるので、空白が入っていても安心ですね。

---

えっ、こんな csv だったらどうするか？

```bash:こんなCSVは
2015/12/04 00:00:00,"A,B",c,d\ne
```

```bash:こうなっちゃう
Column 1: 2015/12/04
Column 2: 00:00:00
Column 3: "A
Column 4: B"
Column 5: c
Column 6: dne
```

ぐっ、こ、これは。。。

```bash:Bashでやり切るのは大変だから...
$ cat data.csv | ruby -r 'csv' -e 'CSV.parse($<).each{|row| row.each_with_index{|c,i| puts "Column #{i}: #{c}"}}'
Column 0: 2015/12/04 00:00:00
Column 1: A,B
Column 2: c
Column 3: d\ne
```

つまり、CSVのパースを真剣にやるなら、シェルスクリプトは止めたほうが良いという話でした。


# 最後に

Greg's Wiki を読みながら書いてみるのが一番の勉強だと思います。

# 参考

* http://mywiki.wooledge.org/EnglishFrontPage
    * Greg's Wiki
    * 実装方法に悩んだ時は、ここのBashFAQを見れば大抵模範解が得られます
    * 良くやりがちなミスをまとめたページは、最近日本語訳がでましたね
        * http://mywiki.wooledge.org/BashPitfalls （原文）
        * https://yakst.com/ja/posts/2929 （日本語訳）
* https://github.com/jlevy/the-art-of-command-line
    * シェルというよりは、コマンドラインで作業する時のノウハウ集です
    * 日本語訳もあります
* https://github.com/alebcay/awesome-shell
    * すばらしいシェルスクリプトたち
* http://explainshell.com/
	* ワンライナーなシェルスクリプトを解析、解説してくれる素敵サービスです
	* 隣のシェル芸人の作品を紐解くときにでも
* http://www.shellcheck.net/
	* いわゆるLintのようなものです
	* brewでインストールしてコマンドからも使えます
		* 参考) http://rcmdnk.github.io/blog/2014/11/26/computer-bash-zsh/
* Qiita記事
	* 検索するといい記事たくさんあります


明日は @yasuto777 さんです！