第32回シェル芸勉強会 Q1 の解答

前置き

アウトプットの練習と日課を作るため、毎日何かしらの記事を書こうという企画です。
まずは、体調不良で休んでしまった(追加で8時間寝てたら元気になりました)シェル芸勉強会の問題を解こうと思います。

第32回シェル芸勉強会の問題は次のURLにあります。
【問題のみ】jus共催 第32回全くインスタ映えしないシェル芸勉強会 | 上田ブログ


普通に解いても面白くないし、記事にする意味も薄いので、一問ずつ色々な解き方で思考を書けるだけ書こうと思います。(回数も稼げるし。。。)

Q1 問題(上記URLからコピー)

つぎのように1から9までの数を、間の数字を適当抜いてechoで出力します。

echo 14679

このechoの後ろにパイプでコマンドをつなぎ、次のように各数字を1行一個、抜けた数字の行を飛ばして出力してください。

1


4

6
7

9

問題を見た感想

素数やん!と思ったら違った。(7がなくて8があれば、10より小さい非素数でしたが。。。)

解答1

echo 14679 | grep -o . | diff -u <(seq 9) - | sed 1,3d | sed '/^-/s/.*//;s#.##'

解説

まず最終出力が1桁の縦1列にいい感じにスペースを入れたものだと認識しました。それに対して入力は、横に並んだ空白の含まれないデータになっています。
ワンライナーでは、空白や改行でデータの区切りを認識することが多いです。つまりこの問題では、最終出力は1桁ごとに区切られたもので、入力は1塊になっています。
この不整合は何も考えずに最初に揃えてしまっても不都合がないですし、「いい感じに入れる」のは若干めんどくさいことが多いです。
なので、入力を縦に整列させます。
シェル芸勉強会により「1桁ごとに縦に整列する」操作の定型文と化してしまった grep -o . をはさみます。

$ echo 14679 | grep -o .
1
4
6
7
9

これで、後はいい感じに空白を入れるだけです。

いい感じに空白を入れるためには、そもそも1~9までの並びとくらべて見てどうなのかを見ます。
paste コマンドを使うと横に並べて表示してくれるので結果を眺めます。

$ echo 14679 | grep -o . | paste <(seq 9) -
1       1
2       4
3       6
4       7
5       9
6
7
8
9

上記ワンライナーは脇道で、最終的には含まれません)

眺めていると(上の文章に若干ネタバレがありますが、)この左右をくらべれば答えが得られるような気がしてきます。
くらべる、つまり差分をとるための diff の出番です。(catに置き換えるとしても、pasteの文字と置き換えるだけで良いのも paste の便利さかもしれません)

$ echo 14679 | grep -o . | diff <(seq 9) -
2,3d1
< 2
< 3
5d2
< 5
8d4
< 8

何も考えずに diff をとると、上記のように元のテキストは消えてしまい本当に差分だけがでてきます。
コード差分を見るだけなら上記でも問題ないこともありますが、コード差分を見るときにもデータ加工ツールとして使うときにも大体は、-uオプションを付けて前後の行を出力してやったほうが便利です。(-U 9999のように-Uに大きな数を与えて、「とりあえず全部」みたいな使い方をすることも多いです。)

$ echo 14679 | grep -o . | diff -u <(seq 9) -
--- /dev/fd/63  2017-12-06 00:33:20.457702900 +0900
+++ -   2017-12-06 00:33:20.461029100 +0900
@@ -1,9 +1,5 @@
 1
-2
-3
 4
-5
 6
 7
-8
 9

出力されてきた結果を見ると、なんとなく最終出力に近い部分があるので整形すればなんとかなりそうです。
とりあえずは、diff のサマリー(対象ファイルの情報など)は不要なので、最初の3行を sed 1,3dで削ります。

$ echo 14679 | grep -o . | diff -u <(seq 9) - | sed 1,3d
 1
-2
-3
 4
-5
 6
 7
-8
 9

ここからマイナスで始まる行を取っ払って、diffの付ける行頭の空白を消すと、解答1になります。

$ echo 14679 | grep -o . | diff -u <(seq 9) - | sed 1,3d | sed '/^-/s/.*//;s#.##'
1


4

6
7

9

解答2

echo 14679 | grep -o . | awk -v i=1 '{for(;i<$0;i++)print "";print;i++}'

解説

awk によるゴリ押しです(つよい)。
awk に渡された各行の値になるまでにあったはずの数字の回数分、改行を出力します。

まとめ

アウトプットの練習のために、1日1投稿し始めました。
シェル芸勉強会の問題の解答を、思考をなるべく懇切丁寧に解説しながら書きました。

最終出力までにどういう変換が必要か想像しつつ、コマンド1つ1つを目的から逆引きしつつ解いている様子がわかります。

感想

執筆時間を1時間で切って解答を始めた結果、2パターンしか記載できませんでした。
制限時間を付けると頑張る気になりますね。

なるべく丁寧に思考をなぞったつもりですがいかがでしょうか。


改訂履歴

  • 2017/12/08: 「まとめ」を「感想」にして、きちんとした「まとめ」を書きました。