bashのブレース展開についてまとめていきます。
※今回はBash 4 の環境を利用して検証しています。
4 より前の環境では再現できない場合があります。
ブレース展開って何?
Linuxのシェルである、bashの機能の一つです。
ブレース展開は任意の文字列を生成するために使用されます。
指定された文字列は、その文字列と、展開の前後に付けた、追記を有する全ての可能な組み合わせを生成するために使用されます。
超簡単に言うと複数の文字列に任意の文字列を加えたり、指定した文字数字を展開できる機能です。
と、書いてもよくわからないですね。
語彙力がなさすぎるのでこればっかりはどうしようもないです。
以下、使用例を交えて解説していきます。
基本的な書式
{文字列1,文字列2,...,文字列N}
{<始まり>..<終わり>}
{<始まり>..<終わり>..<インクリメントしたい数>} (Bash 4 以降に実装)
<頭に付けたい文字列>{........}
{........}<後尾に付けたい文字列>
<頭に付けたい文字列>{........}<後尾に付けたい文字列>
{文字列1,文字列2}{文字列3,文字列4}
{,文字列1,文字列2}
<文字列0>{文字列1,,,文字列2}
<文字列>{,,,}
#使用法
{文字列1,文字列2,...,文字列N}
この書式は、入れた文字列をそのまま展開します。
試しに、以下のコマンドを実行してみましょう。
$ echo {a,b,c}
a b c
これは単純に {}
の中身を見て、カンマで区切っている部分を空白にし出力しているだけです。
正直このままだと何にも使えません。
{<始まり>..<終わり>}
書式の書き方もうちょいどうにかならかったの?と思うかもしれませんが、ちょっとどう表現したらいいものかわかりませんでした。
とりあえず実例を見て行きましょう。
$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
<始まり>から1ずつプラスしていき、スペースで区切り、順番に出力しています。
<終わり>まで達したら、ストップします。
1~100だろうと1~1000だろうと出力してくれます。
簡単なループ文を書くより簡単です。
始まりは1でなくとも大丈夫です。
$ echo {6..10}
6 7 8 9 10
因みに 始まり > 終わり の場合でも出力してくれます。
$ echo {5..1}
5 4 3 2 1
数値だけではなく、文字(アルファベット)も展開が可能です。
$ echo {A..Z}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
$ echo {Z..A}
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
$ echo {z..a}
z y x w v u t s r q p o n m l k j i h g f e d c b a
また大文字と小文字は一気に出力することが可能です。
$ echo {A..z}
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z
大文字と小文字の間に記号が入っていますが、記号はどう展開すればいいのかわかりません。
エスケープを入れてもうまくいかないので、どなたかご存知でしたら教えて下さい。
{<始まり>..<終わり>..<インクリメントしたい数>} (Bash 4 以降に実装)
※Bash 4以降に実装されている機能です。4より前では実行できません。
先程は以下の書き方で1~10を順番に出力しました。
$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
<始まり>から1ずつプラスしていき、スペースで区切り、順番に出力しています。
<終わり>まで達したら、ストップしますが、それはブレース展開のデフォルトが1ずつプラスするようになっている為です。
このプラスする値は自由に変更する事ができます。
書式の <インクリメントしたい数>
で値を指定して変更します。
実例を見てみましょう。
$ echo {1..10..2}
1 3 5 7 9
先ほどの例 {1..10}
の末尾に新たに ..2
を追加します。
これがインクリメントしたい数です。
例を見ると、1に2ずつプラスしていき、9で出力が止まっています。
<終わり>として指定した数より大きくなる事はありません。
<頭に付けたい文字列>{........}
数学の展開を思い出してください。
例えば、 a(m + n)
この式を展開すると、 am + an
となります。
イメージとしてはこれです。
実例を見てみましょう。
$ echo a{m,n}
am an
数学の展開とほぼ同じような感じに展開されるかと思います。
<頭に付けたい文字列> が {}
内の m
と n
の頭にくっついて出力されます。
{........}<後尾に付けたい文字列>
先ほどの書式とさほど変わりありません。
$ echo {m,n}a
ma na
{}
内の m
n
の後尾に a
をくっつけて出力するだけです。
#<頭に付けたい文字列>{........}<後尾に付けたい文字列>
混合技です。
もはや語らずともわかるかと思います。
echo a{m,n}b
amb anb
{文字列1,文字列2}{文字列3,文字列4}
これまた数学の展開と同じですね。
(a + b)(m + n) と同じような感じです。
展開すると am + an + bm + bn となりますね。
大体そんな感じです。
$ echo {a,b}{m,n}
am an bm bn
<文字列1>{,文字列2,文字列3}
文字列2の前にカンマが付いています。
これで挙動が微妙に変わります。
$ echo a{,m,n}
a am an
文字列1が単体で出力され、その後は展開された文字列が出力されます。
カンマの位置によって文字列1の出力される位置が変わってきます。
カンマの位置を文字列3の後ろに持ってくることで、文字列1が文字列3の後ろに単体で出力されます。
$ echo a{m,n,}
am an a
<文字列1>{文字列2,,文字列3}
カンマを続けて付けてみるとどうなるでしょうか。
カンの良い皆さんなら大体想像が付くのではないでしょうか。
以下のようになります。
$ echo a{m,,n}
am a an
<文字列>{,,,}
さて、今度はカンマだけを連続して付けてみます。
これももうどうなるかわかりますね。
$echo a{,,,}
a a a a
以上の事から、{} の前に付けた文字列は、 {} 内のカンマとその直前の文字列の前に置換された状態で展開される ということがわかります。
<文字列1>{文字列2,{文字列3,}}
入れ子にした場合は、カンマの位置によって出力が変わります。
文字列の後ろにカンマを付けた場合は、以下のように出力されます。
$ echo a{b,{c,}}
ab ac a
また数学の展開の話になりますが、これはイメージとしては以下の様な感じになります。
a(b + 1(c + 1))
ab + a(c + 1)
ab + ac + a
カンマが+で、その後ろには(実際には何も入っていませんが)1が入っている感じです。
<文字列1>{,文字列2{,文字列3}}
カンマの位置を変更して、文字列の前に持ってきます。
こうすると、先程と挙動が変わります。
$ echo a{,b{,c}}
a ab abc
これも展開を例に持ってきますが、ちょっと順番が変わったくらいです。
a(1 + b(1 + c))
a + ab(1 + c)
a + ab + abc
使用法とか
バックアップ取得
私がこれを良く使うのは、cp
コマンドで、バックアップとしてファイルを取得する時です。
$ cp -ip test{,.bk}
カレントディレクトリなら別段利用する必要はないのですが、階層が深くなると、めんどくさくなります。
普通にバックアップを取ろうとすると以下のようになるかと思います。
$ cp /etc/bar/foo/hoge/hogehoe/test /etc/bar/hoge/hogehoge/test.bk
パスを打つのがめんどくさいです。
そんな時にはブレース展開。
$ cp /etc/bar/foo/hoge/hogehoe/test{,.bk}
どうせ同じディレクトリに保存するならブレース展開を使った方がタイプミスをする心配もないので、有用な気がします。
同様の方法で、diffやmvを実行する事も可能です。
複数ファイル、ディレクトリの作成
例えばテスト用の空ファイルやディレクトリを複数作成する必要がある時に使えます。
$ echo touch test{1..9}
$ ls
test1 test2 test3 test4 test5 test6 test7 test8 test9
$ echo mkdir test{1..9}
$ ls
test1 test2 test3 test4 test5 test6 test7 test8 test9
同様の方法でrmで一括削除もできます。
ループ文の条件式として利用
条件式にブレース展開を使います。
1~10まで出力します。
$ for i in {1..10}; do echo $i; done
1
2
3
4
5
6
7
8
9
10
超簡単です。
拡張子の一括付与
for文とか使えば、カレントディレクトリのファイルに拡張子を一括で付与出来ます。
$ ls
test1 test2 test3 test4 test5 test6 test7 test8 test9
$ for i in `ls`; do mv $i{,.png}; done
$ ls
test1.jpg test2.jpg test3.jpg test4.jpg test5.jpg test6.jpg test7.jpg test8.jpg test9.jpg
#おまけ
早さ比べ
seqコマンドとブレース展開だと、どちらが早いでしょうか。
1~1000000を出力してみて早さ比べをしてみます。
$ time seq 1 1000000
1
2
3
.
.
.
999998
999999
1000000
real 0m43.338s
user 0m0.012s
sys 0m0.545s
seqは43秒でした。
ブレース展開はどうでしょうか。
$ time echo {1..1000000}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
.
.
.
999988 999989 999990 999991 999992 999993 999994 999995 999996 999997 999998 999999 1000000
real 0m30.178s
user 0m1.185s
sys 0m0.020s
30秒でした。
seqより早いですね。
因みにCだとこうなります。
#include <stdio.h>
int main(void){
int i;
for(i = 1; i <= 1000000; i++){
printf("%d ", i);
}
return 0;
}
$ time ./million
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
.
.
.
999988 999989 999990 999991 999992 999993 999994 999995 999996 999997 999998 999999 1000000
real 0m28.521s
user 0m0.127s
sys 0m0.056s
28秒でした。
ブレース展開よりちょっと早いですかね。
改行を入れるとどうでしょうか。
#include <stdio.h>
int main(void){
int i;
for(i = 1; i <= 1000000; i++){
printf("%d\n", i);
}
return 0;
}
$ ./million
1
2
3
.
.
.
999998
999999
1000000
real 0m44.564s
user 0m0.205s
sys 0m0.939s
44秒になります。
改行処理に時間が掛かるみたいです。
試しにブレース展開で出力した数値に xargs
で改行を加えたら8分近くかかりました(これは xargs
自体の処理が遅いのでしょうけど・・・色々便利なコマンドなのでその分ボリュームがあるのは仕方ないですね)。
というわけでブレース展開は以上です。
「もっとあるだろ!」
「こんな方法もあるよー!」
という方は是非是非教えてください。