Bash 以编程方式定义 whiptail radiolist 的咒语

Bash incantation for defining whiptail radiolist programmatically

我想发出类似于此的 bash 命令:

whiptail --title 'Select Database' --radiolist 'Select Database:' 10 80 2 \
  1 production off \
  2 localhost  on

Whiptail 对于单选列表值的指定方式相当讲究。如图所示,它们必须在各自的行中提供。 Here is a good article on this question.

数据库列表在名为 DBS 的变量中可用,ACTIVE_DB 是要在 whiptail 对话框中突出显示的单选列表项。

这是我目前为构建命令行所做的努力。这可能太复杂了。

DBS="production localhost"
ACTIVE_DB="localhost"
DB_COUNT="$( echo "$DBS" | wc -w )"

DB_LIST="$(
  I=1
  echo ""
  for DB in $DBS; do
    SELECTED="$( if [ "$DB" == "$ACTIVE_DB" ]; then echo " on"; else echo " off"; fi )"
    SLASH="$( if (( $I < $DB_COUNT )); then echo \; fi )"
    echo "  $I $DB $SELECTED $SLASH"
    echo ""
    I=$(( I + 1 ))
  done
)"

OPERATION="whiptail \
  --title \"Select Database\" \
  --radiolist \
  \"Select Database:\" \
  10 80 $DB_COUNT \"${DB_LIST[@]}\""

eval "${OPERATION}"

我已经很接近了。如您所见,扩展中包含的单引号搞砸了,并且在某些 EOL 中缺少反斜杠:

set -xv 
++ whiptail --title 'Select Database' --radiolist 'Select Database:' 10 80 2 '
  1 production  off
  2 localhost  on '

该解决方案需要提供一种方法来以某种方式知道用户做出了哪些选择,或者他们是否按下了 ESC。 ESC 通常将 return 代码设置为 255,所以这应该不难,但是当试图检索用户选择的单选列表项的值时,这个问题变得非常混乱。

以下遵循 BashFAQ #50 中规定的最佳实践:

# note that lower-case variable names are reserved for application use by POSIX
# see https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
active_db="localhost"
dbs=( production localhost ) # using an array, not a string, means ${#dbs[@]} counts

# initialize an array with our explicit arguments
whiptail_args=(
  --title "Select Database"
  --radiolist "Select Database:"
  10 80 "${#dbs[@]}"  # note the use of ${#arrayname[@]} to get count of entries
)

i=0
for db in "${dbs[@]}"; do
  whiptail_args+=( "$((++i))" "$db" )
  if [[ $db = "$active_db" ]]; then    # only RHS needs quoting in [[ ]]
    whiptail_args+=( "on" )
  else
    whiptail_args+=( "off" )
  fi
done

# collect both stdout and exit status
# to grok the file descriptor switch, see 
whiptail_out=$(whiptail "${whiptail_args[@]}" 3>&1 1>&2 2>&3); whiptail_retval=$?

# display what we collected
declare -p whiptail_out whiptail_retval

虽然我没有 whiptail 方便测试,但上述代码的确切调用 运行 与:

完全相同
whiptail --title "Select Database" \
         --radiolist "Select Database:" 10 80 2 \
          1 production off \
          2 localhost on 

...作为一个字符串,当 evaled 时,运行s 可以生成精确的命令:

printf '%q ' whiptail "${whiptail_args[@]}"; echo

使用数组。他们会让这件事变得容易十倍。让我们从小事做起,逐步提高。这是数组形式的 $DBS$DB_COUNT

DBS=(production localhost)
DB_COUNT=${#DBS[@]}

这里的优点是 $DBS 实际上有两个条目,所以我们可以用 ${#DBS[@]} 计算条目的数量,而不必 shell 到 [=17] 这样的外部命令=].

那么现在让我们来解决 $DB_LIST。您试图在每次循环迭代中添加几个选项。让我们将其转换为数组语法,使用 array+=(foo bar baz) 附加项目。

DB_LIST=()
I=1
for DB in "${DBS[@]}"; do
  if [ "$DB" == "$ACTIVE_DB" ]; then SELECTED=on; else SELECTED=off; fi
  DB_LIST+=("$I" "$DB" "$SELECTED")
  I=$(( I + 1 ))
done

反斜杠和换行符由 shell 而非 whiptail 解释。没有必要将它们放入数组中,所以我摆脱了整个 $SLASH 变量。换行无所谓,再见 echo "".

最后,让我们运行 whiptail。不再需要所有疯狂的引用和 eval-ing。我们可以直接运行它并在正确的位置扩展我们构建的数组。

whiptail --title "Select Database" --radiolist "Select Database:" 10 80 "$DB_COUNT" "${DB_LIST[@]}"

还有一些清理工作可以完成,但我认为一天就够了。