はじめに
こんにちは,痛風鍋が恋しくなってきたさわだです.
本記事はLOCAL学生部のAdvent Calendar 2019から拝借しています.
僕の前に投稿のあったmueruくんのKaitai Structというのは僕も初めて知りました.
公式サイトにあるIDEから手軽に試せるので面白かったです.
kodamaくんや工女さん,yamadaくんも時間のある時に投稿しておくれ!楽しみに待ってるぞい(^q^)
さて僕は普段使っているdotfiles改修の話です(何度目だdotfiles)
きっかけ
リモートサーバ複数台を使う作業が多い中Tmuxは自分にとって欠かせない存在です.一番作業で見てる画面ですから,日常的な情報の一つや二つはステータスラインに入れておきたいですよね?僕は入れておきたいです.そんな訳で以前の記事では天気を表示できるようにしました.
いつ頃だったでしょうか,Tmuxのステータスラインに利用していたYahooのお天気APIが仕様変更になり,手軽な存在では無くなりました.その辺は確かあっきぃさんがブログで言及していた気がします.アプリケーションで叩くAPIとしてはそれで良いのですが,只のシェルスクリプトで結果を取得するだけ,かつdotfilesで管理してる状況では面倒事だと感じてしまったので僕は別のAPIへ乗り換えることにしました.
また,つい最近,関東で地震が多発してることに怖くなり,地震情報の表示も欲しくなりました.本来であればtmux-powerlineを使えよ!と叫ぶ人も居るかと思います.しかし奴は既にオワコンです.2018年にメンテナンスモードになっています.代替のpowerlineは無駄にpython製で独自フォントを使うため,設定も複雑で処理は重いし拡張しにくいです.故に検索で出てくる大半のステータスラインに関する情報は(自分には)あてにならないと考えるべきです.前の記事でもtmux-powerlineについて僕は以下のように述べていますね...
いい感じの見た目とすべく,昔は先人の情報をもとにtmux-powerlineを使っていました. しかし,dotfilesで管理しにくい点,及びpyenv環境ではpowerline設定がややこしい点から廃止し,ステータスラインをフルスクラッチすることにしました.
無いものは自分で工夫して再現するしかありません.やっていきです.
Tmux weather
OpenWeatherMapのAPIへ変更することにしました.自分は予報ではなく現在の天気が知りたかったので,無料枠でそこそこ叩けるAPIとしてピッタリ*1.今までは xml
形式を正規表現で上手くパースしていたのですが,時代の流れに従い json
で取得します.シェルスクリプトで json
をパースする際はjqが大変便利です.sedみたいなものです.
https://stedolan.github.io/jq/
APIのドキュメントに従い,取得するお天気情報と表示内容を決めます.気温と天気がわかれば十分ですが,現時刻まで取得できるので日の出・日の入り時刻も取得して,晴れのとき夜なら月を表示する親切設計になりました.また,天気情報も英語でそのまま表示するのは残念なので,Nerd fontsに埋め込まれているWeather iconを活用します.天気用アイコンなのに宇宙人の襲来とかも想定されててなかなか面白いです.ユーモアがあります,大好き.
作成したスクリプトは以下のとおりです.
#!/usr/bin/env bash # # Author: takuzoo3868 # Last Modified: 27 Nov 2019. # API: https://openweathermap.org/current # # NEED API KEY in .bashrc_local: WEATHER_API ostype() { echo $OSTYPE | tr '[:upper:]' '[:lower:]'; } export SHELL_PLATFORM='unknown' case "$(ostype)" in *'linux'* ) SHELL_PLATFORM='linux' ;; *'darwin'* ) SHELL_PLATFORM='osx' ;; *'bsd'* ) SHELL_PLATFORM='bsd' ;; esac shell_is_linux() { [[ $SHELL_PLATFORM == 'linux' || $SHELL_PLATFORM == 'bsd' ]]; } shell_is_osx() { [[ $SHELL_PLATFORM == 'osx' ]]; } shell_is_bsd() { [[ $SHELL_PLATFORM == 'bsd' ]]; } export -f shell_is_linux export -f shell_is_osx export -f shell_is_bsd # Path tmp file export DIR_TEMPORARY="/tmp/tmux-weather_${USER}" if [ ! -d "$DIR_TEMPORARY" ]; then mkdir -p "$DIR_TEMPORARY" fi # DEFAULT WEATHER_DATA_PROVIDER_DEFAULT="openweathermap" WEATHER_UNIT_DEFAULT="metric" # metric:Celsius, imperial:Fahrenheit WEATHER_UPDATE_PERIOD_DEFAULT="600" WEATHER_LOCATION_DEFAULT="1864518" #chofu export WEATHER_DATA_PROVIDER="${WEATHER_DATA_PROVIDER_DEFAULT}" export WEATHER_UNIT="${WEATHER_UNIT_DEFAULT}" case "$WEATHER_UNIT" in "metric") export WEATHER_UNIT_CASE="c" ;; "imperial") export WEATHER_UNIT_CASE="f" ;; *) export WEATHER_UNIT_CASE="k" esac # How often to update the weather in seconds. export WEATHER_UPDATE_PERIOD="${WEATHER_UPDATE_PERIOD_DEFAULT}" # Name of GNU grep binary if in PATH, or path to it. export WEATHER_GREP="${WEATHER_GREP_DEFAULT}" # Your location. Find a code that works for you: export WEATHER_LOCATION="${WEATHER_LOCATION_DEFAULT}" # Setting grep command if shell_is_bsd && [ -f /user/local/bin/grep ]; then WEATHER_GREP_DEFAULT="/usr/local/bin/grep" else WEATHER_GREP_DEFAULT="grep" fi __default_settings() { if [ -z "$WEATHER_DATA_PROVIDER" ]; then export WEATHER_DATA_PROVIDER="${WEATHER_DATA_PROVIDER_DEFAULT}" fi if [ -z "$WEATHER_UNIT" ]; then export WEATHER_UNIT="${WEATHER_UNIT_DEFAULT}" fi if [ -z "$WEATHER_UPDATE_PERIOD" ]; then export WEATHER_UPDATE_PERIOD="${WEATHER_UPDATE_PERIOD_DEFAULT}" fi if [ -z "$WEATHER_GREP" ]; then export WEATHER_GREP="${WEATHER_GREP_DEFAULT}" fi if [ -z "$WEATHER_LOCATION" ]; then echo "No weather location specified."; exit 8 fi } # Run status line in tmux __run_weather() { __default_settings local tmp_file="${DIR_TEMPORARY}/weather_openweathermap.txt" local weather case "$WEATHER_DATA_PROVIDER" in "openweathermap") weather=$(__openweathermap_weather) ;; *) echo "Unknown weather provider [$WEATHER_DATA_PROVIDER]"; return 1 esac if [ -n "$weather" ]; then echo "$weather" fi } # Get the weather from OpenWeatherMap __openweathermap_weather() { if [ -f "$tmp_file" ]; then if shell_is_bsd; then last_update=$(stat -f "%m" ${tmp_file}) elif shell_is_linux || shell_is_osx; then last_update=$(stat -c "%Y" ${tmp_file}) fi time_now=$(date +%s) up_to_date=$(echo "(${time_now}-${last_update}) < ${WEATHER_UPDATE_PERIOD}" | bc) if [ "$up_to_date" -eq 1 ]; then __read_tmp_file fi fi degree="" if [ -z "$degree" ]; then weather_data=$(curl --max-time 4 -s "http://api.openweathermap.org/data/2.5/weather?id=${WEATHER_LOCATION}&units=${WEATHER_UNIT}&appid=${WEATHER_API}") # echo "$weather_data" if [ "$?" -eq "0" ]; then degree=$(echo "$weather_data" | jq .main.temp) condition=$(echo "$weather_data" | jq -r '.weather[] | .description') sunrise_unixtime=$(echo "$weather_data" | jq .sys.sunrise) sunset_unixtime=$(echo "$weather_data" | jq .sys.sunset) if shell_is_bsd; then date_arg='-j -f "%H:%M %p "' else date_arg='-d' fi sunrise=$(date ${date_arg} @${sunrise_unixtime} +%H%M) sunset=$(date ${date_arg} @${sunset_unixtime} +%H%M) elif [ -f "${tmp_file}" ]; then __read_tmp_file fi fi if [ -n "$degree" ]; then if [ "$WEATHER_UNIT_CASE" == "k" ]; then degree=$(echo "${degree} + 273.15" | bc) fi condition_symbol=$(__get_weather_image "$condition" "$sunrise" "$sunset" "$degree") echo "${condition_symbol} ${degree}°$(echo "$WEATHER_UNIT_CASE" | tr '[:lower:]' '[:upper:]')" | tee "${tmp_file}" fi } # Get symbol for condition. # Available conditions: https://openweathermap.org/weather-conditions __get_weather_image() { local condition="$1" local sunrise="$2" local sunset="$3" local degree="$4" case "$condition" in ## Group 800: Clear "clear sky") time_forecast=$(date +%H%M) if [ "$time_forecast" -ge "$sunset" -o "$time_forecast" -le "$sunrise" ]; then if [ "$degree" -le 5 ]; then echo "" else echo "" fi else if [ "$degree" -ge 25 ]; then echo "" # weather_hot else echo "" fi fi ;; ## Group 80x: Clouds "few clouds") echo "" ;; "scattered clouds") echo "" ;; "broken clouds" | "overcast clouds") echo "" # fa_cloud ;; ## Group 7xx: Atmosphere "dust" | "Haze" | "Smoke" | "sand" | "dust whirls") echo "" # weather_dust ;; "fog" | "mist") echo "" # weather_fog ;; "volcanic ash") echo "" ;; "tornado" | "tropical storm" | "hurricane") echo "" # weather_hurricane ;; ## Group 5xx: Rain & Group 3xx: Drizzle "rain" | "light rain" | "moderate rain" | "heavy intensity rain" | "drizzle" | "light intensity drizzle" | "heavy intensity drizzle" | "light intensity drizzle rain" | "drizzle rain") echo "" ;; "shower rain" | "scattered showers" | "very heavy rain" | "extreme rain" | "light intensity shower rain" | "heavy intensity shower rain" | "ragged shower rain" | "heavy intensity drizzle rain" | "shower rain and drizzle" | "heavy shower rain and drizzle" | "shower drizzle" | "squalls") echo "" # weather_showers ;; "mixed rain and snow" | "mixed rain and sleet" | "freezing drizzle" | "freezing rain" | "mixed rain and hail" | "Light rain and snow" | "Rain and snow") echo "" # weather_rain_mix ;; ## Group 6xx: Snow "Snow" | "light snow" | "Heavy snow" | "Sleet" | "Light shower sleet" | "Shower sleet" | "Light shower snow" | "Shower snow" | "Heavy shower snow") echo "" ;; ## Group 2xx: Thunderstorm "thunderstorm with light rain" | "thunderstorm with rain" | "thunderstorm with heavy rain" | "light thunderstorm" | "thunderstorm" | "heavy thunderstorm" | "ragged thunderstorm" | "thunderstorm with light drizzle" | "thunderstorm with drizzle" | "thunderstorm with heavy drizzle") echo "" # weather_lightning ;; "not available") echo "" ;; *) echo "" # unknown ;; esac } __read_tmp_file() { if [ ! -f "$tmp_file" ]; then return fi cat "${tmp_file}" exit } # exec __run_weather
データの取得は weather_data=$(curl --max-time 4 -s "http://api.openweathermap.org/data/2.5/weather?id=${WEATHER_LOCATION}&units=${WEATHER_UNIT}&appid=${WEATHER_API}")
です.curl使ってjson形式のお天気データを取ってきてます.WEATHER_LOCATION
は天気を知りたい地点, WEATHER_UNIT
は摂氏・華氏・ケルビンの選択になります.難点としてはAPIキーが必須になったことです.この点は bashrc_local
へ記載したキー値を読み込む形式で対応するしかなさそうです.もっとスマートな管理方法があったら教えて下さい.
その他にも負荷軽減のために,取得データの一時ファイルを作成し,作成時のタイムスタンプと現在時刻との差が設定時間を超えない限り,一時ファイルの内容を表示してデータ更新は行わないよう工夫してあります.一部過去のpowerlineに入っていたスクリプトを参考にしました.ソースコード中の四角で文字化けになってる箇所は,実際はこの様にフォントがハードコーディングされています.
Tmux earthquake
国産のAPIであるP2P地震情報 JSON APIを利用します.本来は利用者の揺れたという指標を元に各地での地震の影響範囲を推定することが目的のサービスなのですが,なんと嬉しいことに気象庁の地震情報と津波情報をJSONで取得できるのです,すごい有り難い.
何を表示すべきか非常に迷いましたが,作業中はとりあえず「地震があったこと」と「どこでどの程度の震度・津波の有無」がわかればサッとTwitterを見に行けると思いました(おい).なので最終的な表示はこんな感じです.天気もそうですが文字を極力使わないのは,文字で溢れているCUIの中でも視認性を高めるためです.あとは絵文字が好きだから.
今,とりあえず震度3以上で設定してるけれど,任意の震度で最新情報を取得できるようにした.
— さわだ.@UEC/NS (@takuzoo3868) December 6, 2019
実際に動作させるときは,表示有効時間を設定する予定 pic.twitter.com/mB9ZWppbSL
スクリプト内の変数として,プロバイダ,データ更新間隔,過去何時間までの地震を表示するか,地震の取得件数(最新のみなので1),震度幾つ以上のデータを取るか設定します.例としては以下の通り.
EARTHQUAKE_DATA_PROVIDER_DEFAULT="p2pquake" UPDATE_PERIOD_DEFAULT="200" ALERT_TIME_WINDOW_DEFAULT="200" # min EARTHQUAKE_GET_DATA_LIMIT_DEFAULT='1' EARTHQUAKE_MIN_SCALE_DEFAULT='10' export EARTHQUAKE_DATA_PROVIDER="${EARTHQUAKE_DATA_PROVIDER_DEFAULT}" export UPDATE_PERIOD="${UPDATE_PERIOD_DEFAULT}" export ALERT_TIME_WINDOW="${ALERT_TIME_WINDOW_DEFAULT}" export EARTHQUAKE_GET_DATA_LIMIT="${EARTHQUAKE_GET_DATA_LIMIT_DEFAULT}" export EARTHQUAKE_MIN_SCALE="${EARTHQUAKE_MIN_SCALE_DEFAULT}"
当然この辺も bashrc_local
などへ記載してベストな値を探ってみても良い気がします.今後の課題ですね.データの取得・整形・表示関連は以下のスクリプトを作りました.
# Run status line in tmux __run_earthquake() { __default_settings local tmp_file="${EARTHQUAKE_DIR_TEMPORARY}/earthquake.txt" local earthquake case "$EARTHQUAKE_DATA_PROVIDER" in "p2pquake") earthquake=$(__p2pquake_earthquake) ;; *) echo "Unknown earthquake-information provider [${EARTHQUAKE_DATA_PROVIDER}]"; return 1 esac if [ -n "$earthquake" ]; then echo "${earthquake}" fi } # Get earthquake information from p2pquake __p2pquake_earthquake() { if [[ -f "$tmp_file" ]]; then if shell_is_bsd; then last_update=$(stat -f "%m" ${tmp_file}) elif shell_is_linux || shell_is_osx; then last_update=$(stat -c "%Y" ${tmp_file}) fi time_now=$(date +%s) up_to_date=$(echo "(${time_now} - ${last_update}) < ${UPDATE_PERIOD}" | bc) if [ "$up_to_date" -eq 1 ]; then __read_tmp_file fi fi magnitude="" if [ -z "$magnitude" ]; then # get the rss file data=$(curl --max-time 4 -s "https://api.p2pquake.net/v2/jma/quake?limit=${EARTHQUAKE_GET_DATA_LIMIT}&min_scale=${EARTHQUAKE_MIN_SCALE}") if [ "$?" -eq "0" ]; then location=$(echo $data | jq -r .[].earthquake.hypocenter.name) magnitude=$(echo $data | jq .[].earthquake.hypocenter.magnitude) scale=$(echo $data | jq .[].earthquake.maxScale) tsunami=$(echo $data | jq -r .[].earthquake.domesticTsunami) timestamp=$(echo $data | jq -r .[].earthquake.time) if shell_is_bsd; then date_arg='-j -f "%H:%M %p "' else date_arg='-d' fi timestamp_24=$(date ${date_arg} "${timestamp}" +%H:%M) elif [ -f "$tmp_file" ]; then __read_tmp_file fi fi if [ -n "$magnitude" ]; then if __check_alert_time_window ; then scale_jp=$(__get_earthquake_scale "$scale") tsunami_jp=$(__get_tsunami_info "$tsunami") echo " ${location} ${timestamp_24} ${scale_jp} ${tsunami_jp} " | tee "${tmp_file}" fi fi } __check_alert_time_window() { unixtime=$(date -d "${timestamp}" +%s) # (now - occurred_time)/60 < ALERT_TIME_WINDOW ? [[ $(( ( $(date +%s) - unixtime ) / 60 )) -lt $ALERT_TIME_WINDOW ]] } __get_earthquake_scale() { local scale="$1" # https://www.p2pquake.net/develop/api-v2/ case "$scale" in "10") echo "1" ;; "20") echo "2" ;; "30") echo "3" ;; "40") echo "4" ;; "45") echo "5弱" ;; "50") echo "5強" ;; "55") echo "6弱" ;; "60") echo "6強" ;; "70") echo "7" ;; *) echo "" # unknown ;; esac } __get_tsunami_info() { local tsunami="$1" case "$tsunami" in "Warning") echo "津波予報" ;; "Watch") echo "津波注意報" ;; "NonEffective") echo "影響なし" ;; "Checking") echo "調査中" ;; "Unknown") echo "不明" ;; "None") echo "なし" ;; esac }
基本の仕組みはお天気のスクリプトと変わりません.APIを叩いてJSONをパースして,一時ファイルに表示用データを保存の流れになります.少し違うのは,天気が常に表示するものに対して,地震情報は発生したときのみ表示する点です.この点は ALERT_TIME_WINDOW
で設定します.また,震度の概念は日本特有なので,海外仕様に合わせるならマグニチュードの記載でも良いかもしれないです. 津波情報については予報が出ているかそうでないかの表示しか現在は対応していませんが,tsunami APIを活用して表示する情報を増やしてもいいかなと思っています. 今回,自分はTmux内に地震情報を取得できるスクリプトを導入しましたが,世の中にはVimに表示する方もいらっしゃるようです.同志の匂いがしました.
課題
とりあえず,アドカレに間に合うよう実装は終わらせましたが,当然課題はまだまだあります.
お天気なら
地点などの設定変更用オプションの追加
デバッグ用にverbose modeの追加
地震アラートなら
最適な表示方法
津波情報をより詳細に展開するための条件分岐
proxy環境対応
などなどです.自分のdotfilesはコミット数が192もあるのですが,まだ暫くは増える気がします()
おわりに
今回実装したスクリプトを .tmux_local/conf
へこちらのように組み込む事で,最終的な今のステータスラインは以下のようになっています.
いい感じですね.イケイケです!快適作業環境は復活しました.これにてめでたしめでたし!
また,ここがクソなんだけど!のような感想があればdotfilesのリポジトリへissue投げて欲しいです.優先的に改修します*2.
次は工大の後輩であるnayutaくんがなにか書くようです.楽しみに次の日を待ちたいと思います. それではまた別のアドカレで!