初歩のシェルスクリプトで遊ぶ[sedのモヤモヤを整理したい(3)]

使いやすいコマンドに絞り、注意が必要なコマンドを避ける

sedのコマンドにも、よく使うコマンド、あまり使わないコマンド、があります。よく使うのはsコマンド、あまり使わないのはGNU拡張の「シェルでシステムのコマンドを実行するe」とか。
シェルスクリプトsedを真似するときでも、思ったより簡単に真似できるものもあれば、意外とややっこしいものもある。
コマンドを大雑把に分けてみます。

  • 「while read PATTERN_SPACE ; do~」で書き始めた場合に、比較的手軽に実装できるコマンドは実装する
  • 基本の「while read」ループを書き換える必要がある、たとえば「while true ;do~」にしなければならないコマンドは、除外する
続きを読む

初歩のシェルスクリプトで遊ぶ[sedのモヤモヤを整理したい(2)]

今回はパターンスペースとホールドスペースを、シェルスクリプト製の簡易sedに実装します。
内容は「整理したい(1)」で書いた内容の再説明になってますが、繰り返して噛み砕きます。

「2p」は「2行目を出力」ではなく「行番号カウンターが2のとき、変数『パターンスペース』内の文字を全て出力」

# 2行目にマッチして出力?
$ seq 1 3 | sed -e '2p'
1
2
2
3
# 2行目にマッチして削除?
$ seq 1 3 | sed -e '2d'
1
3

前回「整理したい(1)」で例にした、2行目を重複出力する例と、定番の2行目を削除するスクリプト
特に「指定した行を削除するには」という目的で使われたり紹介されたりする例ですが、「2」は「2行目を指定して」と説明されることがあるようです。これが誤解の元になる。

続きを読む

初歩のシェルスクリプトで遊ぶ[sedのモヤモヤを整理したい(1)]

「極める気にはなれないが、ちょっとは便利に使いたい」

sedは私はそんな感じなのだけど。思いついたことがあって、それをメモがてらに書いていきます。
遊びで独学してる素人の勉強メモなんで、調べものには使わんといてください。資料を当たって間違い探しをして勉強する、のならOKです。
記事の趣旨は、「経験でなんとなく覚えたsedだが、よくわからないモヤモヤがあるから、整理したい」です。情報の正確さや実用性は度外視してます。

続きを読む

初歩のシェルスクリプトで遊ぶ[端末の256色カラーセレクタ(2)]

シェルスクリプトの簡易起動ランチャー


シェルスクリプトを起動する、簡易ランチャーです。
カラーセレクタじゃないけれど、中身はだいぶ共通してます。

引数を与えずに、単純に実行するだけ、のスクリプトを、中身を簡単に確認してから選択実行、ができます。

続きを読む

初歩のシェルスクリプトで遊ぶ[テキストファイルを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で入れ替えます。

初歩のシェルスクリプトで遊ぶ[キー入力をEnter無しで受け取りたいがよくわからん]

#!/bin/sh
# ====================================================================
# ■ キー入力テスト_ddコマンドでキーを押すとすぐに受け取る_0
# 
# ▼ 定番のコマンドで動くか?
# INPUT_KEY=`dd bs=1 count=1 2>/dev/null`
# 
# ・1バイト受け取るコマンドの定番らしい。これをもとに、1バイトから3バイト受け取りたい。
# ・無限ループの中でキー入力を受け取りたい。
# ・IMEが日本語入力になっているときでも、致命的な問題は起こりにくいようにしないと、使えない。
# 
# ====================================================================

ctrl_plus_C=`printf '\003'`
UP____KEY=`printf '\033[A'`
DOWN__KEY=`printf '\033[B'`
RIGHT_KEY=`printf '\033[C'`
LEFT__KEY=`printf '\033[D'`
LOOP_COUNT=0

while true ; do
LOOP_COUNT=$(( ${LOOP_COUNT} + 1 ))
echo "====== ${LOOP_COUNT} ======"
# ddコマンド実行前の下準備
stty_BAK=`stty -g` # sttyの現在の状態を丸ごとバックアップ
stty -echo         # 入力が端末に表示されないように、エコーバックをOFF
stty raw           # rawモードに切り替える



# (1) 3バイト指定して受け取る -------------------------------------------------
# IMEがOFFで、英数字を入力するなら問題ない
# 日本語文字列を一気に入力したりすると、何周もループしてしまう
#INPUT_KEY=`dd bs=3 count=1 2>/dev/null `

# (2-1)バイト数を指定せずに1キー受け取る --------------------------------------
# 長い文字列を一気に入力しても何周もループはしない。
# 無指定は不安も残る
#INPUT_KEY=`dd count=1 2>/dev/null`


# (2-2)先頭3バイトのみ残す ----------------------------------------------------
INPUT_KEY=`dd count=1 2>/dev/null | cut -b 1-3`




# ddコマンド実行後の後始末
#stty -raw echo
stty "${stty_BAK}"


# 入力されたキーを そのまま表示する
printf "%s\n" "KEY:${INPUT_KEY}"
# 入力されたキーを 16進数で表示する
printf "%s" "${INPUT_KEY}" | od -A n  -t x1 -v
# 入力されたキーを 8進数で表示する
printf "%s" "${INPUT_KEY}" | od -A n  -t o1 -v


# キー入力を受け取って処理を分岐する
case "${INPUT_KEY}" in
( ${UP____KEY} )
	date
;;
( ${DOWN__KEY} )
	cal
;;
( ${RIGHT_KEY} )
	pwd
;;
( ${LEFT__KEY} )
	ls
;;
( '/' | 'q' |  "${ctrl_plus_C}" ) # 終了する
	exit 0
;;
( * )
	true
;;
esac


done

readコマンドだと、文字を入れてからEnterが要る。これしないで入力を受け取りたい。
キーボードのカーソルキーで、こう、あれだ。ドラゴンクエストのコマンド選択で、矢印が動くみたいなの。十字キーで動かして、Aボタンで決定するとか。あれをやりたいんだが、よくわからん。
矢印キーは3バイトの入力になってるようなので、これを受け取れればできそうなんだが。
検索して出てくるのはddコマンドで1バイト指定して受け取るものなのだけれど、これだとwhileループが何回も回ってしまう。
いくつか試したところだと、結局は「count=1」だけにして、入力を受け取ってから、長すぎるぶんはcutで削るなりするのが、なんだかんだで簡単、なのかなぁ。いちおう動いちゃいるんだが。

実用するとなると、たとえば、うっかり日本語入力にしていて、ひらがなカタカナ漢字を何文字も一気に流し込んでしまった、ということが起きても、問題にならないようにはしなきゃなんないわけです。