前置き
昨日に続いて第32回シェル芸勉強会のQ3を解いていきます。
第32回シェル芸勉強会の問題は次のURLにあります。
【問題のみ】jus共催 第32回全くインスタ映えしないシェル芸勉強会 | 上田ブログ
そういえば、当時の様子は一旦全部解いてから確認する予定です。
また、Q1・Q2の別解も思いついてるので終わってから書きます。
思考を忠実にアウトプットすることを目標にしています。
問題を見た感想・補足
シェル芸人が大好きな素数問題です。これは1時間かかりませんね。
解答1(不足あり)
cat /etc/services | grep -o '[0-9]*/tcp' | cut -d/ -f1 | factor | awk 'NF==2{print $2}'
解説
まず /etc/services
とはなにかと言う話ですが、UNIX系がよく使われるポート番号を管理しているファイルです。
$ cat /etc/services | sed '/^#/d' | head tcpmux 1/tcp # TCP port service multiplexer echo 7/tcp echo 7/udp discard 9/tcp sink null discard 9/udp sink null systat 11/tcp users daytime 13/tcp daytime 13/udp netstat 15/tcp
上記のように、サービス名[TAB]ポート番号/プロトコル
という形式で記載されています。
ちなみに、私は待受しているところを見たことはありませんが、echo
は送ったリクエストをそのまま返してくるサービス(シェルにおけるcat
ですね)で、discard
はただ受け取るだけのサービス(≒ /dev/null
)です。
今回出力するポート番号は、ポート番号/プロトコル
の形で記載されているので、その形でTCPのものだけgrep -o
で引っこ抜くのが楽でしょう。
$ cat /etc/services | grep -o '[0-9]*/tcp' | head 1/tcp 7/tcp 9/tcp 11/tcp 13/tcp 15/tcp 17/tcp 18/tcp 19/tcp 20/tcp
sed 's#\t\t#\t#' | tr \\t \ | cut -d\ -f2
などでもできますが、だいぶめんどくさいです。
今回は付けなくても影響なかったので付けていませんが、コメントに同様の形が記載されていることを危惧するなら、先に sed 's/#.*//'
でコメントを除いておくと安心できます。
ここから数字だけにすれば、対象のポート番号が得られます。パッと思いつくだけでも、以下のいずれかで得られます。
awk -F/ '$0=$1'
,cut -d/ -f1
- / を区切り文字として、第1フィールドを得る
tr -dc 1-9\\n
- 数字と改行コード以外を消す
grep -o '[0-9]*'
- 連続する数字だけ残す
sed 's#/.*##'
- / 以降を消す
$ cat /etc/services | grep -o '[0-9]*/tcp' | tr -dc 0-9\\n | head 1 7 9 11 13 15 17 18 19 20
あとは、いつものやつ(factor | awk 'NF==2{print $2}'
)で結果が得られます。
見たことない人に対して補足すると、素因数分解した(factor
)結果、素因数が自身のみ(awk 'NF==2'
。(factor
はラベルと結果を出すため対象行は2列になる。))だったものを表示しています。
$ cat /etc/services | grep -o '[0-9]*/tcp' | cut -d/ -f1 | factor | awk 'NF==2{print$2}' | head 7 11 13 17 19 23 37 43 53 67
補足
完全に余談ですが/etc/services
はポート番号とサービス名の名前解決?をするためのファイル(IPにおける/etc/hosts
的な存在)なので編集すると、システムが表示する待受サービス名を変えられます。
以下は、ss
で出てくる待受サービス名を編集しています。(普段はss -tna
で名前解決なしで呼ぶのでサービス名見ませんが。。。)
# cat /etc/services | grep ssha ssha 22/tcp # SSH Remote Login Protocol # ss -ta State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:ssha *:* ESTAB 0 0 10.0.2.15:ssha 10.0.2.2:63942 LISTEN 0 128 :::ssha :::*
解答1(修正)
cat /etc/services | grep -o '[0-9]*/tcp' | cut -d/ -f1 | factor | awk 'NF==2{print "\\<"$2"/tcp\\>"}' | grep -f - /etc/services
解説
問題を見返すと、「TCPのポート番号が素数のサービス一覧を作ってください。」と書いてて焦りました。
上記で作成したポート番号一覧でサービス名を拾ってくる必要があります。
ポート番号/プロトコル
の形に直して、 /etc/services
に再度 grep
をかければ実現できます。
grep
のパターンにする際に \<7/tcp\>
などと記載すると、77/tcp
のような文字を弾いて、\t7/tcp\t
だけを抜き出せます。
つまり、\<pettern\>
で、前後が空白や記号のデリミタっぽいもので分けられた文字列だけをマッチさせられます。
$ cat /etc/services | grep -o '[0-9]*/tcp' | cut -d/ -f1 | factor | awk 'NF==2{print "\\<"$2"/tcp\\>"}' | head \<7/tcp\> \<11/tcp\> \<13/tcp\> \<17/tcp\> \<19/tcp\> \<23/tcp\> \<37/tcp\> \<43/tcp\> \<53/tcp\> \<67/tcp\>
上記で出てきた結果を用いて、/etc/services
をgrep
し直せば、サービス一覧が得られます。
ワンライナーの3神器たるgrep
sed
awk
は普段は引数にスクリプトを書きますが、-f
オプションでファイルを渡すことができます。
また、UNIX系コマンドで良くなされている実装で、ファイル名に-
を指定すると標準入力になります。
つまり、... | grep -f - <filename>
とすると、パイプで渡ってきた文字列を検索条件として、ファイル内を検索できます。
$ cat /etc/services | grep -o '[0-9]*/tcp' | cut -d/ -f1 | factor | awk 'NF==2{print "\\<"$2"/tcp\\>"}' | grep -f - /etc/services | head echo 7/tcp systat 11/tcp users daytime 13/tcp qotd 17/tcp quote chargen 19/tcp ttytst source telnet 23/tcp time 37/tcp timserver whois 43/tcp nicname domain 53/tcp # Domain Name Server bootps 67/tcp # BOOTP server
これでサービス一覧が得られました。1列目だけ残すのはお好きにどうぞ。
まとめ
知っているからよく使う手法が多かったです。
具体的には以下のものです。
- フィールドを切り出す
- 素数判定する:
factor | ...
- 標準入力を
grep
の検索条件として扱う
感想
知ってれば解ける系でした。と思いきや問題をちゃんと読まずスタートしており、10分前に気づいて焦りました。。。
記事自体を書くのには結局1時間かかっています。余談が楽しくなってしまうので仕方ありませんね。
改訂履歴
- 2017/12/08: 「まとめ」を「感想」にして、きちんとした「まとめ」を書きました。