[シェル芸]for文を使わず一撃でサブディレクトリを多数作成する


あけましておめでとうございます。本年もどうぞよろしくお願いいたします。親戚付き合いのない私にとって、良くも悪くも正月は暇なので、シェル芸とサーバーリプレースごっこで遊んでいます。

※ 2015-01-03 00:19:38 追記

さいとうさんよりツッコミありました。桁揃えのブレース展開ができるとは知らなかった・・・sh行不足でした(;´д`)

今回、以下のような構造のディレクトリを作成しようと思い、シェル芸を考えてみました。もしかしたら過去のシェル芸勉強会で似たような(もしくは全く同じ)問題があったかも知れませんが、気にしないことにします。お正月ですから。

1/00001
1/00002
...
1/10000

2/00001
2/00002
...
2/10000

まずはシェルのfor文を使わないパターンで、timeコマンドをつけて時間を計測しながらやってみましょう。まずは「1」ディレクトリと「2」ディレクトリの下に00001〜10000のサブディレクトリを作ってみます。

$ A=$(echo \{$(seq -w 1 10000)\} | sed -e 's/ /,/g') ; echo "mkdir -p 1/$A 2/$A" | time sh
0.13user 0.37system 0:00.60elapsed 84%CPU (0avgtext+0avgdata 7356maxresident)k
0inputs+0outputs (0major+2013minor)pagefaults 0swaps

timeコマンドの表示が横1列になってしまいますが、0.13秒で処理が終わりました。次はシェルのfor文を使って同じことをやってみます。

$ time for A in $(seq -w 1 10000); do mkdir -p {1,2}/$A; done

real	0m14.708s
user	0m2.806s
sys	0m12.280s

こちらは0.14秒とあまりパフォーマンスは変わりません。それでは次に100000個のサブディレクトリを作ってみましょう。最初にfor文を使わないパターンです。

$ A=$(echo \{$(seq -w 1 100000)\} | sed -e 's/ /,/g') ; echo "mkdir -p 1/$A 2/$A" | time sh
6.72user 5.04system 0:16.63elapsed 70%CPU (0avgtext+0avgdata 47956maxresident)k
0inputs+0outputs (0major+18260minor)pagefaults 0swaps

7秒弱でできました。それでは同じくfor文でやってみましょう。

$ time for A in $(seq -w 1 100000); do mkdir -p {1,2}/$A; done
real	2m30.545s
user	0m27.905s
sys	2m4.039s

なんと2分30秒もかかっています!これは由々しき事態です。

 

さて、シェル芸を使って一撃で複数のサブディレクトリを作るにあたって、for文を使えば簡単にできる(人が実行したワンライナーが理解しやすい)というのは大多数が直感的に思うでしょう。しかし、パフォーマンスを犠牲にせず、かつワンライナーでエレガントに終わらせたい!という場合にどうするかを考えた結果、ブレース展開の文字列を作って変数に格納してあげることにしました。

また、↑の例ではわざわざmkdirのコマンドをechoの文字列としていますが、多数のディレクトリを作成するにはこれが安全じゃないかと。最後のパイプラインを外して実行結果を確認すれば、間違って変なディレクトリができてしまうことも防げます。例えばこんな感じです。

$ A=$(echo \{$(seq -w 1 5)\} | sed -e 's/ /,/g') ; echo "mkdir -p 1/$A 2/$A"
mkdir -p 1/{1,2,3,4,5} 2/{1,2,3,4,5}

こうすることで実行前の確認ができるので便利ですね。

今年もよいシェル芸ライフを送りましょう!でわ〜♪

[amazonjs asin="4904807154" locale="JP" title="シェルスクリプトマガジン vol.21"]