前置き
昨日に続いて第32回シェル芸勉強会のQ4を解いていく前に、Q1~3を修正した報告です。
第32回シェル芸勉強会 Q1 の解答 - nogiro_iotaのメモ
第32回シェル芸勉強会 Q2 の解答 - nogiro_iotaのメモ
第32回シェル芸勉強会 Q3 の解答 - nogiro_iotaのメモ
主に、「まとめ」が全くまとめていなかったのを修正しました。
あと、Q3 はちょっとした手違いで最後が突貫工事になってしまって grep -f - <filename>
を説明していなかったので追記しました。
(grep -f - <filename>
:パイプで渡ってきたパターンでファイルをgrep
する)
それでは Q4 を解いていきます。
第32回シェル芸勉強会の問題は次のURLにあります。
【問題のみ】jus共催 第32回全くインスタ映えしないシェル芸勉強会 | 上田ブログ
思考を忠実にアウトプットすることを目標にしています。
Q4 問題(上記URLからコピー)
次のデータを
136 725 948
次のように並べ替えてください。
9 7 4 1 2 8 3 5 6
問題を見た感想・補足
問題が何を言ってるのかわからない(あるある)。
問題をよく見ると、どうやら下記の順番に出力するみたいです。
479 258 136
また、入力文字列はGitリポジトリに入っていたnums.txt
を使います。
試行1
とりあえず、最終的にキレイに並ぶデータで試します。次のコマンドがワンラインできれいに出ます。
$ echo $'479\n258\n136' 479 258 136
最初に出力されるべき位置が下にあると扱いづらいのでtac
で反転します。
echo $'479\n258\n136' | tac 136 258 479
上記の出力の各文字を としたときに、下記の優先順位で は より優先的に出力されるようです。(左上が原点。、)
・ のとき
・ かつ (≡ ) のとき
数学できる人ならここから変換できそうだなーと思いつつ、何も思いつかなかったのでお蔵入りです。
解答1
echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#.#sed -n &p <(grep -o . nums.txt | grep \\[0-9\\])#' | bash
解説
やるべきことは、下の順で出力することです。
479 258 136
このままでは扱いづらいので、grep -o .
で1列にします。
$ echo $'479\n258\n136' | grep -o . 4 7 9 2 5 8 1 3 6
1は7行目、2は4行目にあって、つまり、行数が出力したい順番になっています。
行数を付けて、出力したい順番の列でソートします。
$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 7 1 4 2 8 3 1 4 5 5 9 6 2 7 6 8 3 9
左列の順番で出力すると、目的の結果が得られそうです。
$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's# *$#p#' 7p 4p 8p 1p 5p 9p 2p 6p 3p
上記をsed
に与えてやりましょう。
$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#$#p#' | sed -n -f - <(tr -dc 0-9 < nums.txt | grep -o .) 1 3 6 7 2 5 9 4 8
!!!順番が変わっていません。
(私は)やりがちなミスですが、sed
は1行ごとに全スクリプトを適用していくのでこの渡し方では意味がありません。
(sed
に関わらず、ストリーム指向なプログラム(e.g. grep
awk
)はだいたいそうです。)
というわけで、まずは脳死のとりあえず sh
に投げとけ で解きます。
sed
で sed
を含むコマンドを作ります。
$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#.#sed -n &p <(grep -o . nums.txt | grep \\[0-9\\])#' sed -n 7p <(grep -o . nums.txt | grep \[0-9\]) sed -n 4p <(grep -o . nums.txt | grep \[0-9\]) sed -n 8p <(grep -o . nums.txt | grep \[0-9\]) sed -n 1p <(grep -o . nums.txt | grep \[0-9\]) sed -n 5p <(grep -o . nums.txt | grep \[0-9\]) sed -n 9p <(grep -o . nums.txt | grep \[0-9\]) sed -n 2p <(grep -o . nums.txt | grep \[0-9\]) sed -n 6p <(grep -o . nums.txt | grep \[0-9\]) sed -n 3p <(grep -o . nums.txt | grep \[0-9\])
これを bash
(≠sh
。プロセス置換<(...)
を使っているため。)に渡すと答えが得られます。
$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed 's#.#sed -n &p <(grep -o . nums.txt | grep \\[0-9\\])#' | bash 9 7 4 1 2 8 3 5 6
解答2
echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed -e 's#\(.\).*#print $\1#;1i{' -e '$a}' | awk -f - <(tr -dc 0-9 < nums.txt | sed 's#.#& #g')
解説
nums.txt
を最初のecho
のに合わせて1列にする意味ないなーと思った結果です。
ワンライナーだと、行の前後を入れ替えるのはけっこうめんどくさいですが、列の入れ替えは簡単です。
そのため、プロセス置換部分が、<(grep -o . nums.txt | grep \[0-9\])
から <(tr -dc 0-9 < nums.txt | sed 's#.#& #g')
に変更されています。
sed
でスクリプトを作っている部分はゴリゴリ書いているだけです。
$ echo $'479\n258\n136' | grep -o . | nl -nln | sort -k 2 | cut -f 1 | sed -e 's#\(.\).*#print $\1#;1i{' -e '$a}' { print $7 print $4 print $8 print $1 print $5 print $9 print $2 print $6 print $3 }
余談
試作1を使えば、echo $'479\n258\n136' | grep -o .
部分が作れるように思いますが、やり方はわかっていません。
まとめ
問題を見て出力の順番を手で作って、行数と逆変換・出力をしたら解けます。
感想
出力順番と行数の逆変換が思いついたらすぐだった。