初歩のシェルスクリプトで遊ぶ[テキストファイルをlessっぽく表示するときのチラつき対策]
テキストファイルを一部表示させて、スクロールさせたい
#!/bin/sh # テキストファイルを端末に表示して、 # スクロールっぽいことをするテスト # # (1) 基本 # clearで消してから、書き直す # # textFile="${1}" # 表示するテキストの開始行 ORIGIN='1' # 表示するテキストの行数 COUNT='24' # 重い処理 heavyFunc(){ for NN in `seq 1 300` ; do echo "${NN}" | cut -c 1 | tr -d '\n' done } while true ; do { heavyFunc cat "${textFile}" } | tail -n +${ORIGIN} | head -n ${COUNT} | { clear cat - } # 「Enterまたは3」下へスクロール # 「9」上へスクロール read XX case "${XX}" in ( 9 ) ORIGIN=$(( ${ORIGIN} - 4 )) ;; ( 3 | '' ) ORIGIN=$(( ${ORIGIN} + 4 )) ;; esac done
なにがしかのテキストファイルを端末に表示して、lessっぽくスクロール表示をさせたい。テキストファイル以外にも、何かしらを表示しながら。
まず簡単に「clear」で全消去、続けて全表示、としてみた。これでは当然、ちらつく。
上記スクリプトみたいに軽い処理だと、いちいちclearしても、そんなにチラつきません。しかし実用するとそういうわけにもいかず、鬱陶しくちらつきます。キーを押すたびにチラチラチカチカと。
これを、複雑で難しいことをやらずに、ほどほどに改善したい。
#!/bin/sh # テキストファイルを端末に表示して、 # スクロールっぽいことをするテスト # # (2) 行単位で書き換える # エスケープシーケンスを試す # # textFile="${1}" # 表示するテキストの開始行 ORIGIN='1' # 表示するテキストの行数 COUNT='24' # 重い処理 heavyFunc(){ for NN in `seq 1 300` ; do echo "${NN}" | cut -c 1 | tr -d '\n' done } while true ; do printf "\033#8" # テスト用_画面を「E」で埋める { heavyFunc cat "${textFile}" } | tail -n +${ORIGIN} | head -n ${COUNT} | { #printf '\033[?25l' # カーソルを透明化 # 1.カーソルを左上へ移動する tput cup 0 0 #printf '\033[1;1H' # tput cup 0 0 と同様 # 2. 行処理 while read -r LINE ; do printf '%s' "${LINE}" # 読み込んだ1行を改行無しで出力 sleep 0.1 printf '\033[0K\n' # カーソル位置から行末まで削除し、改行 sleep 0.1 done # 3.残り画面下端まで消去 printf '\033[?0J' # カーソル位置から画面下端までを消去 #printf '\033[?25h' # カーソルを可視化 } read XX case "${XX}" in ( 9 ) ORIGIN=$(( ${ORIGIN} - 4 )) ;; ( 3 | '' ) ORIGIN=$(( ${ORIGIN} + 4 )) ;; esac done
- カーソルを端末画面の左上に移動させる
- 一行だけprintfで出力し、出力した文字で、描写されていた文字を上書きする。改行はしないで、カーソルは行末にとどまる。
- 行末から、画面右端までを、行で消去する。
- 改行して下の行へ移動する。
- 同様にprintfで文字を上書きして、同じ作業を繰り返す。
- 新規に出力する文字が終わったら、カーソル位置から下端までの画面を消去する。
という感じでどうかと。
主機能は「{}」で括ったブロックにまとまってて、標準入出力で済む。パイプで繋げばいいわけで、シェル関数化もしやすい。
このスクリプトを整理すると、主要部分は4行程度にまとめられます。
#!/bin/sh # テキストファイルを端末に表示して、 # スクロールっぽいことをするテスト # # (3) 行単位で書き換える # スクリプトを整理する。 # ddコマンドを使い、キーを押すとEnterしなくても反応するように。 # textFile="${1}" # 表示するテキストの開始行 ORIGIN='1' # 表示するテキストの行数 COUNT='24' # 重い処理 heavyFunc(){ for NN in `seq 1 300` ; do echo "${NN}" | cut -c 1 | tr -d '\n' done } while true ; do { heavyFunc cat "${textFile}" } | tail -n +${ORIGIN} | head -n ${COUNT} | { # sed用のエスケープ文字を作成 ESC=`printf '\033'` # カーソルを透明化,カーソルを左上へ移動 printf '\033[?25l''\033[1;1H' # タブを半角空白に入れ替え,行末まで削除 sed -e 's/ / /g' -e "s/$/${ESC}[0K/1" # カーソル位置から画面下端までを消去,カーソルを可視化 printf '\033[?0J''\033[?25h' } # キー入力をEnter無しで受け付ける stty_BAK=`stty -g` stty -echo raw XX=`dd count=1 2>/dev/null` stty ${stty_BAK} case "${XX}" in ( 9 ) ORIGIN=$(( ${ORIGIN} - 4 )) ;; ( 3 ) ORIGIN=$(( ${ORIGIN} + 4 )) ;; ( * ) exit 0 ;; esac done
キー入力を受けると、Enterしなくてもスクロールっぽく表示します。重い処理が挟まれても、ちらつきは改善します。
だいたい「while read」と同じ処理なんですが、水平タブを半角空白に入れ替える処理を加えました。はてなブログで見ると空白にしか見えんっすな。
タブだと、上書きして消せません。代わりに半角空白で上書きするように、sedで入れ替えます。