ハトネコエ Web がくしゅうちょう

プログラミングとかAndroid

Wi-Fiの接続先が変化した際にシェルスクリプトを走らせる(Mac OSX)

すごく稀なユースケースだと思うんですが、
例えば研究室の無線LANに接続しているときだけプロキシ設定を有効にし、それ以外のときは無効にしたい場合を考えます。

通常であれば、研究室のWi-Fiに繋いだら

export http_proxy="http://www-proxy.waseda.jp:8080"
export HTTPS_PROXY="http://www-proxy.waseda.jp:8080"

環境変数の設定をして、家に戻ったら

unset http_proxy HTTPS_PROXY

環境変数をリセットさせる。

このようなことを何度も繰り返します。
でも、間違いなくめんどうです!

そこで、接続先の無線LANが変化した(異なるSSIDのネットワークに接続した)際に、プロキシ設定を更新することを考えます。

1. シェルの hook を考える

通常、特定のシェルスクリプトを何度も走らせたい場合、cronを用いることが一般的だと思いますが、
今回は、特定のイベントがおこなわれた際に発火する hook を使ってみます。

シェルの hook については、こちらのサイトが詳しく説明してくれていました。
大変ありがたかったです。

シェルでコマンドの実行前後をフックする - Hibariya ( http://note.hibariya.org/articles/20170219/shell-postexec.html )

hook の種類は少ないですが、
そこは if 文でがんばるとして、
preexec という、シェルに入力したコマンドが実行される直前に発火されるというこの hook を使ってみましょう。

2. 接続中の SSID を取得する

ここのサイトで知りました。

https://stackoverflow.com/questions/4481005/get-wireless-ssid-through-shell-script-on-mac-os-x

/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I | awk '/ SSID/ {print substr($0, index($0, $2))}'

これを実行すると、たしかに現在接続中のSSID名称が出てきます。すごい。

SSID取得の方法がわかったので、この値を環境変数として記録しておいて、
次回呼び出したときに、前回のSSIDと変わっているのかを確認すれば良さそうです。

SSIDが変更されたかの条件分岐を最初におこなうことで、
コマンド実行ごとに発火されるこの hook によるシェルへの負荷を、できるだけ少なくしてあげることが出来ます。

3. できあがるスクリプト

上の 1. , 2. を参考にすると、例えば以下のようなスクリプトが出来上がります。

zshbashに対応させました。(bashの方は検証が足りていないので、もしかすると上手く動かないかも…)

# Check if SSID is changed, and update proxies
function check_ssid() {
  local -r LABO_PROXY="http://www-proxy.waseda.jp:8080"
  
  local previous_ssid="$CURRENT_SSID"
  local current_ssid=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I | awk '/ SSID/ {print substr($0, index($0, $2))}')

  export CURRENT_SSID="$current_ssid"

  if [ "$previous_ssid" != "$current_ssid" ] ; then
    case "$current_ssid" in
      "ssid_of_labo") # SSID of your labo's network
        export http_proxy="$LABO_PROXY"
        export HTTPS_PROXY="$LABO_PROXY"
        ;;
      *)
        unset http_proxy HTTPS_PROXY
        ;;
    esac
  fi
}

function add_shell_hook() {
  local shell_pid=$$

  if ps -p $shell_pid | grep -qs "zsh" ; then
    autoload -Uz add-zsh-hook
    add-zsh-hook preexec check_ssid
  elif ps -p $shell_pid | grep -qs "bash" ; then
    local -r BASH_PREEXEC_PATH="${HOME}/.bash-preexec.sh"
    if [ ! -e $BASH_PREEXEC_PATH ]; then
      curl -s https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o $BASH_PREEXEC_PATH
    fi

    source $BASH_PREEXEC_PATH
    preexec() { check_ssid; }
  else
    echo "Error: This script supports only zsh and bash." 1>&2
  fi
}

add_shell_hook

これを ~/.zshrc (もしくは ~/.bashrc )に直書きするか、
もしくはシェルスクリプトに記述して、 ~/.zshrc ( ~/.bashrc ) から source コマンドで呼び出すようにすれば、hook が登録され、
SSID が変わるごとにプロキシ設定が更新されるようになります。

もちろん、 LABO_PROXY"ssid_of_labo" の値は適当に変えてくださいね。

4. 注意点

私のハマったところとして、
このシェルスクリプト内で export CURRENT_SSID="$current_ssid" と export している箇所がありますが、
単純なシェルスクリプトとして実行させていたときは CURRENT_SSID環境変数として保存されず悩んでいました。

【追記あり】シェルスクリプト内でexportした環境変数は実行後でも使えるのか - by shigemk2 ( https://www.shigemk2.com/entry/linux_shell_export_path )

これによると、

シェルスクリプトを実行する際には、実行するための新たな環境ができるので、環境変数はプロンプトの環境に引き継がれません

ということでとても合点がいきました。
スクリプトを使って現在の shell に環境変数を読み込ませるためには、
.zshrc に直接書くなり、 source コマンドで呼び出すなりしないといけないのですね。

5. まとめ

以上、shell の hook 機能と、
SSIDを取得できることを利用したプチハックでした。

これの延長線上で、いろいろ便利なことができそうな気がします。

以下に、より細かい設定を加えたバージョンの gist を貼っておきます。ご参考にどうぞ。