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

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


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

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

外部ファイルでスクリプトを書き、サブシェル入り関数で1ファイルにまとめる

スクリプトはファイル1枚にまとめてますが、制作中は細かくファイルを分けてみました。
というか、これについてはシェル関数をサブシェルで書いてはいたんですが、終盤でファイルに分けました。分けてミスに気付いたりもした。

「# ====[shMerge]====」なんてついてるのが、その目印。雑ですけど、まぁ。

  • 機能を、外部の独立したシェルスクリプトファイルで作り始めて、単体で動作テストを済ませる。
  • そのまま外部ファイルで構わないなら、無理にファイルをまとめない。
  • ファイルを統合するなら、シェル関数のサブシェルに入れる。サブシェルの必要が無くてもサブシェルにする。
  • サブシェルでないシェル関数は、必要なときのみ使う。

ってルールにしてみました。
作業用に、PATHを通したディレクトリを作り、その中にシェルスクリプトをファイルをまとめます。ファイル名は、そのままシェル関数名に使える名前、つまり拡張子とかは無しで作る。スクリプトの中でも、そのファイル名で指定しておく。

pressKeySh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# キー入力を1回のみ受け取る
stty_BAK=`stty -g`
stty -echo raw
dd count=1 2>/dev/null
stty "${stty_BAK}"
) }	# ====[shMerge</ins>]====

たとえばこういう、キー入力をする機能を外部の独立したシェルスクリプトで書いて作り、外部で単独で動作するようにします。でもってこれを、中身をそのまんま、サブシェルのシェル関数へコピーする。この作業はシェルスクリプトファイルで行って、定形作業にしてしまう。
サブシェルだから、独立したスクリプトファイルとは違って、トラブルもありますけど。

bashの機能を使う前提だと、トラブルは多くなるかもしんないです。変数を読み出し専用に設定してしまうと、サブシェルの中では変更できなくなったり、だとか。あと、配列も面倒かも。

「外部ファイルはスクリプトに統一する」って方針なので、たとえば、ドットコマンドで読み込むような、シェル関数の設定ファイルも、独立したシェルスクリプトにしてます。
このスクリプトだと、起動するスクリプトファイルのパスを書き込んだ「scriptListSh」など。登録するスクリプトのページを増やしても、メインのスクリプトで保持するシェル変数が増えずに済む。

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

# ========================================================================================
# ■ シェルスクリプト起動ランチャー_方向キー操作、1ページの登録数無制限 v2.0
# https://twitter.com/beta_reverse_2
# ========================================================================================
# ▼ 必須の設定
# シェル変数「scriptPageN」に、登録して実行したいスクリプトファイルへのパスを書き込む。
# 
# ▼ 主要操作
# ・ カーソル移動
#   ↑ カーソル↑,8,k
#   ↓ カーソル↓,2,j
#   → カーソル→,6,l
#   ← カーソル←,4,h
# ・ スクリプト実行
#   Enter,Ctrl+M
# ・ このスクリプトを終了
#   /,q,Ctrl+C
# ・ ページ切り替え
#   0,スペース,Tab
# 
# ▼ 拡張操作
# ・ テキストファイル表示 ページスクロール下
#   PageDown,shift+カーソル↓,3
# ・ テキストファイル表示 ページスクロール上
#   PageUp,  shift+カーソル↑,9
# ・ テキストファイル表示 ページスクロール リセット
#   カーソル移動するとリセットされる
# ・ スクリプトに引数を与えて実行
#   :
# ・ スクリプトのパスを表示
#   p
# ・ スクリプトをテキストエディタで開く
#   w
# 
# ▼ highlightコマンドでスクリプトソースを色分け
#   (1) sudo apt install highlight
#   (2) ユーザ設定_拡張で、該当部分のシェル変数をコメントアウト
# 
# 
# 

# PATH="./lib:${PATH}" # ====[shMerge<disable>]==== #_shMergeDisabled

# **************************************************************************************
# ユーザ設定_必須 scriptListSh()
# **************************************************************************************
# # ====[shMerge]====	scriptListSh	20201022_191249
scriptListSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 実行したいスクリプトや実行ファイルを、シェル変数に書き込む。
# 1コマンドを1行に。
# 絶対パス指定、相対パス指定、コマンド名で指定する。
# そのまま端末に打ち込んで実行するのと同様に実行される。
# ただし、「*.sh」などのパス展開は、実行時には使えない。(set -f 設定済み)

# ディレクトリは、指定しても使えない。
# ディレクトリ内のファイルを指定するには、コマンド置換を使う。

# シェル変数名は「scriptPage0」「scriptPage1」「scriptPage2」……とする。
# 「0,1,3」とすると、「0,1」は使えるが、「3」は使えなくなる。

# 普通に代入する標準の方法
# scriptPageN='date
# /home/user/dir/script.sh
# ./script.sh'

# ヒアドキュメントで代入
# scriptPageN=`cat - << ----PATH_LIST
# date
# cal
# ----PATH_LIST`

# 外部設定ファイルを作って読み込む
# scriptPageN=`cat ./pathListFile.txt`

# 指定ディレクトリの全てのファイル
# scriptPageN=`ls /home/user/dir/*`
# 指定ディレクトリの全てのスクリプトファイル
# scriptPageN=`ls /home/user/dir/*.sh`

# 終了をキーで選択してEnterで行う
#   「exit」を入れる。

# 一時的にページ設定「scriptPageN」を無効にする
#   変数名を変える。例えば「_scriptPageN」。

cd $(dirname `readlink -f "${0}"`)

# ${1}:ページ番号
scriptPage0=`cat - << ----PATH_LIST
/bin/ls
sudo apt update
sudo apt upgrade
stty size
exit
echo "123   567"
exec pwd
----PATH_LIST`
scriptPage1=`cat - << ----PATH_LIST
date
pwd
ls
cal
sl
----PATH_LIST`
scriptPage2=`ls /home/user/shell/*`



# 引数に指定された番号のシェル関数を標準出力する
# 存在しない番号を指定されたとき、exit 1

eval 'XX=`printf "%s\n" "${scriptPage'${1}'}"`'
if test -n "${XX}" ; then
	eval 'printf "%s\n" "${scriptPage'${1}'}"'
	exit 0
else
	exit 1
fi
# [EOF]
) }	# ====[shMerge</ins>]====


# **************************************************************************************
# ユーザ設定_拡張
# **************************************************************************************
# ファイルビューアの行数
viewLine=26
# コマンドビューアのカラムの横数 3から6程度を推奨
columnTD=4
# 空白「____」に指定するコマンド_エイリアス作成
alias "____=\date"

# テキストファイルの色付けをするコマンドをひとつ選択
#viewFilter=''           # 色をつけない
viewFilter='sed'        # sedでコメントのみ簡易着色
#viewFilter='highlight'  # highlightコマンドで着色

# テキストエディタ_テキストファイルを開くときに使う
userTextEditor='nano'

# # ====[shMerge]====	helpMessageSh	20201022_191249
helpMessageSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 簡易操作説明_不要ならprintfをコメントアウト
printf "\033[7m%s\033[0m %s  \033[7m%s\033[0m %s  \033[7m%s\033[0m %s  \033[7m%s\033[0m %s\n" '矢印' '選択' 'SPACE' '頁切替' 'Enter' '実行' 'q /' '終了'
true
) }	# ====[shMerge</ins>]====

# ======================================================================================
# シェル関数_外部へ分離可能
# ======================================================================================
# # ====[shMerge]====	pressKeySh	20201022_191249
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]====	pressKeyScanSh	20201022_191249
pressKeyScanSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
#!/usr/bin/zsh

# キー入力を受け取り、入力キーを判定して、結果を文字で出力する
# 文字出力は、シェル関数の名前に使える文字列のみで構成すること

eval "`printf \
'ESC="\033"
ctrl_plus_C="\003"
ENTER_KEY="\015"
TAB______KEY="\011"'`"

# 操作キー
UP____KEY="${ESC}[A"
DOWN__KEY="${ESC}[B"
RIGHT_KEY="${ESC}[C"
LEFT__KEY="${ESC}[D"
SPACE_KEY=' '
INS_KEY="${ESC}[2~"
PAGE_UP___KEY="${ESC}[5~"
PAGE_DOWN_KEY="${ESC}[6~"
SHIFT_UP___KEY="${ESC}[1;2A"
SHIFT_DOWN_KEY="${ESC}[1;2B"

stty_BAK=`stty -g`
stty -echo raw

while true ; do

# キー入力を1回のみ受け取る
case "`dd count=1 2>/dev/null`" in
( "${ENTER_KEY}" )
echo '_ENTER'
break 1
;;
( ':' )
echo '_COLON'
break 1
;;
( 0 | "${SPACE_KEY}" | "${TAB______KEY}" | "${INS_KEY}" )
echo '_TAB'
break 1
;;
( "${UP____KEY}" | 8 | k )
echo '_UP'
break 1
;;
( "${DOWN__KEY}" | 2 | j )
echo '_DOWN'
break 1
;;
( "${LEFT__KEY}" | 4 | h )
echo '_LEFT'
break 1
;;
( "${RIGHT_KEY}" | 6 | l )
echo '_RIGHT'
break 1
;;
( "${PAGE_DOWN_KEY}" | 3 | "${SHIFT_DOWN_KEY}" )
echo '_PAGE_DOWN'
break 1
;;
( "${PAGE_UP___KEY}" | 9 | "${SHIFT_UP___KEY}" )
echo '_PAGE_UP'
break 1
;;
( '/' | 'q' | "${ctrl_plus_C}" )
echo '_QUIT'
break 1
;;
( p )
echo 'p'
break 1
;;
( w )
echo 'w'
break 1
;;
( * )
true
;;
esac
done

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

# # ====[shMerge]====	scriptSourceViewSh	20201022_191249
scriptSourceViewSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
#!/usr/bin/zsh
#!/bin/bash

scriptPath="${1}"         # 表示するスクリプトのパス_正規化前の生データ
viewFilter="${2}"         # テキストファイルのハイライトコマンド



  if test "`file --mime --brief "${scriptPath}" | cut -d '/' -f 1`" = 'text' ; then
# 単純にテキストファイルならば、ソース表示処理に進む_実行ファイルでなくとも
	true
elif ! scriptPath=`which "${scriptPath}"` ; then
# 実行ファイルではない
	exit 1
elif ! test -z "${scriptPath%%/*}" ; then
# whichの終了コードは0だが、その結果が「/なんとか」ではないならば(zshのビルトイン)
	echo "${scriptPath}"
	exit 0
elif ! scriptPath=`readlink -f "${scriptPath}"` ; then
# 実行ファイルだが実体が見つからない
	exit 1
elif ! test "`file --mime --brief "${scriptPath}" | cut -d '/' -f 1`" = 'text' ; then
# 実行ファイルで実体はあるが、テキストファイルではない

	ls -alF --color=always "${scriptPath}" 2>&1
	file  "${scriptPath}" | tr ',' '\n' 
	exit 2
else
# 実行テキストファイルと見なし、ソース表示処理に進む
	true
fi

# スクリプトソース表示
# あとで1行単位で切るので、着色も1行単位で完結すること
case "${viewFilter}" in
( highlight )
	highlight --out-format=xterm256 --input="${scriptPath}" 
;;
( sed )

termCols=`tput cols`
tensionPole=`printf "%${termCols}s" ' '`

# 空白行を詰めて、コメントのみ簡易着色
sed -n -e "
# 空行を削除
/^$/{
d
}
# コメント部分に着色
/#/{
s/#[^#]*$/\o033[38;5;229m\o033[48;5;235m&/1
}

{ 
# 全行を空白含めて塗りつぶす
# 塗りつぶし棒を横幅いっぱいに半角空白で敷き詰め、右端でリセット
# キャリッジリターン「\o015」で戻し、
# 行の内容「&」を色付きで書き込み、
# 端末右端へカーソル移動(しないとoverWriteで消去される)

s/.*/\o033[38;5;231m\o033[48;5;237m${tensionPole}\o033[0m\o015\
\o033[38;5;231m\o033[48;5;237m&\o033[0m\o033[${termCols}G/
}

p
" "${scriptPath}"
;;
( * )
	tr -s '\n' 0< "${scriptPath}"
;;
esac

exit 0
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	scriptSourceLineFix3sh	20201022_191249
scriptSourceLineFix3sh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
#set -x
# ソース表示を指定行数に固定する
#Origin="${1:-1}"  # テキストファイル表示時の表示開始行
#viewLine="${2:-15}" # 出力行数

awk  -v Origin="${1:-1}" -v viewLine="${2:-15}" '

BEGIN{
# 
}

( NR >= Origin && viewLine > 0 ){
# 行番号をつける
# printf( "%03d %s\n" ,NR ,$0 )

# 標準表示
printf( "%s\n" ,$0 )

viewLine = viewLine - 1
}

END{

while ( viewLine > 0 ) {
print( "_" )
viewLine = viewLine - 1
	}

}' 2>&1

exit 0
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	scriptPageCoverSedSh	20201022_191249
scriptPageCoverSedSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 登録スクリプト変数の行数を、${columnTD}の整数倍に増やす
# 空白行を「____」に入れ替える
# 行の内容は、そのまま実行できるPATHに保つこと
# ${1} ${columnTD}         カラム列数

columnTD="${1:-4}"


# 空行をエイリアス文字列に置換
# 余剰行を追加

# テーブル列を「7」の文字数でカウント。Hに「777...」を蓄積する
sed --posix -n -e '
/^$/{ # 空行ならば「____」を入れる
#s/.*/__空行__/
s/.*/____/

# ↓つづく
}

{ # テーブル列をカウント
x
s/$/7/
/7\{'${columnTD}'\}/ {
# もしP「7777」ならば、消去
s/7*//
}
# p
x
}

# 元空行の「____」も含めて全行を出力
p

# ========最終行処理============
${ # 余剰行を追加_最終行は出力済

# P「____」H「7???」
# PH交換
# P「7???」 H「____」

# ラベルloopStart

# もしPが「」ならば、元データでちょうど割り切れた
	# 何も出力せずに終了
# もしPが「7777」ならば、補完ループの役目が終わった
	# 何も出力せずに終了

# 「7が半端な数だけある(列の出力が途中)」ならば
# P-H交換、Hに「7」連続を退避
# echo ____
# PH交換
# P末尾に「7」を追加
# ジャンプloopStart

#s/.*/__補完__/
s/.*/____/
x

: loopStart

/^$/                 { q }
/7\{'${columnTD}'\}/ { q }

x
p
x
s/$/7/
b loopStart
}' 
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	scriptLabelViewSedSh	20201022_191249
scriptLabelViewSedSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# 実行するスクリプトのラベルを表示する
# stdin:scriptPageN_列数の整数倍に行数を調整済み
# ${1}:選択中の行番号
# ${2}:横に何列で表示するのか

tdWidth='17'
tdSelect=${1:-1}
tdCount=${2:-4}

# ホールドスペースに、以下の形式で行内容を貯める
#
# 7777       (列数を文字数で)
# aaa.sh     (列データ1)
# bbb.sh
# ccc.sh
# ddd.sh
# 
# 入力1行ごとに、「7」を一つずつ増やす
# 行内容は改行追記してゆく
# 
# 先頭が/7\{指定数\}/になったとき、
#     /7\{指定数\}/を削除
#     改行を削除
#     行内容全出力
#     H,Pともに削除


sed --posix -n -e '#n
# ディレクトリ部分を削除してファイル名のみ残す
s:.*/::

# 行の末尾に半角空白を追加(手抜き)
s/$/                                                                /

# 先頭から指定幅の文字数を切り出す
s/^\(.\{'${tdWidth}'\}\).*/ \1/

# フォーカス行に色をつける
'${tdSelect}'s/.*/\o033[97;44m&\o033[0m/

# === ここまでで出力してからprコマンドに繋ぐことも可能 =======

# Hを呼び出し、Pの入力内容をHへ退避
x
# カウンタ文字「7」をHの先頭(行)に追加
s/^/7/

# 入力内容をHから改行追加
G

# もしカウンタ文字が列数だけ貯まっているならば
/^7\{'${tdCount}'\}/{
	# カウンタ文字を削除
	s/^7\{'${tdCount}'\}//
	# 改行を削除
	s/\n//g
	# テーブル1行分をまとめて出力
	p
	# Hの内容(この行の読み込み分)を削除
	x
	s/.*//
	# Pを全削除して次行へ
	x
	d
}

# カウンタ文字と行内容蓄積分をHへ退避
x
'
# 表形式表示を行う
#| pr --omit-header --columns=${2} --across --separator=' ' --indent=1 --output-tabs=' '1 
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	messageColorSh	20201022_191249
messageColorSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# メッセージ表示に色をつける
sed -e "s/.*/\o033[30;102m&\o033[0m/"
) }	# ====[shMerge</ins>]====

# # ====[shMerge]====	overWriteSh	20201022_191249
overWriteSh(){ (	# ====[shMerge<ins>]====
#!/bin/sh
# ちらつき防止 一行書き換え

#printf '\033[1;1H''\033[?25l'
#sed --posix -e 's/	/        /g' -e "s/$/\o033[0K/1"
#printf '\033[?0J''\033[?25h'

sed --posix -n -e '
# [エスケープシーケンス][1行目] 改行
1 {
s/^/\o033[1;1H\o033[?25l&/
}

# [最終行][エスケープシーケンス] 改行
$ {
s/$/\o033[?0J\o033[?25h/
}

# 行の水平タブを半角空白に入れ替え
s/	/        /g
# 行の行末に、行の右側消去を仕込む
s/$/\o033[0K/1

# エスケープシーケンスも含めて行を一気に出力
p
'
) }	# ====[shMerge</ins>]====

# ======================================================================================
# シェル関数_外部へ分離できない_キー実行アクション
# ======================================================================================
keyActionFunc__ENTER(){ # スクリプトを実行する
set -f
eval "${scriptPath}"
set +f
echo 'キーを押すと続行します。' | messageColorSh 1>&2
pressKeySh 1>/dev/null
}

keyActionFunc__COLON(){ # スクリプトに引数を与えて実行する
echo '実行するスクリプト:'"${scriptPath}" 1>&2
echo '引数を入力' 1>&2
read YY
set -f
eval "${scriptPath} ${YY}"
set +f
echo 'キーを押すと続行します。' | messageColorSh 1>&2
pressKeySh 1>/dev/null
}

keyActionFunc__TAB(){ # ページを切り替える
scriptPageNo=$(( ${scriptPageNo} + 1 ))
scriptPageNo=$(( ( ${scriptPageNo} + ${scriptPageMax} + 1 ) % ( ${scriptPageMax} + 1 ) ))
scriptPathX='0'
scriptPathY='0'
}

keyActionFunc__UP(){ # ↑
scriptPathY=$(( ( ${scriptPathY} - 1 + ${columnTR} ) % ${columnTR} ))
viewLineOrigin='1'
}

keyActionFunc__DOWN(){ # ↓
scriptPathY=$(( ( ${scriptPathY} + 1 ) % ${columnTR} ))
viewLineOrigin='1'
}

keyActionFunc__RIGHT(){ # →
scriptPathX=$(( ( ${scriptPathX} + 1 ) % ${columnTD} ))
viewLineOrigin='1'
}

keyActionFunc__LEFT(){ # ←
scriptPathX=$(( ( ${scriptPathX} - 1 + ${columnTD} ) % ${columnTD} ))
viewLineOrigin='1'
}

keyActionFunc__PAGE_DOWN(){ # ソース表示 下スクロール
viewLineOrigin=$(( ${viewLineOrigin} + ( ${viewLine} / 2 ) ))
}

keyActionFunc__PAGE_UP(){ # ソース表示 上スクロール
viewLineOrigin=$(( ${viewLineOrigin} - ( ${viewLine} / 2 ) ))
if test "${viewLineOrigin}" -le 0 ; then
	viewLineOrigin='1'
fi
}

keyActionFunc__QUIT(){ # 終了する
echo '終了します。' | messageColorSh 1>&2
exit 0
}

keyActionFunc_p(){ # whichコマンドでパスを表示する
echo "${PS1}"'which'
	which "${scriptPath}"
echo "${PS1}"'readlink -f'
	readlink -f $(which "${scriptPath}")
echo 'キーを押すと続行します。' | messageColorSh 1>&2
pressKeySh 1>/dev/null
}

keyActionFunc_w(){ # テキストエディタで開く
eval "${userTextEditor}" '"${scriptPath}"'
}

# ======================================================================================
# シェル関数_外部へ分離できない
# ======================================================================================
debug1Sub(){ (
printf "%s\n" 'pwd:'`pwd`
printf "%s\n" 'scriptPageMax:'"${scriptPageMax}"
NN=0
while test ${NN} -le ${scriptPageMax} ; do
eval 'echo "---- scriptPage'${NN}'"'
scriptListSh ${NN}
NN=$(( ${NN} + 1 ))
done
exit 0
) }

debug2Sub(){ ( 
printf '\033[0J'
echo 'pwd:'`pwd`
for XX in scriptPageNo scriptPathX scriptPathY columnTD columnTR \
 viewLineOrigin scriptPathNo ; do
eval 'echo ${XX}:"${'${XX}'}"'
done
echo '${PRESS_KEY_SCAN}:'`printf "%s" "${PRESS_KEY_SCAN}" | od -tc -An -v`
) }

# ======================================================================================
# スタートアップ処理
# ======================================================================================
cd "$( dirname "`readlink -f "${0}"`" )"

# ページ番号_初期設定
scriptPageNo='0'
# スクリプト選択座標_初期設定
scriptPathX='0'
scriptPathY='0'
# スクリプトファイルビューアの表示開始行番号_初期設定
viewLineOrigin='1'

#ESC=`printf "\033"`

# 「scriptPageMax」scriptPageNの最大値を取得する
NN='-1'
while true ; do
NN=$(( ${NN} + 1 ))

if scriptListSh "${NN}" 1>/dev/null ; then
	scriptPageMax="${NN}"
else
	break
fi
done

#debug1Sub ; exit # 【debug】


# ======================================================================================
# メインループ
# ======================================================================================
while true ; do
# ---- ページ情報 読み込み処理 ------------------------
# 処理するスクリプトのリストを読み込む
scriptPageCurrent="`scriptListSh ${scriptPageNo} | scriptPageCoverSedSh "${columnTD}"`"
# ラベル表示のカラム行数を取得
columnTR=$(( `printf "%s\n" "${scriptPageCurrent}" | wc -l` / ${columnTD} ))

# ---- ページ情報から1行を抜き出す --------------------
# 現在選択中のスクリプト番号を取得
scriptPathNo=$(( ${scriptPathX} + ${scriptPathY} * ${columnTD} + 1 ))
# スクリプト番号でパス文字列を抜き出す
scriptPath="`printf "%s\n" "${scriptPageCurrent}" | sed -n ${scriptPathNo}p `"

{ # 画面表示ブロック
helpMessageSh
# テキストかバイナリかを判定して、
# テキストなら冒頭のみ表示、バイナリならfileを実行
scriptSourceViewSh "${scriptPath}" ${viewFilter} |
	scriptSourceLineFix3sh ${viewLineOrigin} ${viewLine}


# 実行するコマンドのパスを表示
# 文字色と背景色を決めて 横幅一杯にマーカーラインを引き キャリッジリターン\015で戻し
# パス文字列を上書き 右端へ移動してから 色指定リセット 改行
NN=`tput cols`
printf '\033[30;106m%'${NN}'s\015 %s\033['${NN}G'\033[0m\n'  ' '  "${scriptPath}"

# スクリプト選択表を表示する
echo "${scriptPageCurrent}" | scriptLabelViewSedSh ${scriptPathNo} ${columnTD}
	} | overWriteSh 1>&2

#debug2Sub # 【debug】

# キー入力を受け取る
PRESS_KEY_SCAN=`pressKeyScanSh`

# ---- 入力キーアクション実行 ----------------------------------------------------
keyActionFunc_${PRESS_KEY_SCAN}


done

exit 255
# EOF

スクリプト一括合成スクリプトの例

要するに、テキストファイルを、しおりで指定した場所に差し込むスクリプトです。
必要なパスを絶対パスで書き込んで使います。
やっつけ作業だし、再帰をしちゃってたりで、これで大丈夫か自信無いですけど。
ファイルの中身は削除しないようにしてます。不要部分はコメントアウトするだけです。分離も簡単。

#!/bin/sh
: << '#__________comment'

スクリプト合成


合成位置タグ
タブ区切りでこの後に外部スクリプトのファイル名を書き込む
コードの行末に置かずにタグだけで1行にすること。
# ====[shMerge]====	scriptNameSh

コード無効化タグ
行末に置くと、置いた行がコメントアウトされる
# ====[shMerge<disable>]====

コード有効化タグ
行末に置くと、置いた行のコメントアウトが削除される
# ====[shMerge<enable>]====


#__________comment


# 主スクリプトのフルパス
mainScript='/home/'
# ライブラリの入ったディレクトリ
libDir='/home/'

# 新規作成シェルスクリプト名
newScriptName='launchB20_merge'
# 出力先ディレクトリ
newScriptDir='/home/'


true ${mainScript:?}
true ${libDir:?}
true ${newScriptName:?}
true ${newScriptDir:?}

cd $( dirname `readlink -f "${0}"` )


mainSub(){ (
# 再帰呼び出し処理するメイン処理

# マージ機能を有効にするならtrue
margeTagScanFlag="${1:-false}"

# 作成日時
dateTime=`date +"%Y%m%d_%H%M%S"`

LINE_COUNT='0'

while IFS='' read -r LINE ; do # -----------------------------------------------------↓

LINE_COUNT=$(( ${LINE_COUNT} + 1 ))


#sleep 0.01

case "${LINE}" in
# ----------------------------------------------------------------------
( '# ====[shMerge]===='* ) # 合成位置タグ
# ----------------------------------------------------------------------
case "${margeTagScanFlag}" in
( true )

# 外部スクリプト合成

# シェル関数の名前を取得
functionName=`echo "${LINE}" | cut -d '	' -f 2`

echo "# ${LINE}	${dateTime}"
echo "${functionName}"'(){ (	# ====[shMerge<ins>]===='
#echo "${functionName}"'(){ 	# ====[shMerge<ins>]===='

# 外部スクリプト読み込み、存在判定
# いったんコマンド置換で変数に入れることで、
# 末尾の連続改行を縮める。

if funcData=`cat "${libDir}/${functionName}"` ; then

# サブシェルで再帰呼び出し
# 1行処理繰り返しループが終わるまで出力を続ける
# 外部スクリプト合成指示タグは無視する
	printf '%s\n' "${funcData}" | mainSub 'false'
else
	printf '%s' "${LINE_COUNT}:" 1>&2
	exit 2
fi

# サブシェルからの出力が終わったら、シェル関数の括弧を閉じる
echo ') }	# ====[shMerge</ins>]===='
#echo ' }	# ====[shMerge</ins>]===='

;;
( * )
printf '%s\n' "# ${LINE}"

;;
esac

;;
# ----------------------------------------------------------------------
( *'# ====[shMerge<disable>]====' ) # コメントアウトタグ
# ----------------------------------------------------------------------
# 先頭にコメントアウトを付加して出力
printf '%s\n' '# '"${LINE}"' #_shMergeDisabled'


;;
# ----------------------------------------------------------------------
( *'# ====[shMerge<enable>]====' ) # コード有効化タグ
# ----------------------------------------------------------------------
# 先頭の「#」と、続く空白を削除して出力
printf '%s\n' "${LINE}" | sed -e 's/^#//1' -e 's/^ *//1' -e 's/$/ #_shMergeEnabled/1'


;;
# ----------------------------------------------------------------------
( * ) # 通常のコードならば
# ----------------------------------------------------------------------
# タグではなく、コード有効化も無効化も指示されていないならば、そのまま出力
printf '%s\n' "${LINE}"

;;
esac

done # ------------------------------------------------------------------------------↑
) }


dateTime=`date +"%Y%m%d_%H%M%S"`

  if ! cat "${mainScript}" ; then exit 2
elif ! test -f "${newScriptDir}/${newScriptName}${dateTime}.sh" ; then exit 2
else
	true
fi | mainSub true  2>> ./shMergeErr.log | tee "${newScriptDir}/${newScriptName}${dateTime}.sh"

chmod 554 "${newScriptDir}/${newScriptName}${dateTime}.sh"