petitviolet blog

    Zshで長い処理をしている間に読込中を表示する

    2014-12-21

    QiitaZshshellgradle

    内容

    • shell でのバックグラウンド処理 - ジョブ制御フラグ
    • メタ文字による標準出力の管理

    Android - Gradle を Zsh で補完する - Qiitaという記事を書いた時に

    なお、初回のみタスクの読み込みに時間がかかるが、2 回目以降はファイルキャッシュを読み込むため比較的速くなる

    と書いたが、初回の読み込み時の長さが気になるので読み込み中である indicator を表示することにした

    参考

    Python でローディングのぐるぐるを表示する - CAMPHOR- Tech Blog

    完成品

    loading_tasks.gif

    ./gradlewのあとでTABを押すとtasksを読み込む 読んでる間の時間が長いので、indicator を出して読み込み中であることを示している

    コード

    gistも更新した

    _gradle
    #!/usr/bin/env zsh
    
    # refs: https://gist.github.com/nolanlawson/8694399 : bash-version
    
    loading() {
      local count=30
      if [ $# -eq 1 ]; then
        count=$1
      fi
      # KILL -INT をtrapして最後の出力を消す
      trap "echo -en '\r                      '; exit 0" INT
    
      # 改行
      echo
      for i in `seq 1 1 $count`
      do
        echo -en '|\r' ; sleep 0.05;
        echo -en '/\r' ; sleep 0.05;
        echo -en '-\r' ; sleep 0.05;
        echo -en '\\\r'; sleep 0.05;
      done
      exit 0
    }
    
    _gradle() {
      local cur="$1"
      local gradle_cmd='gradle'
      if [[ -x ./gradlew ]]; then
        gradle_cmd='./gradlew'
      fi
      if [[ -x ../gradlew ]]; then
        gradle_cmd='../gradlew'
      fi
    
      local completions=''
      local cache_dir="$HOME/.gradle_tabcompletion"
      mkdir -p $cache_dir
    
      local gradle_files_checksum='hoge';
      if [[ -f build.gradle ]]; then # top-level gradle file
        if [[ -x `which md5 2> /dev/null` ]]; then # mac
          local all_gradle_files="$(find . -name build.gradle 2> /dev/null)"
          gradle_files_checksum="$(md5 -q -s "${all_gradle_files}")"
        else # linux
          gradle_files_checksum="$(find . -name build.gradle | xargs md5sum | md5sum)"
        fi
      else
        gradle_files_checksum='no_gradle_files'
      fi
    
      if [[ -f $cache_dir/$gradle_files_checksum ]]; then # cached! yay!
        completions=$(\cat $cache_dir/$gradle_files_checksum)
      else
        ################################
        # ジョブ制御を無効化
        set +m
        # バックグラウンドでindicatorを回す
        loading 1000 & >/dev/null 2>&1
        set -m
        # indicatorのprocess id
        local LOADING_PID=$!
        completions=$($gradle_cmd --no-color --quiet tasks | grep ' - ' | awk '{print $1}' | tr '\n' ' ')
        # indicatorを殺す
        kill -INT $LOADING_PID
        ################################
        if [[ ! -z $completions ]]; then
          echo $completions > $cache_dir/$gradle_files_checksum
        fi
      fi
    
      local -a commands
      commands=( "${(z)completions}" )
      compadd $commands
      return 0;
    }
    
    compdef _gradle gradle
    compdef _gradle gradlew
    compdef _gradle ./gradlew
    

    読み込み中の表示

    loading関数は引数を 1 つとってその引数回数だけ indicator を回転させるものとなっている whileを使って無限ループにしても良いと思う \rは復帰文字と呼ばれるもので、標準出力で今いる行の先頭にカーソルを動かすイメージのもの \bはバックスペース 参考:シェル・スクリプト・リファレンス - 【 メタ文字の取り扱い 】:ITpro

    くるくる回る棒を2>&1して標準エラー出力にリダイレクトしても良いが、どうせ後で消すのでここではしなかった

    この読み込み中のものだけ切りだすとこんな感じ

    loading.sh
    #!/usr/bin/env zsh
    
    loading() {
      local count=30
      if [ $# -eq 1 ]; then
        count=$1
      fi
    
      for i in `seq 1 1 $count`
      do
        echo -en '|\b'  1>&2; sleep 0.05;
        echo -en '/\b'  1>&2; sleep 0.05;
        echo -en '-\b'  1>&2; sleep 0.05;
        echo -en '\\\b' 1>&2; sleep 0.05;
      done
      # 最後の出力を消す
      echo -en ' \b' 1>&2;
      exit 0
    }
    
    loading $@
    

    loading.gif

    新しいバージョンだとBashでも動きます

    読み込み中を隠す

    普通に&をつけてバックグラウンド処理をするとバックグラウンドに移った処理のプロセス ID が表示されてしまいます

    loading_background.gif

    鬱陶しいのでset +mとしてジョブ制御をしないフラグを立てることでこれを表示しないように出来る 参考: Linux コマンド集 - 【 set 】 シェルのオプションを設定する:ITpro

    loading_no_job.gif

    直前に実行したプロセスの ID は$!で取得出来るので、 これに対して読み込み中の表示を消したいタイミングでkillを送ってプロセスを殺す killで送られてきたシグナルを

    trap "echo -en '\r                      '; exit 0" INT
    

    のようにtrapINTシグナルを受け取って標準出力を掃除してからexitするようにしている

    心残り

    どうしても

    [8] + done loading 1000

    といったkill時に表示されるメッセージを消せなかった これはkillした側の shell で表示されるらしく、killする部分をサブプロセスにするなど、手を尽くしたがだめだった

    from: https://qiita.com/petitviolet/items/5cc1916eb3fdb8d54823