![見出し画像](https://assets.st-note.com/production/uploads/images/21776491/rectangle_large_type_2_245cc2e8f40868d26d6376a08382049a.png?width=1200)
【0057】readコマンドに穴があった
何で今まで気づかなかったのかと穴があったら入りたい気分なんですが
こんなコマンド無いわけですよ。
「aa」と入力するなら、
「da 4」つまり左方向へダッシュするコマンドなんですけど
間にスペースが有る場合は、
当然違うものとして認識されてほしいわけです。
が。
aaコマンドとして認識される。
なぜじゃ。
どうもスペースがきちんと認識されていないような感じです。
コマンドは、画面上の「COMMAND」欄に表示されている内容ではなく
ターミナル上で入力を受け付けた値を内部変数として保持しており
それを元に判断しています。
なので、内部的にスペースが取り除かれてしまっているのでしょう。
シェルスクリプトでなにかしようということは
空白との戦いだということが最近よくわかってきました。
区切り文字にスペースを使いたがるのはコンピュータも人間も同じなのに
人間とコンピュータでは区切ってほしいものが違うということで
まるで種の存亡を賭けた戦いのようです。
ここの所区切り文字に起因する想定外の動きが頻発していて
非常につらいです。
まあ今回は「入力コマンドのスペースが除去される」ことの対処だけに
フォーカスしていきます。
: '任意長入力受付' && {
###########################################
##getChrV
## 任意の文字数入力受付(エコーバックあり)。
## mainで定義しているグローバルinKeyをクリアしてから、入力値で上書き
###########################################
function getChrV(){
inKey=''
read inKey
}
}
: '1文字入力受付' && {
###########################################=
##getChrH
## なんか1文字の入力受付(エコーバックなし)。
## mainで定義しているグローバルinKeyをクリアしてから、入力値で上書き
###########################################
function getChrH(){
inKey=''
read -s -n 1 inKey
}
}
ということで、
現在文字入力には2種類あって、
1文字を画面表示なく受け付けるgetChrHと、
任意の文字数をエンターされるまで画面表示しながら受け付けるgetChrVが
あります。
双方ともグローバル変数の入力キー保持用の変数にブチ込むという
まあ冗長化待ったなしの作りになってるんですが……
今はそこはとりあえずいいです。
###########################################
##mainLoop
## 主処理の基幹
## 移動とコマンド呼び出しを反復し続ける。
###########################################
mainLoop(){
jmpPosWrgl 20 6
dspCmdLog 'リグルくん爆誕!!'
dispAll $CNST_YN_Y
while :
do
tput cup $CNST_POS_CMDWIN
getChrH<<<<<<<<<<<<<<<<ここ
#移動入力として1文字受け付ける 。移動を指示しない入力だった場合
#任意長のコマンド受付にリダイレクトされる 。
case "$cmdMode" in
#通常時はmvコマンドのショートカットが有効
$CNST_CMDMODE_NRML0 )
case "$inKey" in
[1Z] ) mv 1;;
[2X] ) mv 2;;
[3C] ) mv 3;;
[4A] ) mv 4;;
[5S] ) mv 5;;
[6D] ) mv 6;;
[7Q] ) mv 7;;
[8W] ) mv 8;;
[9E] ) mv 9;;
' ' ) dspCmdLog '入力してください。';;
* ) getCmdInMain;;
esac
;;
で、メインループの中で1文字取得を実行して
移動などの1文字だけで起動するコマンドを判断しています。
それに該当しない場合は
* ) getCmdInMain;;
ということで、コマンド取得関数を呼び出しています。
: 'コマンド受付' && {
###########################################
##getCmdInMain
## mainLoopから呼び出される任意長コマンド受付
## 各種コマンド割り振りと制御
## $1:メニュー内コマンド選択で呼び出された場合の引数
###########################################
function getCmdInMain(){
case "$cmdMode" in
#通常時は続けてコマンドを取得し 、各種コマンドを起動する
##通常時はコマンド数が多いためdispallコマンドは各ケースごとに記述する。
##コマンドによって引数を変えるため。
$CNST_CMDMODE_NRML0 )
inKey2="$inKey"
printf "$inKey2"
getChrV<<<<<<<<<<<<<ここ
inKey="$inKey2$inKey"
case "$inKey" in
'ma can') ma can;;
*'can' ) dspCmdLog 'OK、キャンセルしたよ:)'
dispAll $CNST_YN_Y;;
'te' ) te;;
'??' ) viewHelp;;
'ma '* ) ma "${inKey:3}";;
'mv '* ) mv "${inKey:3}";;
'op '* ) op "${inKey:3}";;
'cl '* ) cl "${inKey:3}";;
'da '* ) da "${inKey:3}";;
'pk '* ) pk;;
'qq' ) da 7;;
'ww' ) da 8;;
'ee' ) da 9;;
'aa' ) da 4;; <<「a a」という入力がこれに化ける
'dd' ) da 6;;
・・・続く
コマンド受付の箇所では、任意帳の文字列を取得していますが
その場合は1文字目の入力は表示しなければいけないので
1文字目の入力をinkey2という変数に退避した上で画面に表示し
その次の文字位置から2文字目のコマンドを受け付けます。
コマンドとしては1文字目と2文字目以降全体で判断する必要があるので
inkey2と結合して最終的な入力値とし、
その文字列をコマンド判断に使用している。
という感じなのですが……
このドコカでスペースが消えている。らしい。
いっそのこと画面表示されている文字を再取得して
コマンドとして受け付けるという方法も考えたんですが……
入力コマンドはいちいち画面表示情報のlnseed[]に登録していないことと
もう内部に持っている(筈)のものを捨てるのもなんだか癪なので
何とか原因を探ります。
判断される最終的なinKey変数に変更を加えているのは
・getChrH
・getChrV
・inKey2へinkeyを代入する操作
・inKeyへ「inkey2とinkeyを結合した文字列」を代入する操作
です。
いずれの箇所でも””でくくるのも忘れていないですし
(シェルスクリプトでは””でくくらないとよく空白が消える)
それ以外に空白を除去するような操作は行っていません。
わからぬ。
で、完全にお手上げで空白の処理周りでフラフラとwebを見ていたときに
こんな記事を見つけました。
えう?
単にreadをした場合は、前後のタブや空白が全て消えてしまっていますが
こ、これだーっっ!?
そして
またIFS(空白区切り)てめーかああああっっ!
記事では余り深く言及していないようですが
おそらく、readの直前に「IFS=」と書くことは
IFSを空白にする処理を直後のreadにだけかけることが出来る
書き方なのでしょう。
このあとでIFSを元に戻す処理の記述はありません。
シェルスクリプトには「サブシェルに閉じ込める」という思想があるようで
()の中に書いた処理呼び出しは、
その中に閉じ込められて外に影響しないというものがあるようです。
よく調べられていません(ちょっと調べてもよくわからなかった)が。
このreadの頭に「IFS=」を書くというのも似たようなものなのでしょうか。
配列の文字列化処理(いわゆるjoin)をシェルスクリプトで実装するときも
IFS変更して配列を文字列変数に読み込む処理を()の中でやっているのを見たことがあります。
(同じ人の記事じゃんか……!)
まあそれはそれとして、とりあえず同じ様に
・getChrH
・getChrV
のreadに「IFS=」を付与してみます。
: '任意長入力受付' && {
###########################################
##getChrV
## 任意の文字数入力受付(エコーバックあり)。
## mainで定義しているグローバルinKeyをクリアしてから、入力値で上書き
###########################################
function getChrV(){
inKey=''
IFS= read inKey
^^^^
}
}
: '1文字入力受付' && {
###########################################=
##getChrH
## なんか1文字の入力受付(エコーバックなし)。
## mainで定義しているグローバルinKeyをクリアしてから、入力値で上書き
###########################################
function getChrH(){
inKey=''
IFS= read -s -n 1 inKey
^^^^
}
}
……なんか代入してるみたいで気持ち悪いですね。
まあbashの代入は少なくとも後ろにスペースをつけてはいけないので
代入ではないということになるんでしょうけど、
コーディング中にミスって大変なことになりそう。
(IFSを「read」「-s」「-n」「1」 「inKey」に設定する、ってなりそう)
出来ればIFSって触りたくない感覚があるんですけど
空白との戦いでどうしても取らざるを得ない武器なんですよね……。
まあ実行してみます。
やっていきます。
とまりました。
エラーメッセージが「aa...」となっているのは、
その後で意図的にスペースを消しているためです。
* ) dspCmdLog "[${inKey// /}...]は無効です"
dispAll $CNST_YN_Y;;
esac
いずれのコマンドにも該当しなかった場合(*)、
dspCmdLog関数を実行してエラー表示をしているのですが、
このコマンドにスペースを含んだ文字列を渡すと、
それも引数分解を受けてしまい、不正な挙動をすることが分かりました。
……ええー、ほんとう?
前までちゃんと動いてた気がするんだけど……
なんか最近級に動かなくなったような印象があって
このスクリプトのあちこちで似たような障害が上がりまくっています。
ちう背景があり、一旦
"[${inKey// /}...]は無効です"
ということで、
変数置換展開を使用して
スペースをすべて除去してエラー文言に渡しています。
これはこれで問題なのですが、一旦は据え置きます。
入力コマンドのスペースが消える問題はこれにて解消です。
解決に大量の時間を費やしても記事にすると大したことないのなんだかね…
この記事が気に入ったらサポートをしてみませんか?