1

I want to issue a bash command similar to this:

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

Whiptail is rather particular about how the radio list values are specified. They must be provided each on their own line, as shown. Here is a good article on this question.

The list of databases is available in a variables called DBS, and ACTIVE_DB is the radiolist item to highlight in the whiptail dialog.

Here is my current effort for building the command line. It is probably way too convoluted.

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}"

I get fairly close. As you can see, the expansion contains single quotes that mess things up, and backslashes are missing at some EOLs:

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

The solution needs to provide a way to somehow know which selection the user made, or if they pressed ESC. ESC usually sets the return code to 255, so that should not be difficult, however this problem gets really messy when trying to retrieve the value of the user-selected radiolist item.

4

2 回答 2

4

以下遵循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 https://stackoverflow.com/a/1970254/14122
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 运行时,可以使用以下命令生成精确命令:

printf '%q ' whiptail "${whiptail_args[@]}"; echo
于 2019-05-01T00:17:12.123 回答
1

使用数组。他们会让这变得容易十倍。让我们从小处着手,一路向上。这是数组形式$DBS$DB_COUNT

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

这里的优点是$DBS实际上有两个条目,因此我们可以计算条目的数量,${#DBS[@]}而无需使用外部命令,例如wc.

所以现在让我们来解决$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[@]}"

还有更多的清理工作可以完成,但我认为这对于一天来说已经足够了。

于 2019-05-01T00:19:29.603 回答