初歩のシェルスクリプトで遊ぶ[テキストファイルを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
  1. カーソルを端末画面の左上に移動させる
  2. 一行だけprintfで出力し、出力した文字で、描写されていた文字を上書きする。改行はしないで、カーソルは行末にとどまる。
  3. 行末から、画面右端までを、行で消去する。
  4. 改行して下の行へ移動する。
  5. 同様にprintfで文字を上書きして、同じ作業を繰り返す。
  6. 新規に出力する文字が終わったら、カーソル位置から下端までの画面を消去する。

という感じでどうかと。

主機能は「{}」で括ったブロックにまとまってて、標準入出力で済む。パイプで繋げばいいわけで、シェル関数化もしやすい。

このスクリプトを整理すると、主要部分は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で入れ替えます。