Baca kunci khusus dalam bash

8

Saya bermain dengan skrip yang, antara lain, daftar pilihan-daftar. Seperti dalam:

1) Item 1               # (disorot)
2) Butir 2
3) Item 3 # (dipilih)
4) Item 4

  • Ketika pengguna menekan down-arrowitem berikutnya disorot
  • Saat pengguna menekan up-arrowitem sebelumnya disorot
  • dll.
  • Ketika tabitem pengguna tekan dipilih
  • Ketika pengguna menekan shift+tabsemua item dipilih / tidak dipilih
  • Ketika pengguna menekan ctrl+asemua item dipilih
  • ...

Ini berfungsi dengan baik pada penggunaan saat ini, yang merupakan penggunaan pribadi saya di mana input difilter oleh pengaturan saya sendiri.

Pertanyaannya adalah bagaimana membuat ini dapat diandalkan di berbagai terminal.


Saya menggunakan solusi yang agak peretasan untuk membaca input:

while read -rsn1 k # Read one key (first byte in key press)
do
    case "$k" in
    [[:graph:]])
        # Normal input handling
        ;;
    $'\x09') # TAB
        # Routine for selecting current item
        ;;
    $'\x7f') # Back-Space
        # Routine for back-space
        ;;
    $'\x01') # Ctrl+A
        # Routine for ctrl+a
        ;;
    ...
    $'\x1b') # ESC
        read -rsn1 k
        [ "$k" == "" ] && return    # Esc-Key
        [ "$k" == "[" ] && read -rsn1 k
        [ "$k" == "O" ] && read -rsn1 k
        case "$k" in
        A) # Up
            # Routine for handling arrow-up-key
            ;;
        B) # Down
            # Routine for handling arrow-down-key
            ;;
        ...
        esac
        read -rsn4 -t .1 # Try to flush out other sequences ...
    esac
done

Dan seterusnya.


Seperti disebutkan, pertanyaannya adalah bagaimana membuat ini dapat diandalkan di berbagai terminal: yaitu urutan byte apa yang menentukan kunci tertentu. Apakah itu layak di bash?

Satu pemikiran adalah menggunakan salah satu dan tputatau infocmpmemfilter berdasarkan hasil yang diberikan oleh hal itu. Namun saya dalam kesulitan karena keduanya tputdan infocmpberbeda dari apa yang sebenarnya saya baca ketika benar-benar menekan tombol. Sama berlaku misalnya menggunakan C over bash.

for t in $(find /lib/terminfo -type f -printf "%f\n"); { 
    printf "%s\n" "$t:"; 
    infocmp -L1 $t | grep -E 'key_(left|right|up|down|home|end)';
}

Sekuens hasil dibaca seperti yang didefinisikan misalnya linux, tetapi tidak xterm, yang ditetapkan oleh TERM.

Misal panah kiri:

  • tput/ infocmp:\x1 O D
  • read: \x1 [ D

Apa yang saya lewatkan?

pengguna367890
sumber
tidak perlu menemukan kembali roda, iselect sudah melakukan ini. Atau, gunakan salah satu dialogvarian, atau gunakan bahasa dengan ncursesdukungan yang layak (perl atau python misalnya, jika Anda ingin tetap menggunakan bahasa "scripting").
cas
1
Catatan yang zshmemiliki dukungan kutukan bawaan (dalam modul zsh / kutukan) di samping kueri terminfo dasar dengan array echotibawaan dan $terminfoasosiatifnya.
Stéphane Chazelas

Jawaban:

5

Apa yang Anda lewatkan adalah bahwa sebagian besar deskripsi terminal ( linuxada dalam minoritas di sini, karena penggunaan string-code yang sulit dikenali .inputrc) menggunakan mode aplikasi untuk kunci khusus. Itu membuat kunci kursor seperti yang ditunjukkan oleh tputdan infocmpberbeda dari apa yang dikirim terminal Anda (tidak diinisialisasi). aplikasi kutukan selalu menginisialisasi terminal, dan data base terminal digunakan untuk itu tujuan.

dialogmemiliki kegunaannya, tetapi tidak secara langsung menjawab pertanyaan ini. Di sisi lain, itu merepotkan (secara teknis bisa dilakukan , jarang dilakukan ) untuk memberikan solusi bash-only. Secara umum kami menggunakan bahasa lain untuk melakukan ini.

Masalah dengan membaca kunci khusus adalah bahwa mereka sering beberapa byte, termasuk karakter canggung seperti escapedan ~. Anda dapat melakukan ini dengan bash, tetapi kemudian Anda harus menyelesaikan masalah menentukan kunci khusus apa yang bisa dibawa-bawa.

dialogkeduanya menangani input tombol khusus dan mengambil alih (sementara) tampilan Anda. Jika Anda benar-benar menginginkan program baris perintah yang sederhana, itu tidak benar dialog.

Berikut ini adalah program sederhana dalam C yang membaca kunci khusus dan mencetaknya dalam bentuk yang dapat dicetak (dan portabel):

#include <curses.h>

int
main(void)
{   
    int ch;
    const char *result;
    char buffer[80];

    filter();
    newterm(NULL, stderr, stdin);
    keypad(stdscr, TRUE);
    noecho();
    cbreak();
    ch = getch();
    if ((result = keyname(ch)) == 0) {
        /* ncurses does the whole thing, other implementations need this */
        if ((result = unctrl((chtype)ch)) == 0) {
            sprintf(buffer, "%#x", ch);
            result = buffer;
        }
    }
    endwin();
    printf("%s\n", result);
    return 0;
}

Seandainya ini disebut tgetch, Anda akan menggunakannya dalam skrip Anda seperti ini:

case $(tgetch 2>/dev/null) in
KEY_UP)
   echo "got cursor-up"
   ;;
KEY_BACKSPACE|"^H")
   echo "got backspace"
   ;;
esac

Bacaan lebih lanjut:

Thomas Dickey
sumber
Terima kasih. Ya, inputrcmemang pelakunya yang saya cari. Harus melihatnya lagi. Telah mempertimbangkan untuk menggunakan python atau C, tetapi merasa senang untuk melakukan hack sebagai skrip bash juga. Saya juga mencoba untuk melihat sumber ncurses untuk melihat apakah saya dapat mengekstrak bit yang saya butuhkan - tetapi setelah beberapa waktu menggali sumber saya meninggalkannya di atas es. The "proyek" dimulai sebagai perintah sederhana, kemudian menjadi script interaktif sederhana, dan kemudian diperpanjang pada itu lagi. Di suatu tempat di sepanjang jalan saya harus pergi bahasa lain , tetapi mendapat sedikit keras kepala (dan seperti yang disebutkan itu menyenangkan untuk di-hack di bash 2 :)
user367890
Ditemukan urutan dalam, antara lain /usr/share/doc/readline-common/inputrc.arrows,. Karena saya sudah memiliki fungsi "read_key" generik yang saya gunakan di skrip, saya berharap ada cara yang lebih mudah untuk menentukan urutan (dalam skrip) dari apa yang sebenarnya disajikan ketika tombol ditekan. Yaitu mirip dengan mengekstraksi definisi dari infocmp. Tapi tebak tidak dan harus membiarkannya apa adanya atau beralih ke bahasa lain. Sebuah kompromi tentu saja bisa menggunakan potongan-C Anda yang bagus. Tapi kemudian saya bisa menulis semuanya dalam C sebagai gantinya. (Maaf karena
berbagi berlebihan
Apakah itu kode C yang lengkap? Saya mendapatkan sekitar selusin kesalahan ketika saya mencoba mengompilasinya menggunakan gcc pada Debian 9
InterLinked
Anda mungkin menghilangkan -lncurses, dll.
Thomas Dickey
6

Sudahkah Anda mencoba menggunakan dialog? Muncul standar dengan sebagian besar distro Linux dan dapat membuat semua jenis dialog berbasis teks, termasuk daftar periksa.

Sebagai contoh:

exec 3>&1 # open temporary file handle and redirect it to stdout

#                           type      title        width height n-items    
items=$(dialog --no-lines --checklist "Title here" 20    70     4 \
          1 "Item 1" on \
          2 "Item 2" off \
          3 "Item 3" on \
          4 "Item 4" off \
            2>&1 1>&3) # redirect stderr to stdout to catch output, 
                       # redirect stdout to temporary file
selected_OK=$? # store result value
exec 3>&- # close new file handle 

# handle output
if [ $selected_OK = 0 ]; then
    echo "OK was selected."
    for item in $items; do
        echo "Item $item was selected."
    done
else
    echo "Cancel was selected."
fi

Anda akan mendapatkan sesuatu seperti ini:

masukkan deskripsi gambar di sini

Dan hasilnya adalah:

 OK was selected.
 Item 1 was selected.
 Item 3 was selected.

(atau item mana saja yang Anda pilih).

man dialog akan memberi Anda informasi tentang jenis dialog lain yang dapat Anda buat, dan cara menyesuaikan tampilan.

marinus
sumber
Memberi +1 untuk upaya, tetapi Dickey lebih ke titik dari apa yang saya minta. Bagi seseorang dengan apa masalah yang dijelaskan adalah - dalam arti yang lebih umum, daftar itu hanya untuk memberikan konteks. Kedua saya telah melihat dialog dengan cepat - dan memang saya belum melihatnya secara menyeluruh, kasus saya, untuk memperluasnya, adalah sebuah front untuk database sqlite dengan beberapa ribu catatan di mana saya memiliki contoh Page-Up / Down untuk gulir melalui seleksi. Scroll-region, scroll-buffer, baris status, ex line dengan input modal,
subfungsi
... tetapi dialog tampaknya tidak cukup memenuhi kebutuhan, atau agak rumit untuk kasus saya.
user367890
@ user367890 suara aplikasi Anda seperti cocok untuk perl Curses, DBIdan DBD::SQLitemodul. atau setara python mereka.
Kasus
@cas: Ya. Telah menulis aplikasi serupa menggunakan python dan C sebelumnya - meskipun saya harus belajar banyak tentang itu. "Proyek" ini lebih merupakan petualangan ke kemungkinan bash dan "untuk bersenang-senang" :) Meskipun saya semakin dekat untuk meninggalkannya atau memindahkannya ke bahasa lain. Terima kasih atas masukannya.
user367890