Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

日々の作業をもっとサッとこなしたい!「ワンライナー勉強会」を開催しました

4月入社の新人エンジニアのxztaityozxです。趣味はdotfilesいじりです

皆さんは日々の開発でふと、調査やデータの加工が必要となったことはありませんか?私はたくさんあります。
ではそういった時、どうやって解決していますか?私はいくつかのCLIツールを組み合わせることで解決しています。

例えば、/etc/services からtcpなサービスの名前を取り出すときは以下のようにします

$ cat /etc/services | grep -P "\d+/tcp" | awk '{print $1}'

こういったテクニックを「ワンライナー」と呼ぶことにし、日常のちょっとした調査やデータ加工をサッと出来るようにコマンドラインと触れ合っていくことを目的とした勉強会「ワンライナー勉強会」を開催しました。

f:id:xztaityozx:20200616151321p:plain

この記事では、「ワンライナー勉強会」の進め方や問題、解答例を紹介します。

進め方

社内勉強会は開催者が自由にその形式を決めて良いとのことなので、プライベートでよく参加しているシェル芸勉強会を参考に、以下のような進め方にしました。

  1. 参加者に問題を提示する
  2. 参加者は10分間でその問題にチャレンジする
    1. このとき使って良いのはコマンドラインのみ
  3. 解答ができたから、Slackへ投稿する
    1. このとき私が解説できそうなものは解説します
  4. 解答例を示す
  5. 1~4を繰り返す

作問はUbuntu 20.04のbash v5.0.16 で行いました。

問題

今回の勉強会のためにいくつか作問しましたが、この記事ではその中から2問紹介したいと思います。この記事を読んでいるみなさんも是非挑戦してください!

Q1 30までのFizzBuzzを出力してください

FizzBuzzとは、3の倍数のときはFizz、5の倍数ではBuzz、15の倍数ではFizzBuzzと発言するパーティーゲームです。よくプログラミングの入門としても出題されますね。

参加者の解答として投稿されたものをいくつか紹介します。

$ seq 30 | awk '{ if ($1 % 15 == 0) { print "FizzBuzz" } else if ($1 % 3 == 0) { print "Fizz" } else if ($1 % 5 == 0) { print "Buzz" } else { print $1 } }'

スクリプト言語であるawkを使った解答です。FizzBuzzの要件をそのまま落とし込んだ!という感じの解答でとても読みやすいです。

$ python3 -c "for i in range(1,31):print('Fizz'*(i%3==0)+'Buzz'*(i%5==0) or str(i))"
$ python -c "[print(('Fizz' if i % 3 == 0 else'') + ('Buzz' if i % 5 == 0 else '') or i) for i in range(1, 31)]"

こちらはPythonを使った解答です。両者とも「文字列を組み立てて出力する」タイプで、1つ目の解答は文字列の繰り返しを上手く使った解答でした。
この他にも、Perlやnodeを使った解答もありました。同じ問題を解くにしても、いろんなアプローチが見られるのがワンライナーの面白いところです。

ちなみに私が用意してきた解答例は

$ seq 30 | awk 'NR%3==0{printf "Fizz"}NR%5==0{printf "Buzz"}{print " "$1}' | awk '{print $1}'

というものです。1回目のawkではデータの加工、2回目のawkではデータの抽出を行っています。

Q2 numsというファイルには、100,000個の数値が改行区切りで書いてあります。これの総和を求めてください。

10万行の数値データの総和を求める問題です。皆さんはどういう解答をされたのでしょうか。以下に示します。

$ cat nums | awk 'BEGIN{s = 0} {s += $1} END{print s}'
$ cat nums | python -c 'import sys; print(sum(map(int, sys.stdin.readlines())))'
$ ghc -e 'do  f <- fmap (\x -> map read (lines x)) (readFile "nums"); print $ sum f'

この問題もawk,python,haskellなど、たくさんの方法での解答が投稿されました。特に気になったのは以下の解答です。

$ echo $(tr '\n' '+' < ./nums) 0 | bc

trで数式を組み立て、bcコマンドで数式を評価するというものです。少し詳しく解説してみます。かんたんのためにnumsファイルの中身は以下のとおりとします

$ cat nums
12726
24406
17542
30717
14595
9099
10860
9689
29765
122

これに対して解答にあるtrコマンドを実行してみます。

$ tr '\n' '+' < ./nums
12726+24406+17542+30717+14595+9099+10860+9689+29765+122+

数式が出来ました。ただし、最後の改行も+に置換されているので、式が成り立っていません。このままでは bc に渡したところでエラーが返ってきてしまいます。

$ tr '\n' '+' < ./nums | bc
(standard_in) 1: parse error

なので、末尾に0を付けて式を成立させます。

$ echo $(tr '\n' '+' < ./nums) 0 
12726+24406+17542+30717+14595+9099+10860+9689+29765+122+ 0

$()はコマンド置換と呼ばれるものです。$() でくくったコマンドの結果を別のコマンドに埋め込むことが出来ます。この場合では、trコマンドの結果をechoに埋め込んでいます。これにより無事式が成立し、bcで評価することが出来るようになりました。

$ echo $(tr '\n' '+' < ./nums) 0 | bc
159521

とても素敵な解答でした。

ちなみに100,000個の数値データは以下のようなワンライナーで生成しました。この問題に挑戦するときにお使いください。

$ seq 100000 | while read _;do echo $RANDOM; done

まとめ

初めての開催でしたが、たくさんの参加者が来てくれ、積極的に解答してもらえて嬉しかったです。参加者からも「楽しかった」「面白かった」と言ってもらえたので次回開催のモチベーションになりました。問題の難易度や時間配分を調整してまた開催したいです。


モバイルファクトリーは、技術好きなエンジニアを募集しています。