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

16色版v4.1


写真を出すの忘れてた。16色版だとこんなんです。操作系とか256色とだいぶ違いますが、そのぶんスクリプトが簡略化されてます。
多少変更を加えて4.1にして置いときます。

  • 一部をsedスクリプトに変更して、特にprintfが外部コマンドになるkshでの動作速度を改善した

sedは読みにくいので迷ったのだけれど、せっかく書いたし、いいや組み込んじゃえと。

#!/bin/sh
#!/bin/ksh
#!/bin/bash
#!/usr/bin/posh
#!/bin/zsh
#!/bin/busybox sh
#!/usr/bin/yash


# ========================================================================================
# ■ 端末カラーセレクター16色 v4.1
# https://twitter.com/beta_reverse_2
# ========================================================================================
# ▼ 操作
# 上キー-下キー,8-2:フォーカス移動
# 左キー-右キー,4-6:色選択のとき 色を選択決定
#                   文字装飾のとき フォーカス左右移動
# スペース,0:文字装飾のとき フォーカスの値を有効/無効切替
# /,q,Ctrl+C:終了
# s,-:メインサンプルとサブサンプルの色を入れ替える
# *  :フリーサンプル文字を書き換える
# 
# ▼ 動作するシェル
#!/bin/dash
# 
# ▼ 簡単にテスト済み
#!/bin/bash
#!/bin/busybox sh
#!/usr/bin/yash
#!/bin/zsh
# 
# ▼ 動作はするが非推奨_遅い
#!/bin/ksh
#!/usr/bin/posh
# 
# ▼ 動かない
#!/usr/bin/fish
# 

# cd "$(dirname "`readlink -f "${0}"`" )" # ====[shMerge<disable>]==== #_shMergeDisabled
# PATH="./lib:${PATH}" # ====[shMerge<disable>]==== #_shMergeDisabled

# **************************************************************************************
# ユーザ設定_任意
# **************************************************************************************

# サンプルテキスト
# 横幅推奨値='                                                  '
  STD_SAMPLE='                             standard sample text '
 MAIN_SAMPLE='                                 main sample text '
  SUB_SAMPLE='                                  sub sample text '
 FREE_SAMPLE='                                 free sample text '

# # ====[shMerge]====	effectCodeList16sh	20201106_205011
effectCodeList16sh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 16色版の文字装飾コード
# このシェル変数を書き換えるだけで、カスタマイズ可能。
# 改行区切りで数字を追加削除すること。

LF='
'
codeList="0${LF}1${LF}2${LF}3${LF}4${LF}5
6${LF}7${LF}8${LF}9${LF}10${LF}11
12${LF}13${LF}14${LF}15${LF}16${LF}17
18${LF}19${LF}20${LF}21${LF}22${LF}23
24${LF}25${LF}26${LF}27${LF}28${LF}29
51${LF}52${LF}53${LF}54${LF}55${LF}56
60${LF}61${LF}62${LF}63${LF}64${LF}65"

echo "${codeList}"
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	helpMessageC16sh	20201106_205011
helpMessageC16sh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 操作説明_不要ならtrueのみに

printf \
'\033[7m%s\033[27m %s  '\
'\033[7m%s\033[27m %s  '\
'\033[7m%s\033[27m %s\n'\
'\033[7m%s\033[27m %s  '\
'\033[7m%s\033[27m %s  '\
'\033[7m%s\033[27m %s  '\
'\033[7m%s\033[27m %s\n' \
 ' up down '    'フォーカス切替' \
 ' left right ' '選択決定,選択' \
 ' space 0 '    'チェック' \
 ' + a '        'コード表示'\
 ' - s '        'mainとsubを入替' \
 ' * '          'サンプル書換' \
 ' / q '        '終了'
true
) }	# ====[shMerge</ins>]====

# ======================================================================================
# シェル関数_外部へ分離可能
# ======================================================================================
# # ====[shMerge]====	pressKeySh	20201106_205011
pressKeySh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# キー入力を1回のみ受け取る
stty_BAK=`stty -g`
stty -echo raw
dd count=1 2>/dev/null
stty "${stty_BAK}"
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	pressKeyScan16sh	20201106_205011
pressKeyScan16sh(){ (	# ====[shMerge<ins>]====
#!/bin/sh

focusID=${1:-1}

# 操作キー取得
# eval `printf 'ESC=\033 ctrl_plus_C=\003 ENTER_KEY=\015 '` # 【gloval const ESC ctrl_plus_C ENTER_KEY】# ====[shMerge<disable>]==== #_shMergeDisabled

UP____KEY="${ESC}[A"
DOWN__KEY="${ESC}[B"
RIGHT_KEY="${ESC}[C"
LEFT__KEY="${ESC}[D"
SPACE_KEY=' '
TAB______KEY='	'
PAGE_UP___KEY="${ESC}[5~"
PAGE_DOWN_KEY="${ESC}[6~"


stty_BAK=`stty -g`
stty -echo raw


while true ; do


case "`dd count=1 2>/dev/null`" in
# ----------------------------------------------------------------------
( 6 | "${RIGHT_KEY}" ) 
# ----------------------------------------------------------------------
case "${focusID}" in
( 0 )
echo '0__RIGHT'
break 1
;;
( 1 )
echo '1__RIGHT'
break 1
;;
( 2 )
echo '2__RIGHT'
break 1
;;
esac

;;
# ----------------------------------------------------------------------
( 4 | "${LEFT__KEY}" ) 
# ----------------------------------------------------------------------
case "${focusID}" in
( 0 )
echo '0__LEFT'
break 1
;;
( 1 )
echo '1__LEFT'
break 1
;;
( 2 )
echo '2__LEFT'
break 1
;;
esac

;;
# ----------------------------------------------------------------------
( 8 | "${UP____KEY}" | "${PAGE_UP___KEY}" ) 
# ----------------------------------------------------------------------
echo '_UP'
break 1

;;
# ----------------------------------------------------------------------
( 2 | "${DOWN__KEY}" | "${TAB______KEY}" | "${PAGE_DOWN_KEY}" ) 
# ----------------------------------------------------------------------
echo '_DOWN'
break 1

;;
# ----------------------------------------------------------------------
( s | '-' ) 
# ----------------------------------------------------------------------
echo '_MINUS'
break 1

;;
# ----------------------------------------------------------------------
( 0 | "${SPACE_KEY}" ) 
# ----------------------------------------------------------------------
case "${focusID}" in
( 0 )
	true
;;
( 1 )
	true
;;
( 2 )
echo '2__SPACE'
break 1
;;
esac

;;
# ----------------------------------------------------------------------
( '*' )
# ----------------------------------------------------------------------
echo '_ASTER'
break 1

;;
# ----------------------------------------------------------------------
( '+' | a )
# ----------------------------------------------------------------------
echo '_PLUS'
break 1

;;
# ----------------------------------------------------------------------
( "${ENTER_KEY}" )
# ----------------------------------------------------------------------
echo '_ENTER'
break 1

;;
# ----------------------------------------------------------------------
( '/' | 'q' | "${ctrl_plus_C}" ) 
# ----------------------------------------------------------------------
echo '_QUIT'
break 1

;;

esac
done


stty "${stty_BAK}"
exit 0
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	colorRadioSedSh	20201106_205011
colorRadioSedSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh

# カラーサンプルとラジオボタン
TARGET="${1}" # 0-15

echo "40_0
41_1
42_2
43_3
44_4
45_5
46_6
47_7
N_N
100_8
101_9
102_10
103_11
104_12
105_13
106_14
107_15
N_N" | sed -n -e '

/_'${TARGET}'$/{
s/^\([^_]*\)_.*$/\o033[\1m    \o033[0;7m*\o033[0;\1m    /
  H
  d
}
/^[41]/{
s/^\([^_]*\)_.*/\o033[\1m         /1
  H
  d
}

/^N/{
  g
  s/\n//g
  s/$/\o033[0m/
  p
  s/.*//
  h
  d
}
'
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	overWriteFocusMarkSedSh	20201106_205011
overWriteFocusMarkSedSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh

focusNo="${1}"


#printf '\033[1;1H''\033[?25l'
sed -n -e '#n

# 1行目行頭にエスケープシーケンス埋め込み
1{
s/^/\o033[1;1H\o033[?25l/
}
# 行末改行前にエスケープシーケンス埋め込み
${
s/$/\o033[J\o033[?25h/

}

# フォーカスしているならば
/^FOCUS_ID_'${focusNo}'$/{
# 上書きして次の行を読み込む
n
# 矢印を右端に付加
s/$/  \o033[7m <= \o033[0m/

# ↓のフォーカスID削除には該当せず通常出力へ
}

/^FOCUS_ID_/ {
# フォーカスID行を出力せずにcontinue
d
}

# フォーカスIDではない他の全行ならば
{
s/$/\o033[0K/         # 行末尾に行右側消去を埋め込み
p                     # 出力
}
'
#printf '\033[?0J''\033[?25h'
#printf '\033[J''\033[?25h'
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	transFgNoSh	20201106_205011
transFgNoSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 色番号とSGRの番号を変換する
# ${1}:数値

# 0-7 => 30-37
  if test "${1}" -ge 0 && test "${1}" -le 7 ; then
echo $(( ${1} + 30 ))

# 8-15 => 90-97
elif test "${1}" -ge 8 && test "${1}" -le 15 ; then
echo $(( ${1} + 82 ))

fi
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	transBgNoSh	20201106_205011
transBgNoSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh

# ${1}:数値

# 0-7 => 40-47
  if test "${1}" -ge 0 && test "${1}" -le 7 ; then
echo $(( ${1} + 40 ))

# 8-15 => 100-107
elif test "${1}" -ge 8 && test "${1}" -le 15 ; then
echo $(( ${1} + 92 ))

fi
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	effectCodeBuildSh	20201106_205011
effectCodeBuildSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 実用できる文字装飾コードを組み立て
eval 'set -- '${1}
IFS=';'
echo "${*}"
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	effectCodeCheckSh	20201106_205011
effectCodeCheckSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
#!/usr/bin/zsh
# 文字装飾コードのチェックシートを表示する

# ESC=`printf '\033'` # 【gloval const ESC】# ====[shMerge<disable>]==== #_shMergeDisabled

# ${1}:フォーカスのID値(${2}のデータの行番号-1)
focusIdNo=${1}
# コードリストの行番号(${1}で指定される数値)
codeIdCount='-1' # forループ1回ごとに+1

# 出力は1行ずつechoで
echoLine=''

# ${2}:コードの番号*1000 のリスト 有効/無効(正/負)
# コマンド置換で「-1000 LF -1001 LF 1002 LF 1-1003 ....」の改行を空白に変換(zsh)
set -- `echo ${2}`

for flagNo in ${@} ; do
codeIdCount=$(( ${codeIdCount} + 1 ))

codeNo=$(( ${flagNo#-} - 1000 ))

# 表示文字と色も変数に格納しておく
# コードが有効化されているならば
case "${flagNo}" in
( -* ) Ast=' ' ;;
(  * ) Ast="${ESC}[7m*${ESC}[27m" ;;
esac

# 表示文字と色も変数に格納しておく
# フォーカスが当たっているならば
case ${focusIdNo} in
( ${codeIdCount} )
brL="${ESC}[7m[${ESC}[27m"
brR="${ESC}[7m]${ESC}[27m"
;;
( * )
brL=' '
brR=' '
;;
esac

case "${codeNo}" in # 桁数に合わせて横幅調整
( ? ) # 1文字ならば
echoLine="${echoLine}${brL}${Ast}${brR} ${codeNo}:${ESC}[${codeNo}mCode ${codeNo}${ESC}[0m"
;;
( * ) # 2文字以上ならば
echoLine="${echoLine}${brL}${Ast}${brR}${codeNo}:${ESC}[${codeNo}mCode${codeNo}${ESC}[0m"
;;
esac

if test $(( ( ${codeIdCount} + 1 ) % 6 )) -eq 0 ; then
echo "${echoLine}"
echoLine=''
fi

done

# 16色版は6の倍数以外もサポートする
if test -n "${echoLine}"
then echo "${echoLine}"
fi
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	effectCodeFilterSh	20201106_205011
effectCodeFilterSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# ${1}:1;2;3;4...
# stdinからの文字列に装飾を行う
sed -e "{ s/^/\o033[${1}m/1 }"
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	effectCodeInversionSh	20201106_205011
effectCodeInversionSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 指定行の数値を、正負反転させる
NN=$(( ${1} + 1 ))
sed -n -e "#n
${NN}{
  s/^-/m/
  s/^[^m]/-&/
  s/^m//
}
{
  p
}
"
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	effectCodeRemoveSh	20201106_205011
effectCodeRemoveSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
#!/usr/bin/zsh
# 文字装飾コードを有効リストから削除する
# ${1}:'1'
# ${2}以降:' 2 15 1 3 6'
# output:'2 15 3 6'

MM=${1} ; shift 1
LL=''
for NN in ${@} ; do
	if  	test ${NN} -eq ${MM}
	then	true
	else	LL="${LL} ${NN}"
	fi
done
echo ${LL}
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	color16codeBuildSh	20201106_205011
color16codeBuildSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 16色指定の色とテキスト装飾のコードを組み立てる
# 「CSI Pn m」の形式で

# 前景色と背景色の色指定
# 色コードそのものではなく「0-15」で指定されている
# fgとbgでそれぞれ作成する番号は異なる
fgColorNo="${1:-7}"
bgColorNo="${2:-0}"
# 文字装飾分のコードが半角空白区切りでリストされる
# 順番を維持すること
effectCodeList="${3}"

# 0-7 なら 30-37
if test "${fgColorNo}" -le 7 ; then
fgColorID=$(( ${fgColorNo} + 30 ))
else
# 8-15 なら 90-97
fgColorID=$(( ${fgColorNo} + 82 ))
fi

# 0-7 なら 40-47
if test "${bgColorNo}" -le 7 ; then
bgColorID=$(( ${bgColorNo} + 40 ))
else
# 8-15 なら 100-107
bgColorID=$(( ${bgColorNo} + 92 ))
fi

# 「;」で繋げる
sgrList=`{
echo ${fgColorID}
echo ${bgColorID}
echo ${effectCodeList} | tr ' '  '\n'
} | tr -s '\n' | paste -s -d ';'`

# echoで出力するとdashで「\033」がエスケープ扱いされる
printf '%s\n' '\033['"${sgrList}"'m'

# [EOF]
) }	# ====[shMerge</ins>]====

# ======================================================================================
# シェル関数_外部へ分離不可
# ======================================================================================
keyActionFunc__UP(){
# フォーカス切替(-1)
focusID=$(( ( ${focusID} - 1 + 3 ) % 3 ))
}

keyActionFunc__DOWN(){
# フォーカス切替(+1)
focusID=$(( ( ${focusID} + 1 + 3 ) % 3 ))
}

keyActionFunc__MINUS(){
# main と sub を入れ替え
NN="${mainFgNo}"
MM="${mainBgNo}"
mainFgNo="${subFgNo}"
mainBgNo="${subBgNo}"
subFgNo="${NN}"
subBgNo="${MM}"
}

keyActionFunc_0__RIGHT(){
# 値やフォーカスを+1
mainFgNo=$(( ( ${mainFgNo} + 1 + 16 ) % 16 ))
}
keyActionFunc_1__RIGHT(){
# 値やフォーカスを+1
mainBgNo=$(( ( ${mainBgNo} + 1 + 16 ) % 16 ))
}
keyActionFunc_2__RIGHT(){
# 値やフォーカスを+1
effectCodeFocus=$(( ( ${effectCodeFocus} + 1 + ${effectCodeWordCount} ) % ${effectCodeWordCount} ))
}

keyActionFunc_0__LEFT(){
# 値やフォーカスを-1
mainFgNo=$(( ( ${mainFgNo} - 1 + 16 ) % 16 ))
}
keyActionFunc_1__LEFT(){
# 値やフォーカスを-1
mainBgNo=$(( ( ${mainBgNo} - 1 + 16 ) % 16 ))
}
keyActionFunc_2__LEFT(){
# 値やフォーカスを-1
effectCodeFocus=$(( ( ${effectCodeFocus} - 1 + ${effectCodeWordCount} ) % ${effectCodeWordCount} ))
}

keyActionFunc_2__SPACE(){
# 文字装飾コードを有効/無効
# idフラグ番号を取得_$((id + 1000 * -1))
MM=`echo "${effectCodeFlags}" | sed -n -e "$(( ${effectCodeFocus} + 1 )){ p ; q }"`
# id番号を取得_マイナスがあれば取ってから1000を引く
NN=$(( ${MM#-} - 1000 ))
# チェックフラグの有効無効を反転
effectCodeFlags="`echo "${effectCodeFlags}" | effectCodeInversionSh ${effectCodeFocus}`"

if test ${MM} -le -1 ; then
# 現在無効ならば、有効リストに空白区切りで追加する
effectCodeList="${effectCodeList} ${NN}"

else
# 現在有効ならば、有効リストから削除する
eval 'set -- '${effectCodeList}
effectCodeList=`effectCodeRemoveSh ${NN} ${@}`

fi
}

keyActionFunc__ASTER(){
echo 'サンプル文字を変更します。' 1>&2
read FREE_SAMPLE
if test -z "${FREE_SAMPLE}"
then FREE_SAMPLE='                                '
fi
}

keyActionFunc__PLUS(){
color16codeBuildSh "${mainFgNo}" "${mainBgNo}" "${effectCodeList}"
color16codeBuildSh "${subFgNo}" "${subBgNo}" "${effectCodeList}"
pressKeySh 1>/dev/null
}

keyActionFunc__ENTER(){
clear
}

keyActionFunc__QUIT(){
# 終了する
stty echo
exit 0
}

# デバッグ用
debug1Sub(){ (
for XX in mainFgNo mainBgNo subFgNo subBgNo focusID effectCodeFocus ; do
eval 'echo "${XX}:${'${XX}'}"'
done
) }

debug2Sub(){ (
echo "${effectCodeList}"
echo "${effectCodeFlags}" | column -x
) }

# ======================================================================================
# グローバル定数 シェル関数内での再定義と検索タグは必須
# ======================================================================================
ESC=`printf '\033'` # 【gloval const ESC】
ctrl_plus_C=`printf '\003'` # 【gloval const ctrl_plus_C】
ENTER_KEY=`printf '\015'` # 【gloval const ENTER_KEY】

# ======================================================================================
# スタートアップ処理
# ======================================================================================

stty_BAK=`stty -g`
trap 'printf "\033[?9l" 1>&2 ; stty "${stty_BAK}"' 1 2 3 15

# サンプル色[0-15]
mainFgNo=15
mainBgNo=0
subFgNo=0
subBgNo=15

# 操作対象
focusID=0

# 文字装飾コードチェックシート カーソル位置
effectCodeFocus='0'
# 文字装飾コードの総数( = チェックボックス数)
if ! XX=`effectCodeList16sh` ; then exit 127 ; fi
effectCodeWordCount=`echo "${XX}" | wc -l`


# 文字装飾コードからフラグ用定数を作成
# 1000を足して負にしたもの(「0」に正負をつけるため)
effectCodeFlags=`effectCodeList16sh | while read NN ; do
echo '-'$(( ${NN} + 1000 ))
done`

# テキスト装飾コード有効リスト
# 有効にした「順番に」改行区切りでコード番号が入る
# 「1,2,3」と「2,3,1」は異なる
effectCodeList=''

# エコーバックを切る
# キーを押しっぱなしのとき、ちらつく
# 端末エミュレータによっては、終了しても設定が残る
#stty -echo

# ======================================================================================
# メインループ
# ======================================================================================

while true ; do

# 色番号をCSIコードに変換
# 16色システムカラーは前景と背景で番号が異なるため
mainSampFgColor=`transFgNoSh ${mainFgNo}`
mainSampBgColor=`transBgNoSh ${mainBgNo}`
subSampFgColor=`transFgNoSh ${subFgNo}`
subSampBgColor=`transBgNoSh ${subBgNo}`

# テキスト装飾コードを構築
EFFECT_CODE=`effectCodeBuildSh "${effectCodeList}"`

{ # overWriteブロック
{ # 表示サンプルブロック
echo "${ESC}[${EFFECT_CODE}m${ESC}[39;49m${STD_SAMPLE}${ESC}[0m 39;49 ${EFFECT_CODE}"
echo "${ESC}[${EFFECT_CODE}m${ESC}[${mainSampFgColor};${mainSampBgColor}m${MAIN_SAMPLE}${ESC}[0m\
 ${mainSampFgColor};${mainSampBgColor} ${EFFECT_CODE}"
echo "${ESC}[${EFFECT_CODE}m${ESC}[${subSampFgColor};${subSampBgColor}m${SUB_SAMPLE}${ESC}[0m\
 ${subSampFgColor};${subSampBgColor} ${EFFECT_CODE}"
echo "${ESC}[${EFFECT_CODE}m${ESC}[${mainSampFgColor};${mainSampBgColor}m${FREE_SAMPLE}${ESC}[0m"
echo "${ESC}[${EFFECT_CODE}m${ESC}[${subSampFgColor};${subSampBgColor}m${FREE_SAMPLE}${ESC}[0m"
}

echo 'FG     30       31       32       33       34       35       36       37'
echo 'FOCUS_ID_0'
colorRadioSedSh ${mainFgNo}
echo 'FG     90       91       92       93       94       95       96       97'

echo 'BG     40       41       42       43       44       45       46       47'
echo 'FOCUS_ID_1'
colorRadioSedSh ${mainBgNo}
echo 'BG    100      101      102      103      104      105      106      107'

echo 'FOCUS_ID_2'
effectCodeCheckSh ${effectCodeFocus}  "${effectCodeFlags}"

helpMessageC16sh
} | overWriteFocusMarkSedSh ${focusID}

#debug1Sub  # 【debug】
#debug2Sub  # 【debug】



keyActionFunc_`pressKeyScan16sh ${focusID}`

done


exit 255





# EOF