読者です 読者をやめる 読者になる 読者になる

双六工場日誌

平凡な日常を淡々と綴ります。

bashのブレース展開を使ってカンマ区切りリストを作る

bash操作のTipsメモです。

bashには、ブレース展開というブレース({}のカッコのこと)の中身を一定の法則で展開して、半角スペース区切りのリストにしてくれる機能があります。

例えば、以下のように使うことができます。

$ echo 192.168.1.{1,3}
192.168.1.1 192.168.1.3
→ブレースの直前の文字列にカンマで区切った文字列を組み合わせたリストに変換

$ echo 192.168.1.{1..5}
192.168.1.1 192.168.1.2 192.168.1.3 192.168.1.4 192.168.1.5
→数値の連番の場合は ".." で数値を繋ぐとその範囲で数値を変化させたリストに変換

$ echo /opt/{app,db}/{data,conf}
/opt/app/data /opt/app/conf /opt/db/data /opt/db/conf
→複数のブレースがある場合は、そこに入っている値の組み合わせでのリストを出力。
これによってまとめて複数のディレクトリを作ったりすることができる。

「ブレース展開」でググると、いろいろな使い方が紹介されているのを見つけることができますが、例えば、 http://d.hatena.ne.jp/xr0038/20111221/1324446298 では使い方の具体例がいろいろ紹介されています。

これは非常に便利な機能なのですが、値の区切るデリミタが必ず半角スペースになってしまうので、例えば、カンマ区切りでホスト名が欲しい時*1などは、得られた結果を素朴に sed で置換していました。*2

sedを使った素朴な変換の例
$ hostlist=$(echo 192.168.1.{1..5} | sed 's/ /,/g')
$ echo $hostlist
192.168.1.1,192.168.1.2,192.168.1.3,192.168.1.4,192.168.1.5

これ自体は汎用的な方法で、特に問題があるわけではありませんが、せっかくbash組み込みの機能を使っているので、sedを使わないでできる方法を調べてみました。

bash組み込み機能を使うと、以下の方法で半角スペースからカンマへの変換ができます。

bashの変数展開時のパターンマッチを使ったやり方
$ hostlist=$(echo 192.168.1.{1..5})
# ${変数名//パターン/文字列/} とするとパターンに一致するすべての部分を文字列で置換したものを返す
$ echo ${hostlist// /,}
192.168.1.1,192.168.1.2,192.168.1.3,192.168.1.4,192.168.1.5
bash組み込みのprintfコマンドを使った方法
# bash組み込みのprintfコマンドは "-v 変数名" でその変数に出力結果を格納することができる
$ printf -v hostlist '%s,' 192.168.1.{1..5}

# ${変数名%パターン} とすると、変数末尾をパターンマッチし最短マッチ部分を削除された結果を出力する
# それを利用して最後の余分なカンマを削除する
$ echo ${hostlist%,}
192.168.1.1,192.168.1.2,192.168.1.3,192.168.1.4,192.168.1.5

前者はsedにも近い書式のパターンマッチで比較的覚えやすく、値を使う際にデリミタを切り替えて使えるというメリットがあります。一方、後者は printf が /usr/bin/printf ではなく、bash組み込みコマンドの方になっている*3という罠があるのに加えて、変数展開も独自書式なので、この場合はあまり向いていなさそうです。

bashのパターンマッチには、ここで使った以外にもシェルを使った作業を効率的に勧められるようなものが揃っています。

参考サイト

例としては、パターンマッチと同じ方法を使ったディレクトリ名、ファイル名が挙げられているのをよく見ます。

$ filepath=/path/to/file.txt
$ dir=${filepath%/*}        # => /path/to
$ filename=${filepath##*/}  # => file.txt

ただ、僕としてはディレクトリ名、ファイル名の取得では、専用コマンド dirname, basename を使う方がお勧めです。bash依存にならずに済みますし、これらのコマンドはオプションを使うことで、拡張子を除いたファイル名を取り出すといった操作がより直感的にできるからです。

公開されているスクリプトを読んだ時に、この書き方が出てくることがあるので、こういう書き方があるのか、ぐらいに覚えておくのがいいと思います。*4

今日のところは、ここまでで。

*1:例えば、Fabric の -H オプションでホスト名の一覧を渡すとき

*2:手元で実験した限りでは、IFSを変えてもデリミタは半角スペースのままでした。これを変更できる方法を知っている方がいれば教えてもらえると嬉しいです。

*3:type printfで調べられます

*4:このエントリのメインで挙げている例は、そもそもbash組み込みのブレース展開との組み合わせなので、そこでbash依存になるのは割り切って使っています。