[シェル芸](初心者向け)空行を出力する(別解)


みなさんこんばんは。夜の @nullpopopo です。前回こちらの記事を書いたら殊の外Twitterでの反響がありましたので、別解をまとめてみたいと思います。 (2014/12/12 12:00 別解をいただいたので追記しました。) 最後に明日のシェル芸勉強会の宣伝をしようと思ったのですが、書いてて結構自分で盛り上がって長いエントリーになってしまったので、最初に書いておきます。

第14回東京居残りシェル芸勉強会&第32回蟹ではなくピザが出るUSP友の会定例会

2014-12-13(土)14:00〜17:00です。本編終了後、会場内で懇親会(#!/beer/bash)を行いますので、ダラダラと盛り上がることができます。LTも大歓迎です。

さて、シェル芸の醍醐味の1つに、正解が1つじゃないという面白みがあるのですが、今回Twitterで実によいコラボレーションができたと思います。何か1つワンライナーができたら、もっと違うアプローチがあるのでは?もっとパイプの本数を減らせないか?冗長な処理が削れないか?というチャレンジをするのは非常に勉強になりますね。

元のお題は「100行の空行を出力する」というシェル芸でした。こちらではseqコマンドで1から100を出力したものをsedやawkで空文字に置換するものでしたが、Twitterでは別のコマンドでやってみた方たちからリプライをいただきました。

こちらはシンプルに、yesコマンドで空文字を出力し、headで100行ぶん切り出すというものです。正規表現で置換する必要がなく、シンプルですね。性器表現で痴漢、アカン!

次にレスポンスがあったのがこちらになります。

echoのオプションだけで、「1 2 3 ... 99 100」と横に表示される数字を見事に縦にしています。そして、cutコマンドで5文字目を表示するとしていますが、こうすることで空行として表示されることを狙っているのですね。(「echo -n -e {1..100}"n"」)だけで実行するとわかるのですが、先頭にスペースが入っているので、1行の最大文字が4文字なのです。

で、私ももう1捻りしてみました。

100行の何かを出力するのに、lsの結果を使ってみました。たまたま/binディレクトリの下にどれだけファイルがあるか見てみたのですが、

$ ls /bin | wc -l
1895

ご覧の通り1895ものファイルがあったので、headで100行切り取りました。で、今度は1行の最大文字数を超えるぶんを表示しようということで、cutコマンドにパイプで渡しました。オプションは -b 36- でも -c 36 でもどちらでも構いません。たまたまファイル名の最長文字数が35文字だったので、36文字目以降を表示するか、36文字目を表示するかの違いだけで、いずれも空行が表示されるようになっています。

さらにこのようなレスがつきました。

さきほどのechoの応用で、すべての文字を改行に置換していますね。実にシンプルです。

で、yesやechoやls以外に何か新ネタを披露せねば・・・と焦って作ったシェル芸がこちらです。

/dev/urandomの出力結果から数字とアルファベットを出力するようにし、100バイト分表示し、「grep -o .」で縦に並べ替えてからsedで空行に置換しています。パッと閃いてあまり考えずに実行したので、Twitterに投稿してから「これもうちょっと短くできないとカッコ悪いなぁ・・・」と思ってしまいました。で、自己レスしたのがこちらです。

もう単純にurandomの出力をいきなり改行に置換して、100行でぶった切ってます。最初からこうすればよかった。。。シェル芸力のなさっぷりを発揮してしまいました。。。(´・_・`)

ところが、どうやらこのtrコマンドのオプション、linuxでは使えてもMAC OSXでは使えないようで・・・。

そこはさすがPOSIXおじさん、awkだけでパイプを1本も使わずに解決してしまいました。

forを使うから遅いかなとか思ってしまってごめんなさい。シェルのforを使うと遅いのですが、awkのforを使うと、むしろ先のurandomを使うより速かったです。計測結果は以下の通りです。

$ time cat /dev/urandom | tr -dc 'n' | head -c 100 > /dev/null
real    0m0.204s
user    0m0.003s
sys     0m0.210s

$ time awk 'BEGIN{ for(i=0; i<100; i++) print}' > /dev/null
real    0m0.003s
user    0m0.001s
sys     0m0.002s

ちなみに、シェルのforとawkのforでどれだけ実行時間に差があるか計測してみました。100行ぽっちだとシェルの方が速いくらいですが、これが1000000行ですと差は歴然です。

$ time for A in {1..1000000}; do echo $A; done | cut -b 4- > /dev/null
real    0m9.081s
user    0m8.631s
sys     0m3.333s

$ time awk 'BEGIN{ for(i=0; i<1000000; i++) print}' > /dev/null
real    0m0.112s
user    0m0.110s
sys     0m0.003s

たまげたなぁ。パイプを通るから遅いのかと思って、シェルのfor文だけで再計測してみましたが、こんな感じでした。

$ time for A in {1..1000000}; do echo $A; done > /dev/null
real    0m5.628s
user    0m5.308s
sys     0m0.323s

以上のことから、シェルのforは遅い(forに与えた条件がすべて終わってはじめて全体の終了だから当たり前ですが)ということになるようです。awkのforがこうならずに速いメカニズムについては、awk神さいとうさんの降臨を待ちたいところですが、きっと今頃鳥取のシェルスクリプトワークショップの準備でお忙しいことかと思われますので、今度お会いしたときに聞いてみたいと思います。なお、このお題をやろうと決めたときから、私ははなからシェルのforとwhileは使わないと心に決めていました。アンサイクロペディアではフザケて

主な教義としては、「cat以外の第一コマンドでのファイル名オプション指定の禁止」や「ファイル上書きオプション使用の禁止」、「思考順序に従わない記述の禁止」、「for, case, whileの禁止」、「コマンド単騎使用の禁止」等が有名である。

と書いているようですが、本当にこれ理由があってやっていることなんです。

こちらは単純にechoで空文字(改行つき)をprコマンドに渡して100行にしています。

※ 2014/12/12 12:00追記

さらに別解いただきました。修正とありますが、今までのワンライナーが間違っていたわけではないです(wc -lでの検算済)。

いかがでしたでしょうか?たかだか「100行の空行を表示する」だけのシェル芸でもこれだけの解があり、しかも実行するコマンドによっては速度差が歴然だったりします。勿論、思いついていないだけで他にも解があるでしょうし、こうして解のパターンを考え、しかもその理由を人に説明できれば、相当なスキルがつくはずです。是非週末にでもお題を決めてトライしてみてください。面白いですよ!

USP MAGAZINE vol.20

[シェル芸](初心者向け)空行を出力する(別解)”に関する1件のコメント

  1. ピングバック

コメントは受け付けていません。