ゴールデンウイークシェル芸問題のまとめ
Mon May 4 18:16:41 JST 2015 (modified: Sun Oct 1 10:50:27 JST 2017)
views: 2225, keywords:コマンド,CLI,ファイル入出力,素数ネタ,シェル芸 この記事は最終更新日が7年以上前のものです。
問題はこんなものでした
以下のように1から100まで数字が書いてあるansというファイルを作り、ansの中から素数でない数をワンライナーだけで消し去ってください。(ansの中身を書き換えるということです。forもwhileもなしで、コマンドはパイプでつないで。)
ueda@ubuntu:~/tmp$ seq 1 100 > ans
問題の意図
意図は隠しつつ伝える必要があるので毎回苦労しますが、この問題は素数がポイントなのではなく、「入力ファイルを出力で上書きできますか?」ということがポイントです。
こういうことを言うと「シェルによって違う」という話になりがちですが、まずは理詰めで考えることの方が大事なんじゃないかなと、個人的には思います。コード読めという話も出てきますが、これも同様、理詰めで考えればわざわざ読む必要もありません。
攻略法
理屈で考えると、パイプラインの中身がすべて同時に動いている状況で、入力ファイルを出力ファイルに書き戻すには、
- 入力と出力を時間的に完全に分離
- iノード(= ファイルの実体)を分離
- パイプに通さずにコマンドに上書きさせる
のいずれかの方法がとられている必要があります。もちろん、こうでなくてもOSがよしなに計ってくれればよいのですが、そんな冗長な処理をOSに搭載するのは問題です。
シェルの話をすると、問題のansファイルは、シェルの「あるプロセス」がansファイルに書き込みを始めるときにファイルの中身が空になります。空にしないとファイルの頭からデータを保存できないので、これもコードを読まずに理屈で考えると分かるかと。
で、私が想定していた解は、こちらです。
factor <ans |sed -n 's/^\\([0-9]*\\): *[0-9]*$/\\1/p' |sponge ans moreutils好き。 https://t.co/ZdC6JeAfih
— ふみやす%シェルまおう的なにか@通販生活 (@satoh_fumiyasu) May 2, 2015
% factor < ans | awk '$0*=NF==2' | sponge ans moreutilsのspongeコマンド #シェル芸
— eban (@eban) May 1, 2015
平たく書くとこんな感じ。
###spongeコマンドをインストールする###
ueda@ubuntu:~/tmp$ sudo apt-get install moreutils
###使う###
ueda@ubuntu:~/tmp$ cat ans | factor | awk 'NF==2{print $2}' | sponge ans
sponge(1)というコマンドを使います。名前の通り、入力された字を吸い取っていき、吸い取ってから引数に指定したファイルに書き込みます。ということで、上に挙げた「入力と出力を時間的に完全に分離する」が守られています。
「ファイルの実体を分離する」の解もありました。すごい。これは思いつきません。
% ( rm ans && factor | awk '$0*=NF==2' > ans ) < ans #シェル芸
— eban (@eban) May 1, 2015
ebanさんはどんな方法でも解いてくるので本当にすごいなあと・・・。
解説をしておくと、まずansをオープンした後にansを消してから処理してansに書き出すという処理になっています。「rm ans」の時点では、ansが開いているので実体(iノード)はまだ消去されません。ansという名前だけ消えます。んで、その後に「factor ... > ans」となっているので、ansという名前の別のiノードができます。確認しておきましょう。
ueda@web:~/tmp$ seq 1 10 > ans
ueda@web:~/tmp$ ls -i ans
2363953 ans
ueda@web:~/tmp$ (rm ans; factor | awk 'NF==2{print $2}' > ans) < ans
ueda@web:~/tmp$ ls -i ans
2364013 ans
実はこの挙動、フルスクラッチから1日でCMSを作る シェルスクリプト高速開発手法入門で少し説明しています。ただ、「具体的な使い道が分からん」という言葉と共に書いたので、今、使い道が分かりました。しかし、こんなコードは普通書かないので「使い道」なのかどうかは定かではありません。
最後のコマンドに上書きさせる方法は、(私がspongeを教えてもらったのは斉藤さんからなので)解答を禁止したはずの斉藤さんから強烈なものをいただきました。
最新の gawk の解を gawk だけで。 $ gawk -i inplace '{"factor "$0|getline}NF==2&&$0=$2' ans http://t.co/3ALgyyYuUp #シェル芸
— Hirofumi Saito (@hi_saito) May 2, 2015
説明は斉藤さんにお任せします。っていうかなんだこれ???
ブログで取り上げていただきましたのでリンク
おれの無いぞという方はぜひご連絡くだしあ。
はてなブログに投稿しました #はてなブログ #シェル芸 【FreeBSD】入力ファイルに直接リダイレクトして書き込みができるかどうか実験してみた - くんすとの備忘録 http://t.co/B45Aoo8piL
— くんすとのプレアデス (@kunst1080) May 2, 2015
日記書いた! 入力ファイルへのリダイレクト問題 #シェル芸 http://t.co/LsLYN0H76l
— eban (@eban) May 2, 2015
はてなブログに投稿しました #はてなブログ ゴールデンウイークシェル芸問題を解きました - くんすとの備忘録 http://t.co/yO2UVK1aWm
— くんすとのプレアデス (@kunst1080) May 4, 2015
ブログでないけど有難うございます。
GWシェル芸問題、なんか面白いことしたいと思ったらえらいことになりました #シェル芸 https://t.co/69qhyMxvms
— ひまじん (@__Himajin) May 1, 2015
せんでん
最後に宣伝・・・。今回の問題は扱ってませんが、基本、ワンライナーの、シェル芸人による、ワンライナーの本です。