スクリプト ファイル内の TMOUT が select コマンドに対して誤動作をします。どうすれば修正できますか?

スクリプト ファイル内の TMOUT が select コマンドに対して誤動作をします。どうすれば修正できますか?

アップデート- これは bash 4.3.46 では問題なく動作するため、bash 4.3.42 特有の現象のようです。今後同じ問題に遭遇する人のためにこの投稿を残しておきます。

このコマンドを bash コマンドラインで実行すると、正常に動作します。

% (TMOUT=3; s="no selection"; select s in a b c ; do break ; done; echo $s)
1) a
2) b
3) c
#?
no selection
% _

結果: 表示選択なしコマンドラインに戻ります。

しかし、スクリプトを配置して実行すると、繰り返し選択を求められます。

% cat a.sh
#!/bin/bash
(TMOUT=3; s="no selection"; select s in a b c ; do break ; done; echo $s)

% ./a.sh

結果:

1) a
2) b
3) c
#? 1) a
2) b
3) c
#? 1) a
2) b
3) c
#? ^C
% _

なぜそうなるのか?私の主な疑問は、スクリプトで動作させるにはどうすればいいですか?

更新しました

% bash --version
GNU bash, version 4.3.42(1)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.

% uname
Linux lx1 2.6.32-642.6.2.el6.x86_64 #1 SMP Mon Oct 24 10:22:33 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux

答え1

マニュアルより:

TMOUT 0 より大きい値に設定すると、TMOUT は組み込みの read のデフォルトのタイムアウトとして扱われます。端末からの入力の場合、TMOUT 秒後に入力が到着しないと、select コマンドは終了します。対話型シェルでは、この値はプライマリ プロンプトの発行後に入力を待機する秒数として解釈されます。入力が到着しない場合、bash はその秒数待機した後に終了します。

したがって、最初のケースは対話型シェルによるものです。

$ ps -aef|grep bash; echo "before"; (TMOUT=3; s="no selection"; ps -aef|grep bash; select s in a b c ; do break ; done; echo $s;ps -aef|grep bash);echo "after";ps -aef|grep bash
asktyagi    4926 23767  0 09:40 pts/0    00:00:00 grep --color=auto bash
asktyagi   23767 23741  0 09:03 pts/0    00:00:00 -bash
**before**
asktyagi    4927 23767  0 09:40 pts/0    00:00:00 -bash
asktyagi    4929  4927  0 09:40 pts/0    00:00:00 grep --color=auto bash
asktyagi   23767 23741  0 09:03 pts/0    00:00:00 -bash
1) a
2) b
3) c
#?
no selection
asktyagi    4927 23767  0 09:40 pts/0    00:00:00 -bash
asktyagi    4931  4927  0 09:40 pts/0    00:00:00 grep --color=auto bash
asktyagi   23767 23741  0 09:03 pts/0    00:00:00 -bash
**after**
asktyagi    4933 23767  0 09:40 pts/0    00:00:00 grep --color=auto bash
asktyagi   23767 23741  0 09:03 pts/0    00:00:00 -bash

スクリプト付き

$ cat a.sh
#!/bin/bash
(TMOUT=3; s="no selection"; select s in a b c ; do break ; done; echo $s)
ps -aef|grep bash

$ sh a.sh
1) a
2) b
3) c
#?
no selection
asktyagi    5201  5188  0 09:41 pts/0    00:00:00 grep bash
asktyagi   23767 23741  0 09:03 pts/0    00:00:00 -bash

答え2

コマンドを使用しますtimeout。この例は改善の余地がありますが、要点は伝わるでしょう。まず、bash 組み込みtrapコマンドを使用して終了時に何かを実行し、次にループを実行します。次のように実行されます。

#] timeout 3s sh -c 'trap "echo no selection" EXIT; select s in a b c ; do break ; done; '
1) a
2) b
3) c
#? <waits 3 seconds>no selection

答え3

最終的に、スクリプトで使用できる独自のセレクションを作成しました。

# $1 timeout
# rest - selection
function select_ {
  t=$1
  shift
  arr=($*)
  i=1
  for a in $*; do
    echo "$i) $a" >$(tty)
    ((i++))
  done
  echo "TIMEOUT: In ${t} seconds ${arr[0]} will be automatically selected." >$(tty)
  printf "#? " >$(tty)
  read -t $t x
  [ "$x" == "" ] && {
     x=1
     echo "$x" >$(tty)
  }
  ((x=x+0))
  [ "$x" -ge 1 -a "$x" -le ${#arr[@]} ] && {
    ((x--))
    echo ${arr[$x]}
  }
}

それの使い方:

select_ 10 a b c

例:

$ ./x.sh
1) a
2) b
3) c
TIMEOUT: In 10 seconds a will be automatically selected.
#? 2
b

$ ./x.sh
1) a
2) b
3) c
TIMEOUT: In 10 seconds a will be automatically selected.
#? 1
a

関連情報