petitviolet blog

    shellでTTL付きのキャッシュをしたい

    2017-05-04

    QiitaZshshell

    こんな感じ

    sleep 3 ; echoの結果を最大 5 秒間キャッシュしている様子。

    cache-ttl-on-sh.gif

    キャッシュの実装

    実装はこのあたりに置いてある。

    TTL 付きのキャッシュを実装するにあたり、シェルで実現するならファイルに保存しておいて、作成日時はそのファイルのメタ情報を利用すれば楽。
    また、やろうと思えば更新日時と作成日時がどちらも取れるので、細かい TTL の実装も可能。

    stat -c %Yコマンドで指定したファイルの最終更新日時の epoch 秒を取得できる。 それと現在時刻を比べて指定した時間より経過していたら...という処理を書くことで TTL を実装する。

    statを使ってファイルの有効期限判定するには以下のようにする。

    $ touch -d '1day ago' hoge.txt
    $ [ $(( $(date +%s) - $(stat -c %Y hoge.txt) )) -gt $((60 * 60 * 25)) ] && echo 'true' || echo 'false'
    false
    $ [ $(( $(date +%s) - $(stat -c %Y hoge.txt) )) -gt $((60 * 60 * 23)) ] && echo 'true' || echo 'false'
    true
    

    キャッシュを保存するファイルのパスと TTL、実行するコマンドを渡せるようにするとこんな感じになった。

    # TTL的に無効かどうか
    function is_too_old() {
      local file=$1
      local ttl=$2 # seconds
      if [ -f $file ]; then
        [ $(( $(date +%s) - $(stat -c %Y "$1") )) -gt $ttl ]
      else
        false
      fi
    }
    
    # TTLを考慮して読み出す
    function read_from_cache() {
      local cache=$1
      local ttl=$2  # seconds
      local command=$3
      if $(is_too_old $cache $ttl);then
        local result=$(sh -c "$command")
        if [ -z $result ]; then
          return
        else
          echo $result > $cache
        fi
      fi
      cat $cache
    }
    

    ちなみに Mac だと gnubin を入れる必要がある。 テキスト処理のための標準的なコマンド群の OS X への導入手順 - Qiita

    コードを読めばわかるが、例えばgcloud projects listの結果を 1 日キャッシュするには、

    read_from_cache ./cache.txt $((60 * 60 * 24)) "gcloud projects list"
    

    みたいにやれば良い。

    "読み込み中"の表示

    昔書いた記事に大体ある。 Zsh で長い処理をしている間に読込中を表示する - Qiita

    loading コマンドはgithubにおいてある。

    読み込み中を表示するあたりの実装はこうなる。

    # job制御を無効化してバックグラウンド実行しているプロセスのIDが表示されるのを防ぐ
    set +m
    # loadingをバックグラウンド実行して表示
    loading 1000 &
    local loading_pid=$!
    
    # 何か重い処理をする
    local result=$(sh -c "$command")
    
    # loadingを消す
    kill -INT $loading_pid &>/dev/null
    # loadingがkillされるのを待つ
    sleep 0.1
    # job制御を有効に戻す
    set -m
    

    ジョブ制御を無効にしてプロセス ID が表示されてしまうのを防ぐのがポイント。

    $ sleep 10000 &  # IDが表示される
    [1] 89535
    $ kill $!  # IDが表示される
    [1]  + terminated  sleep 10000
    $ set +m
    $ sleep 10000 &  # IDが表示されない
    $ kill $!  # IDが表示されない
    $ set -m
    

    from: https://qiita.com/petitviolet/items/6c68cf414050fa8b83d6