Menu multi-pilih dalam skrip bash


28

Saya seorang pemula bash, tetapi saya ingin membuat skrip di mana saya ingin memungkinkan pengguna untuk memilih beberapa opsi dari daftar opsi.

Pada dasarnya yang saya inginkan adalah sesuatu yang mirip dengan contoh di bawah ini:

       #!/bin/bash
       OPTIONS="Hello Quit"
       select opt in $OPTIONS; do
           if [ "$opt" = "Quit" ]; then
            echo done
            exit
           elif [ "$opt" = "Hello" ]; then
            echo Hello World
           else
            clear
            echo bad option
           fi
       done

(Bersumber dari http://www.faqs.org/docs/Linux-HOWTO/Bash-Prog-Intro-HOWTO.html#ss9.1 )

Namun skrip saya akan memiliki lebih banyak opsi, dan saya ingin memperbolehkan beberapa kelipatan untuk dipilih. Jadi sesuatu seperti ini:

1) Opsi 1
2) Opsi 2
3) Opsi 3
4) Opsi 4
5) Selesai

Memiliki umpan balik tentang yang telah mereka pilih juga akan sangat bagus, misalnya tanda plus di samping yang sudah mereka pilih. Misalnya jika Anda memilih "1" Saya ingin halaman dihapus dan dicetak ulang:

1) Option 1 +
2) Option 2
3) Option 3
4) Option 4
5) Done

Kemudian jika Anda memilih "3":

1) Option 1 +
2) Option 2
3) Option 3 +
4) Option 4
5) Done

Juga, jika mereka kembali memilih (1) Saya ingin "membatalkan pilihan" opsi:

1) Option 1
2) Option 2
3) Option 3 +
4) Option 4
5) Done

Dan akhirnya ketika Selesai ditekan saya ingin daftar yang dipilih untuk ditampilkan sebelum program keluar, misalnya jika keadaan saat ini adalah:

1) Option 1
2) Option 2 +
3) Option 3 + 
4) Option 4 +
5) Done

Menekan 5 seharusnya mencetak:

Option 2, Option 3, Option 4

... dan skrip berakhir.

Jadi pertanyaan saya - apakah ini mungkin dalam bash, dan jika demikian apakah ada yang bisa memberikan contoh kode?

Saran apa pun akan sangat dihargai.

Jawaban:


35

Saya pikir Anda harus melihat dialog atau whiptail .

kotak dialog

Edit:

Berikut ini contoh skrip yang menggunakan opsi dari pertanyaan Anda:

#!/bin/bash
cmd=(dialog --separate-output --checklist "Select options:" 22 76 16)
options=(1 "Option 1" off    # any option can be set to default to "on"
         2 "Option 2" off
         3 "Option 3" off
         4 "Option 4" off)
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
clear
for choice in $choices
do
    case $choice in
        1)
            echo "First Option"
            ;;
        2)
            echo "Second Option"
            ;;
        3)
            echo "Third Option"
            ;;
        4)
            echo "Fourth Option"
            ;;
    esac
done

Terima kasih untuk itu. Terlihat lebih kompleks dari yang saya harapkan, tapi saya akan memeriksanya :-)
user38939

@ am2605: Lihat hasil edit saya. Saya menambahkan contoh skrip.
Dijeda sampai pemberitahuan lebih lanjut.

3
Itu hanya terlihat rumit sampai Anda pernah menggunakannya sekali atau dua kali, maka Anda tidak akan pernah menggunakan yang lain ...
Chris S

27

Jika Anda anggap whiptailkompleks, ini dia kode bash-only yang melakukan persis apa yang Anda inginkan. Ini pendek (~ 20 baris), tetapi agak samar untuk pemula. Selain menunjukkan "+" untuk opsi yang dicentang, itu juga memberikan umpan balik untuk setiap tindakan pengguna ("opsi tidak valid", "opsi X dicentang" / tidak dicentang, dll).

Yang mengatakan, ini dia!

Semoga Anda menikmati ... itu adalah tantangan yang cukup menyenangkan untuk membuatnya :)

#!/bin/bash

# customize with your own.
options=("AAA" "BBB" "CCC" "DDD")

menu() {
    echo "Avaliable options:"
    for i in ${!options[@]}; do 
        printf "%3d%s) %s\n" $((i+1)) "${choices[i]:- }" "${options[i]}"
    done
    if [[ "$msg" ]]; then echo "$msg"; fi
}

prompt="Check an option (again to uncheck, ENTER when done): "
while menu && read -rp "$prompt" num && [[ "$num" ]]; do
    [[ "$num" != *[![:digit:]]* ]] &&
    (( num > 0 && num <= ${#options[@]} )) ||
    { msg="Invalid option: $num"; continue; }
    ((num--)); msg="${options[num]} was ${choices[num]:+un}checked"
    [[ "${choices[num]}" ]] && choices[num]="" || choices[num]="+"
done

printf "You selected"; msg=" nothing"
for i in ${!options[@]}; do 
    [[ "${choices[i]}" ]] && { printf " %s" "${options[i]}"; msg=""; }
done
echo "$msg"

Kerja bagus! Kerja bagus!
Daniel

4
Yang ini agak samar tapi saya suka penggunaan ekspansi kurung yang kompleks dan array dinamis. Butuh sedikit waktu untuk bisa membaca semuanya saat itu terjadi tetapi saya menyukainya. Saya juga suka fakta bahwa Anda menggunakan fungsi printf () bawaan. Saya tidak menemukan banyak yang tahu tentang itu ada di bash. Sangat berguna jika seseorang digunakan untuk pengkodean dalam C.
Yokai

1
Jika ada yang ingin dapat memilih beberapa opsi (dipisahkan dengan ruang) sekaligus:while menu && read -rp "$prompt" nums && [[ "$nums" ]]; do while read num; do ... done < <(echo $nums |sed "s/ /\n/g") done
TAAPSogeking

1
Ini sangat berguna dalam mengembangkan skrip yang digunakan oleh banyak orang lain yang tidak memiliki akses ke whiptail atau paket lain karena mereka gunakan git bashdi windows!
Dr Ivol

5

Inilah cara untuk melakukan apa yang Anda inginkan hanya menggunakan fitur Bash tanpa ketergantungan eksternal. Ini menandai pilihan saat ini dan memungkinkan Anda untuk mengubahnya.

#!/bin/bash
# Purpose: Demonstrate usage of select and case with toggleable flags to indicate choices
# 2013-05-10 - Dennis Williamson

choice () {
    local choice=$1
    if [[ ${opts[choice]} ]] # toggle
    then
        opts[choice]=
    else
        opts[choice]=+
    fi
}

PS3='Please enter your choice: '
while :
do
    clear
    options=("Option 1 ${opts[1]}" "Option 2 ${opts[2]}" "Option 3 ${opts[3]}" "Done")
    select opt in "${options[@]}"
    do
        case $opt in
            "Option 1 ${opts[1]}")
                choice 1
                break
                ;;
            "Option 2 ${opts[2]}")
                choice 2
                break
                ;;
            "Option 3 ${opts[3]}")
                choice 3
                break
                ;;
            "Option 4 ${opts[4]}")
                choice 4
                break
                ;;
            "Done")
                break 2
                ;;
            *) printf '%s\n' 'invalid option';;
        esac
    done
done

printf '%s\n' 'Options chosen:'
for opt in "${!opts[@]}"
do
    if [[ ${opts[opt]} ]]
    then
        printf '%s\n' "Option $opt"
    fi
done

Untuk ksh, ubah dua baris pertama dari fungsi:

function choice {
    typeset choice=$1

dan shebang untuk #!/bin/ksh.


Contoh yang bagus! Bagaimana mengelola untuk menjalankannya di KSH?
FuSsA

1
@ FuSsA: Saya mengedit jawaban saya untuk menunjukkan perubahan yang diperlukan untuk membuatnya bekerja di ksh.
Dijeda sampai pemberitahuan lebih lanjut.

1
Penanganan array dalam bash sangat hardcore. Anda bukan hanya yang pertama, Anda adalah satu-satunya di atas 40k pada keseluruhan trinitas.
peterh mengatakan mengembalikan Monica

1
@ FuSsA: options=(*)(atau pola penggumpalan lainnya) akan memberi Anda daftar file dalam array. Tantangannya kemudian akan mendapatkan array tanda seleksi ( ${opts[@]}) di-zip bersama-sama dengannya. Ini dapat dilakukan dengan satu forloop, tetapi harus dijalankan untuk setiap melewati whileloop luar . Anda mungkin ingin mempertimbangkan untuk menggunakan dialogatau whiptailseperti yang saya sebutkan dalam jawaban saya yang lain - meskipun ini adalah dependensi eksternal.
Dijeda sampai pemberitahuan lebih lanjut.

1
@ FuSsA: Kemudian Anda bisa menyimpan string di array lain (atau menggunakan ${opts[@]}dan menyimpan string, diteruskan sebagai argumen tambahan ke fungsi, bukan +).
Dijeda sampai pemberitahuan lebih lanjut.

2

Saya menulis sebuah perpustakaan bernama kuesioner , yang merupakan mini-DSL untuk membuat kuesioner baris perintah. Ini meminta pengguna untuk menjawab serangkaian pertanyaan dan mencetak jawaban ke stdout.

Itu membuat tugas Anda sangat mudah. Instal dengan pip install questionnairedan buat skrip, misalnya questions.py, seperti ini:

from questionnaire import Questionnaire
q = Questionnaire(out_type='plain')

q.add_question('options', prompt='Choose some options', prompter='multiple',
               options=['Option 1', 'Option 2', 'Option 3', 'Option 4'], all=None)

q.run()

Kemudian jalankan python questions.py. Ketika Anda selesai menjawab pertanyaan-pertanyaan yang mereka cetak untuk stdout. Ini bekerja dengan Python 2 dan 3, salah satunya hampir pasti diinstal pada sistem Anda.

Ini dapat menangani kuesioner yang jauh lebih rumit juga, kalau-kalau ada yang ingin melakukan ini. Berikut ini beberapa fitur:

  • Mencetak jawaban sebagai JSON (atau sebagai teks biasa) ke stdout
  • Mengizinkan pengguna kembali dan menjawab pertanyaan
  • Mendukung pertanyaan bersyarat (pertanyaan dapat bergantung pada jawaban sebelumnya)
  • Mendukung jenis pertanyaan berikut: input mentah, pilih satu, pilih banyak
  • Tidak ada kopling wajib antara presentasi pertanyaan dan nilai jawaban

1

Saya menggunakan contoh dari MestreLion dan menyusun kode di bawah ini. Yang perlu Anda lakukan adalah memperbarui opsi dan tindakan di dua bagian pertama.

#!/bin/bash
#title:         menu.sh
#description:   Menu which allows multiple items to be selected
#author:        Nathan Davieau
#               Based on script from MestreLion
#created:       May 19 2016
#updated:       N/A
#version:       1.0
#usage:         ./menu.sh
#==============================================================================

#Menu options
options[0]="AAA"
options[1]="BBB"
options[2]="CCC"
options[3]="DDD"
options[4]="EEE"

#Actions to take based on selection
function ACTIONS {
    if [[ ${choices[0]} ]]; then
        #Option 1 selected
        echo "Option 1 selected"
    fi
    if [[ ${choices[1]} ]]; then
        #Option 2 selected
        echo "Option 2 selected"
    fi
    if [[ ${choices[2]} ]]; then
        #Option 3 selected
        echo "Option 3 selected"
    fi
    if [[ ${choices[3]} ]]; then
        #Option 4 selected
        echo "Option 4 selected"
    fi
    if [[ ${choices[4]} ]]; then
        #Option 5 selected
        echo "Option 5 selected"
    fi
}

#Variables
ERROR=" "

#Clear screen for menu
clear

#Menu function
function MENU {
    echo "Menu Options"
    for NUM in ${!options[@]}; do
        echo "[""${choices[NUM]:- }""]" $(( NUM+1 ))") ${options[NUM]}"
    done
    echo "$ERROR"
}

#Menu loop
while MENU && read -e -p "Select the desired options using their number (again to uncheck, ENTER when done): " -n1 SELECTION && [[ -n "$SELECTION" ]]; do
    clear
    if [[ "$SELECTION" == *[[:digit:]]* && $SELECTION -ge 1 && $SELECTION -le ${#options[@]} ]]; then
        (( SELECTION-- ))
        if [[ "${choices[SELECTION]}" == "+" ]]; then
            choices[SELECTION]=""
        else
            choices[SELECTION]="+"
        fi
            ERROR=" "
    else
        ERROR="Invalid option: $SELECTION"
    fi
done

ACTIONS

Jawaban yang sangat bagus. Juga tambahkan catatan untuk menambah jumlahnya, mis. Opsi 15; di mana n1 SELECTIONadalah bagian penting untuk meningkatkan jumlah digit ..
dbf

Lupa menambahkan; di mana -n2 SELECTIONakan menerima dua digit (mis. 15), -n3menerima tiga digit ( mis. 153), dll.
dbf

1

Berikut adalah fungsi bash yang memungkinkan pengguna untuk memilih beberapa opsi dengan tombol panah dan Spasi, dan mengonfirmasi dengan Enter. Ini memiliki nuansa seperti menu yang bagus. Saya menulisnya dengan bantuan https://unix.stackexchange.com/a/415155 . Ini bisa disebut seperti ini:

multiselect result "Option 1;Option 2;Option 3" "true;;true"

Hasilnya disimpan sebagai array dalam variabel dengan nama yang disediakan sebagai argumen pertama. Argumen terakhir adalah opsional dan digunakan untuk membuat beberapa opsi dipilih secara default. Ini terlihat seperti ini.

function prompt_for_multiselect {

    # little helpers for terminal print control and key input
    ESC=$( printf "\033")
    cursor_blink_on()   { printf "$ESC[?25h"; }
    cursor_blink_off()  { printf "$ESC[?25l"; }
    cursor_to()         { printf "$ESC[$1;${2:-1}H"; }
    print_inactive()    { printf "$2   $1 "; }
    print_active()      { printf "$2  $ESC[7m $1 $ESC[27m"; }
    get_cursor_row()    { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
    key_input()         {
      local key
      IFS= read -rsn1 key 2>/dev/null >&2
      if [[ $key = ""      ]]; then echo enter; fi;
      if [[ $key = $'\x20' ]]; then echo space; fi;
      if [[ $key = $'\x1b' ]]; then
        read -rsn2 key
        if [[ $key = [A ]]; then echo up;    fi;
        if [[ $key = [B ]]; then echo down;  fi;
      fi 
    }
    toggle_option()    {
      local arr_name=$1
      eval "local arr=(\"\${${arr_name}[@]}\")"
      local option=$2
      if [[ ${arr[option]} == true ]]; then
        arr[option]=
      else
        arr[option]=true
      fi
      eval $arr_name='("${arr[@]}")'
    }

    local retval=$1
    local options
    local defaults

    IFS=';' read -r -a options <<< "$2"
    if [[ -z $3 ]]; then
      defaults=()
    else
      IFS=';' read -r -a defaults <<< "$3"
    fi
    local selected=()

    for ((i=0; i<${#options[@]}; i++)); do
      selected+=("${defaults[i]}")
      printf "\n"
    done

    # determine current screen position for overwriting the options
    local lastrow=`get_cursor_row`
    local startrow=$(($lastrow - ${#options[@]}))

    # ensure cursor and input echoing back on upon a ctrl+c during read -s
    trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
    cursor_blink_off

    local active=0
    while true; do
        # print options by overwriting the last lines
        local idx=0
        for option in "${options[@]}"; do
            local prefix="[ ]"
            if [[ ${selected[idx]} == true ]]; then
              prefix="[x]"
            fi

            cursor_to $(($startrow + $idx))
            if [ $idx -eq $active ]; then
                print_active "$option" "$prefix"
            else
                print_inactive "$option" "$prefix"
            fi
            ((idx++))
        done

        # user key control
        case `key_input` in
            space)  toggle_option selected $active;;
            enter)  break;;
            up)     ((active--));
                    if [ $active -lt 0 ]; then active=$((${#options[@]} - 1)); fi;;
            down)   ((active++));
                    if [ $active -ge ${#options[@]} ]; then active=0; fi;;
        esac
    done

    # cursor position back to normal
    cursor_to $lastrow
    printf "\n"
    cursor_blink_on

    eval $retval='("${selected[@]}")'
}

bagaimana anda menyebutnya? seperti apa file itu?
Eli


-1
export supermode=none

source easybashgui

list "Option 1" "Option 2" "Option 3" "Option 4"

2
Mungkin Anda bisa menambahkan sedikit deskripsi tentang apa yang dilakukan ini? Untuk pengunjung masa depan, tidak terlalu banyak untuk OP.
slm

Juga, tautan ke asal easybashgui.
Dijeda sampai pemberitahuan lebih lanjut.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.