1. niina

    Posted

    niina
Changes in title
+bashのbrace expansionでfor文を使いこなせ!
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,189 @@
+# 背景
+
+退屈なことは大体`shell`でなんとかなります。特に普段からコマンドラインに生息している人は、バーっと書いてそのまま動けばこんなに楽なことはありません。
+
+ところで、`shell`ですることと言えば、`grep`して`sort`して`uniq`みたいなことをここにあるファイル全部についてやりたい、とか、ファイルを連番でコピーして`sed`でちょっとずつパラメータを変えていきたいとか、そういうことでしょう。つまり、`for`文を書く機会がそれなりにあるはずです(前者は`grep 'hoge' piyo/*.fuga`の方が楽ですが)。
+
+では皆さんは`bash`で`for`文を書くときどうしていますか? 私は`seq`コマンドを使っていました。
+
+```bash
+for i in `seq -w 1 10`; do
+ echo ${i}
+done
+01
+02
+03
+04
+05
+06
+07
+08
+09
+10
+```
+
+`-w`オプションを使うと幅が調整されるので連番ファイルの時に地味に便利です。
+
+ですが、例えば`seq`ではアルファベット順に何かをすることが難しいです。
+
+```bash
+for a in `seq A D`; do
+ echo ${a}
+done
+A
+B
+C
+D
+# となってほしいが、ならない
+```
+
+これは、例えば以下のようにすれば動くんですが、
+
+```bash
+for a in A B C D; do
+ echo ${a}
+done
+A
+B
+C
+D
+# 今度は動く
+```
+
+これはプログラマとしてはあまり嬉しくないです。このコードは「怠惰」ではありません。`A`から`Z`まで回したいときはどうするんですか? プログラマが根性で問題を解決してはいけません。
+
+といって`seq`で対応するASCIIコードを吐いて`printf`コマンドを使う、というようなことをすると化け物みたいなコードが生まれます。`bash`ワンライナーは可読性という言葉からかなり遠いところにある概念ですが、それでもASCIIコードが生で登場するワンライナーは一段階ステージが上でしょう。
+
+# 解決策
+
+というわけで本題です。以下が動きます。
+
+```bash
+for a in {A..D}; do
+ echo ${a}
+done
+A
+B
+C
+D
+```
+
+上で言っていた方法は全てお払い箱です。可読性は十分、そして簡単。これで全部なんとかなります。
+
+これは想像以上に強力な機能で、例えば`ABCD`と回した後に`1234`と回したい時などもそれぞれ書けばできますし、
+
+```bash
+# {}間にスペースが必要
+for a in {A..D} {1..4}; do
+ echo ${a}
+done
+A
+B
+C
+D
+1
+2
+3
+4
+```
+
+また`A1 A2 A3 A4 B1 B2 ...`と回したい時も、2重ループなんぞ必要ありません。
+
+```bash
+# {}間にスペースを入れず、詰めて書く
+for a in {A..D}{1..4}; do
+ echo ${a}
+done
+A1
+A2
+A3
+A4
+B1
+B2
+B3
+B4
+C1
+C2
+C3
+C4
+D1
+D2
+D3
+D4
+```
+
+え、ファイルのIDはアンダースコアで区切ってある? 中々見やすい名前をつけていますね。では一気に回しましょう。変数なんか1個でいいですよ。
+
+```bash
+for a in data_{A..D}_{1..4}.dat; do
+ echo ${a}
+done
+data_A_1.dat
+data_A_2.dat
+data_A_3.dat
+data_A_4.dat
+data_B_1.dat
+data_B_2.dat
+data_B_3.dat
+data_B_4.dat
+data_C_1.dat
+data_C_2.dat
+data_C_3.dat
+data_C_4.dat
+data_D_1.dat
+data_D_2.dat
+data_D_3.dat
+data_D_4.dat
+```
+
+ほう、`data_A_01.dat`のようになっているので数字の頭に0を付けたいと。幅が統一され、見目麗しくなる素晴らしい命名規約だと思います。今後もぜひ続けて下さい。同じだけ簡単にループで処理できるので。
+
+```bash
+for a in data_{A..D}_{01..10}.dat; do
+ echo ${a}
+done
+data_A_01.dat
+data_A_02.dat
+data_A_03.dat
+data_A_04.dat
+data_A_05.dat
+# ... (中略) ...
+data_D_06.dat
+data_D_07.dat
+data_D_08.dat
+data_D_09.dat
+data_D_10.dat
+```
+
+最高ですね。
+
+# 補遺
+
+もちろん、刻み幅も変えられます。
+
+```bash
+for i in {01..10..2}; do
+ echo ${i}
+done
+01
+03
+05
+07
+09
+```
+
+アルファベットでもお構いなしです。
+
+```bash
+for a in {A..G..2}; do
+ echo ${a}
+done
+A
+C
+E
+G
+```
+
+# 参考
+
+[Bash Reference Manual -- 3.5.1 Brace Expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html)