blog.petitviolet.net

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