[シェル芸](初心者向け)空行を出力する(別解)
みなさんこんばんは。夜の @nullpopopo です。前回こちらの記事を書いたら殊の外Twitterでの反響がありましたので、別解をまとめてみたいと思います。 (2014/12/12 12:00 別解をいただいたので追記しました。) 最後に明日のシェル芸勉強会の宣伝をしようと思ったのですが、書いてて結構自分で盛り上がって長いエントリーになってしまったので、最初に書いておきます。
2014-12-13(土)14:00〜17:00です。本編終了後、会場内で懇親会(#!/beer/bash)を行いますので、ダラダラと盛り上がることができます。LTも大歓迎です。
さて、シェル芸の醍醐味の1つに、正解が1つじゃないという面白みがあるのですが、今回Twitterで実によいコラボレーションができたと思います。何か1つワンライナーができたら、もっと違うアプローチがあるのでは?もっとパイプの本数を減らせないか?冗長な処理が削れないか?というチャレンジをするのは非常に勉強になりますね。
元のお題は「100行の空行を出力する」というシェル芸でした。こちらではseqコマンドで1から100を出力したものをsedやawkで空文字に置換するものでしたが、Twitterでは別のコマンドでやってみた方たちからリプライをいただきました。
yes '' | head -n 100 という手もときどきやってます→ "[シェル芸](初心者向け)空行を出力する | (っ´∀`)っ ゃー | nullpopopo" http://t.co/zFaJW1dhHS
— Masakazu Takahashi (@emasaka) December 10, 2014
こちらはシンプルに、yesコマンドで空文字を出力し、headで100行ぶん切り出すというものです。正規表現で置換する必要がなく、シンプルですね。性器表現で痴漢、アカン!
次にレスポンスがあったのがこちらになります。
@nullpopopo 変化球的なやつを考えました。echo -n -e {1..100}"n" | cut -c 5
— れびーね (@rebine) December 10, 2014
echoのオプションだけで、「1 2 3 … 99 100」と横に表示される数字を見事に縦にしています。そして、cutコマンドで5文字目を表示するとしていますが、こうすることで空行として表示されることを狙っているのですね。(「echo -n -e {1..100}”n”」)だけで実行するとわかるのですが、先頭にスペースが入っているので、1行の最大文字が4文字なのです。
で、私ももう1捻りしてみました。
@rebine お!その発想はなかったです。もうひと捻りしてみましたw ls /bin | head -n 100 | cut -b 36-
— 40代独身 (っ◠‿◠ )っ ゃー (正気) (@nullpopopo) December 10, 2014
100行の何かを出力するのに、lsの結果を使ってみました。たまたま/binディレクトリの下にどれだけファイルがあるか見てみたのですが、
$ ls /bin | wc -l 1895
ご覧の通り1895ものファイルがあったので、headで100行切り取りました。で、今度は1行の最大文字数を超えるぶんを表示しようということで、cutコマンドにパイプで渡しました。オプションは -b 36- でも -c 36 でもどちらでも構いません。たまたまファイル名の最長文字数が35文字だったので、36文字目以降を表示するか、36文字目を表示するかの違いだけで、いずれも空行が表示されるようになっています。
さらにこのようなレスがつきました。
@nullpopopo おーっ /binって100個もあったんですね。cut -b 36-の36がなんにあたるんだろう…面白いですね!
echo -n {1..100} | tr -s '[:alnum:]' 'n'
もう一つ作ってみました。— れびーね (@rebine) December 11, 2014
さきほどのechoの応用で、すべての文字を改行に置換していますね。実にシンプルです。
で、yesやechoやls以外に何か新ネタを披露せねば・・・と焦って作ったシェル芸がこちらです。
@rebine 今度は冗長なのになってしまいました。 cat /dev/urandom | tr -dc '[:alnum:]' | head -c 100 | grep -o . | sed -e 's/[[:alnum:]]//g'
— 40代独身 (っ◠‿◠ )っ ゃー (正気) (@nullpopopo) December 11, 2014
/dev/urandomの出力結果から数字とアルファベットを出力するようにし、100バイト分表示し、「grep -o .」で縦に並べ替えてからsedで空行に置換しています。パッと閃いてあまり考えずに実行したので、Twitterに投稿してから「これもうちょっと短くできないとカッコ悪いなぁ・・・」と思ってしまいました。で、自己レスしたのがこちらです。
@rebine もっとシンプルにできましたw cat /dev/urandom | tr -dc 'n' | head -c 100
— 40代独身 (っ◠‿◠ )っ ゃー (正気) (@nullpopopo) December 11, 2014
もう単純にurandomの出力をいきなり改行に置換して、100行でぶった切ってます。最初からこうすればよかった。。。シェル芸力のなさっぷりを発揮してしまいました。。。(´・_・`)
ところが、どうやらこのtrコマンドのオプション、linuxでは使えてもMAC OSXでは使えないようで・・・。
@nullpopopo @rebine OS X だと "tr: Illegal byte sequence" と怒られちゃいました
— IMAIZUMI Mitzyuki – Full Vaccinated (5 times) (@bsdhack) December 11, 2014
そこはさすがPOSIXおじさん、awkだけでパイプを1本も使わずに解決してしまいました。
@nullpopopo @rebine ちなみに awk だけの版→ awk 'BEGIN{ for(i=0; i<100; i++) print}'
— IMAIZUMI Mitzyuki – Full Vaccinated (5 times) (@bsdhack) December 11, 2014
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の禁止」、「コマンド単騎使用の禁止」等が有名である。
と書いているようですが、本当にこれ理由があってやっていることなんです。
@nullpopopo @rebine もう一個ネタを提供 echo | pr -l 100 (posix準拠ww
— IMAIZUMI Mitzyuki – Full Vaccinated (5 times) (@bsdhack) December 11, 2014
こちらは単純にechoで空文字(改行つき)をprコマンドに渡して100行にしています。
※ 2014/12/12 12:00追記
@bsdhack @nullpopopo ブログ見ました。for文との比較速度については僕も気になっていたのでスッキリしました。time echo -n {1..100} | tr -s '[:alnum:]' 'n' | tr -d '[:blank:]' 置換系修正です。
— れびーね (@rebine) December 12, 2014
さらに別解いただきました。修正とありますが、今までのワンライナーが間違っていたわけではないです(wc -lでの検算済)。
いかがでしたでしょうか?たかだか「100行の空行を表示する」だけのシェル芸でもこれだけの解があり、しかも実行するコマンドによっては速度差が歴然だったりします。勿論、思いついていないだけで他にも解があるでしょうし、こうして解のパターンを考え、しかもその理由を人に説明できれば、相当なスキルがつくはずです。是非週末にでもお題を決めてトライしてみてください。面白いですよ!
「[シェル芸](初心者向け)空行を出力する(別解)」への0件のコメント