旧 #temanote

Linux大好き。だけど進捗ありません。

Shell芸クリスマス

今日は2017年12月25日(月)、平日です
平日なので平凡なエントリをお送りします。

本記事はFUN AdventCalendar2017 25日目の記事になります。

昨日は僕をカメラ沼に落とした後輩にして居候先の家主hacuskくんの

彼女氏と、時々、惚気」

(http://hacusk.hatenablog.com/entry/2017/12/24/004837)
でした。

@ 「僕と彼女氏と、時々、惚気」感想

多分これははくすけくんの惚気じゃなくて彼女さんの惚気まとめだと思うんですよ。

2018-1-8追記: 白文字ではくすけくんの惚気がありました(本人からネタバレ

@ 自己紹介

@ 概要

せっかくプログラミングを学ぶなら、日常が豊かになるようなものを作ってみない?というご提案です。 おそらく世界一役立つプログラミング言語ShellScriptとこれを用いた遊びを紹介します。

@ ShellScriptとは

この時期になれば公立はこだて未来大学へ通っている人は間違いなく一度は触ったことがあると思います。 それは大学のプログラミングの授業、C言語を実行するときに開くあの黒い画面(デフォルトは何故か白い画面)です。 mkdir, gcc, ppchkall…不思議なコマンドを言われるがまま打ち込んでいるあの画面です。

これ、実はShellと呼ばれるアプリケーションを利用しています。

Shellの使い方はざっくり二通りありまして、 一つはみなさんが使った、一行ずつ入力してコマンドを実行する使い方、もう一つはShellコマンドを書いたShellScriptを実行する方法があります。
この2つめの使い方が非常に便利なので、今年の進捗として覚えて帰ってほしいところです。

@ ShellScriptことはじめ

ShellScriptの作り方は簡単です。普段Shellに打ち込んでいるコマンドを改行区切りで書いていけば完成です。

touch test
echo 'Hello, world!' >> test
cat test

というテキストファイルをscript.shとして保存して、以下のコマンドを実行してみてください

sh script.sh

すると、Hello,world!という文字列が表示され、そのディレクトリにはHello,world!と書かれたtestというテキストファイルが出来上がっています。

ShellScriptの利点は、同じ操作を誰でもワンコマンドで実行できる点です。それはたとえ人でなくてもコンピュータですら同じ操作をすることができます
例えば、ほとんどのシステムにはDownloadフォルダがあると思います。このフォルダにはブラウザからダウンロードしたファイルが置かれます。
このフォルダは面倒くさがりな人には厄介なもので、気を抜くとすぐ不要なファイルが大量に並び、使いたいファイルがすぐに見つからないなんてことがあります。

そこであなたは考えます。毎回システムをシャットダウンするタイミングでDownloadフォルダを綺麗にできれば良いじゃんと。
でも、例えば講義資料のPDFファイルを毎回Downloadフォルダから別の場所に移すのはちょいダルい。自動化しちゃいましょう。

要件としては、

  • システムをシャットダウンする際に実行する
  • 拡張子がpdfのファイルは~/Document/pdf/以下に移動する
  • 残りのファイルは全て削除する

という内容が実行できれば万々歳です。

取り急ぎ以下のようなShellScriptを書いておけば要件に従った動作をします。

#!/bin/sh

DownloadDir="/home/temama/Downloads"          # 各環境に合わせて設定
DocumentsDir="/home/temama/Documents/fun/pdf" # 各環境に合わせて設定

cd $DownloadDir
mv *.pdf $DocumentsDir
rm -rf *

システムをシャットダウンする際に実行する方法は各環境に従うため、今回は割愛します。

上記のShellScriptをシャットダウン時に実行するようにしておけば、次回起動時にはDownloadフォルダは綺麗サッパリな状態です。
これはほんの一例なので、お行儀の良いShellScriptとは言えません。気になる方は勉強してみてください。

少しだけ補足説明をします。 一行目に書いた#!/bin/shはシバン(シェバン、Shebang)と呼ばれるもので、このファイルが/bin/shで実行することを明記しています。
3,4行目では変数にDownloadフォルダ、pdfを保存するフォルダの位置を変数に入れています。しれっと変数を使っていますが、ShellScriptでは変数や配列、if、for、while等基本的なプログラミングで使う機能が入っています。 とても奥深いので興味のある人は是非調べてみてください。

@ Shell芸への道

さて本題です
先程長ったらしく説明したShellScriptですが、もう使いません
それは何故か? 確かにShellScriptはとても便利なツールです。しかし、欠点があります。それは処理をテキストファイルに纏めなければならないことです

一番最初のShellの説明を思い出してください。Shellの使い方には二通りあって、そのうちの一つはShellにそのままコマンドを打ち込んで一行ずつ実行する方法です。
じゃあScriptなんて組まずに一行で完結させればいいんだろ。やってやろうじゃないの。

Shell芸とは、広義の意味では1行で完結するShellプログラムのことです。ワンライナーとも呼びます。
これを可能にするのは、Shellの特権であるパイプ機能です。
パイプとは、前コマンドの実行結果を次のコマンドへ流すことができるコマンドです。文字列処理に効果を発揮します。
(以下、$から始まる行はShellを起動して一行ずつ実行するコマンドを意味します)

  # ファイル一覧を列挙
$ ls -1
file01.csv
file01.txt
file02.csv
file02.txt
file03.csv
file03.txt
file04.csv
file04.txt
file05.csv
file05.txt

  # 一覧から拡張子が.csvのファイルだけを列挙
$ ls -1 | grep .csv
file01.csv
file02.csv
file03.csv
file04.csv
file05.csv

  # 番号が01のファイルを列挙
$ ls -1 | grep 01
file01.csv
file01.txt

上記の例では、lsコマンドによって得られる一覧から、grepコマンドで条件にマッチするものだけを出力しています。
パイプでgrepにつなぐ方法は、出力から目的に合うものだけを見たい時によく使います。 現実的な例としては、システムのログからネットワーク周りのものを見たいときなどですね。

また、セミコロンで区切ることで複数コマンドを順番に実行することもできます。ShellScriptの改行を置き換えることができると考えてください。

それでは早速、Shell芸で遊んでいきましょう。

手始めに、明日の函館市の天気を取ってきてみましょう。 情報源はtenki.jpです。

tenki.jp

  # とりあえずページ取得
  # 毎回アクセスするのはtenki.jpさんに悪いので、手元に保存しておく
$ curl https://tenki.jp/forecast/1/4/2300/1202/ > tenki.html

  # 確認
$ cat tenki.html
  # ページのhtmlが出力される、出力略

ここでtenki.jpをブラウザで開いて、得たい情報をどう探すか考えましょう。 今回は「明日の天気」のアイコンの下に表示される天気情報の文字列を取得します。
ブラウザの機能でそこの要素のhtmlを見てみると、weather-telopというクラスがついていますね。これを利用しましょう。

  # weather-telopがついている行を表示
$ cat tenki.html | grep weather-telop
      <p class="weather-telop">雨</p>    </div>
      <p class="weather-telop">曇時々雪</p>    </div>

  # 一行目は今日の天気のものなので、二行目を取得します
$ cat tenki.html | grep weather-telop | tail -n 1
      <p class="weather-telop">曇時々雪</p>    </div>

  # htmlタグが邪魔なので消します
$ cat tenki.html | grep weather-telop | tail -n1 | sed -e 's/<[^>]*>//g' 
      曇時々雪    

  # 前後に空白があるので消します
$ cat tenki.html | grep weather-telop | tail -n1 | sed -e 's/<[^>]*>//g' -e 's/ //g'
曇時々雪

一行で取得できました。実行時にtenki.jpから最新の情報を取ってくるように最初を書き直して完成です

  # curlの表示が出ないようにstderr出力を/dev/nullに捨てる
$ curl https://tenki.jp/forecast/1/4/2300/1202/ 2>/dev/null | grep weather-telop | tail -n1 | sed -e 's/<[^>]*>//g' -e 's/ //g'
曇時々雪

これにて完成です。あとはこれをaliasにでも貼っておけば良いのではないでしょうか。

$ alias tenki="curl https://tenki.jp/forecast/1/4/2300/1202/ 2>/dev/null | grep weather-telop | tail -n1 | sed -e 's/<[^>]*>//g' -e 's/ //g'"

$ tenki
曇時々雪

これで明日の天気が知りたくなったらtenkiとコマンドを打つだけでわかるようになりました。
僕はこれをbotにつぶやかせることで、朝にその日の天気と夜に翌日の天気を知ることができるようにしています。

temabotは現在30個ほどのShellワンライナーを定期的に走らせています。結構面白いのがこちら。

山岡家HPの更新を検知して僕とBaHoさん(山岡家に行き過ぎて破産したり、数週間の山岡家禁で精神壊す人)に通知してくれるものです。
1日1度だけ取得するようになっています。

@ 狭義のShell芸

実は本当に書きたかったのはここからになります。
まずは以下の問題をご覧ください

ある正の整数 n が入力されます。
n 回「Ann」と繋げて「AnnAnnAnnAnn......」と出力して下さい。

入力例
3

出力例
AnnAnnAnn

見たことある人も多いのではないでしょうか、Paiza Online Hackathon 7 恋愛SLG「プログラミングで彼女をつくる」の初級問題です。

paiza.jp

とりあえずこれをShell芸で解いてみましょう

$ num=$(cat -); for i in $(seq 1 $num); do echo -n Ann; done

見慣れない表記がいくつかありますが、ここでは省略します。 とりあえずPOH7内で提出形式をbashにして上記ワンライナーを打ち込むと正解となります。

でもちょっと待って欲しい。
本当に上記のShell芸はワンライナーと呼べるのか。

ShellScriptの改行はセミコロンで置き換えられると説明をしました。つまり、上記ワンライナーは以下のShellScriptと同義です。

num=$(cat -)
for i in $(seq 1 $num)
do
    echo -n Ann
done

これではいけませんね。
狭義のShell芸では改行を置き換えるセミコロンを使用しません

セミコロンがないとfor文、while文が書けないじゃんというそこのあなた。その通りです。for文、while文を使わずにどうループ処理を記述するかがShell芸がShell芸と呼ばれる所以なのです。

早速回答例です

$ cat - | xargs -IXX sh -c "yes Ann | head -n XX | tr -d '\n'"

"cat -"で標準入力を受け取ることができます。xargsコマンドで任意の場所に標準入力で受け取った値を入れているのがキモです。

ちなみに、$()等を使って

$ yes Ann | head -n $(cat -) | tr -d '\n'

とすると不正解です。詳しくはちょっとわかりませんが。
おそらく実行タイミングの関係だとは思います。

説明すると、yesコマンドでAnnを永遠に出力して、headで入力された値分だけとりだして、改行を消しています。

とまぁこんな感じの楽しい遊びがShell芸です。
以下はおまけです。

@ Shell芸頻出コマンド

yes

yesコマンドは"y"を永遠に出力するコマンドです。続けて文字列を指定することで、その文字列を永遠に出力します。
一見使い道のなさそうなコマンドですが、headと合わせることでforの代替が出来ます。

grep

grepは、受け取った入力あるいはファイルから、条件にマッチした行だけを出力するコマンドです。パイプでつなげることで、出力のフィルタとして扱うことが多いです。
-vオプションをつけると、「条件にマッチしない」ものを出力できます。

sed

sedはStreamEDitorの略で、コマンドでファイルの任意の行を削除したり、書き換えたりすることができます。パイプでつなぐと、入力を書き換えて出力することができます。 -eオプションで正規表現を記述できます。先程の例では、htmlタグを削除と前後の空白の削除に利用しました。-eオプションは一つのsedコマンドに複数個指定することができます。

xargs

xargsは、複数行の出力を一行ずつにして任意のコマンドに流すコマンドです。 xargsの利点として、受け取った入力を次のコマンドの任意の場所に持ってこれるというものがあります。 応用的な利用方法ですが、とても重宝するので覚えておくと良いでしょう。

awk

間違いなく最強コマンド。行を跨いだ計算が出来たりします。間違いなく一番学習コストの高いコマンド。
これを使うと、csvファイルの集計などがワンライナーで終わります。

echo-sd

_人人人人人人_  
> 突然の死 <  
 ̄Y^Y^Y^Y^Y^Y^ ̄  

を生成します。任意の文字列で突然の死が出来ます。

こんな感じに結果を賑やかせます。

@ Shell芸例

素数を出力するShell芸

$ eval eval \''n='\''{1..'$(dc -e 1000vp)'}'\'' eval eval eval echo '\'\\\\\\\\\\\\\\\'\\\\\\\'\\\'\''$(('\'\\\'\\\\\\\'\\\'\''$n'\'\\\'\\\\\\\'\\\'\''*'\'\\\'\\\\\\\'\\\\\\\\\\\\\\\'\\\'\''{2..$((1000/n))}'\'\\\'\\\\\\\\\\\\\\\'\\\\\\\'\\\'\''))'\'\\\'\\\\\\\'\\\\\\\\\\\\\\\'\'';'\' | tr ' ' \\n | sort -n | uniq -u

(出展: アンサイクロペディア「シェル芸」 http://ja.uncyclopedia.info/wiki/%E3%82%B7%E3%82%A7%E3%83%AB%E8%8A%B8

今日のゴミ出し情報を取得

  # 函館市ゴミ情報オープンデータ(作者:ぼく)(未完成)
  # [https://docs.google.com/spreadsheets/d/1p9VFqATrLQQeDXXNsmNl8LiCSOuz9o1yf7F92wW-B3U]
  # License: CC-BY 4.0
$ curl 'https://docs.google.com/spreadsheets/d/1p9VFqATrLQQeDXXNsmNl8LiCSOuz9o1yf7F92wW-B3U/export?format=csv' 2>/dev/null | grep $(date +%y-%m-%d) | cut -d , -f 5

ファンファンファン

社会性フィルター

$ alias 社会性フィルター="sed -e 's/.*/メリークリスマス!!/g'"
$ echo 'つらいめう' | 社会性フィルター
メリークリスマス!!

@ まとめ

本エントリでは、クリスマスに独り身でも寂しくない遊びを紹介しました。
大学のプログラミングの講義課題を是非Shell芸で解いてみたりしてみてください。

ちなみにShell芸で検索するとシェル芸人と呼ばれる所謂プロな方々が遊んでますので、それを眺めるのも楽しいと思います。ただし殆どは常人には理解不能なワンライナーです。

それでは、$(echo 'つらいめう' | 社会性フィルター)