Đọc các phím đặc biệt trong bash


8

Tôi đang chơi với một kịch bản, trong số những thứ khác, liệt kê một danh sách lựa chọn. Như trong:

1) Mục 1               # (được tô sáng)
2) Mục 2
3) Mục 3 # (đã chọn)
4) Mục 4

  • Khi người dùng nhấn down-arrowcác mục tiếp theo được tô sáng
  • Khi người dùng nhấn up-arrowcác mục trước được tô sáng
  • Vân vân.
  • Khi người dùng nhấn tabmục được chọn
  • Khi người dùng nhấn shift+tabtất cả các mục được chọn / bỏ chọn
  • Khi người dùng nhấn ctrl+atất cả các mục được chọn
  • ...

Điều này hoạt động tốt như sử dụng hiện tại, đó là sử dụng cá nhân của tôi, nơi đầu vào được lọc theo thiết lập của riêng tôi.

Câu hỏi là làm thế nào để làm cho điều này đáng tin cậy trên các thiết bị đầu cuối khác nhau.


Tôi sử dụng một giải pháp hơi hackish để đọc đầu vào:

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

Và như thế.


Như đã đề cập, câu hỏi là làm thế nào để làm cho điều này đáng tin cậy trên các thiết bị đầu cuối khác nhau: tức là chuỗi byte nào xác định một khóa cụ thể. Nó thậm chí còn khả thi trong bash?

Một ý nghĩ là sử dụng hoặc tputhoặc infocmplọc theo kết quả được đưa ra bởi điều đó. Tuy nhiên, tôi gặp khó khăn vì cả hai tputinfocmpkhác với những gì tôi thực sự đọc khi nhấn phím. Tương tự như vậy, ví dụ sử dụng C trên 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)';
}

Trình tự năng suất đọc như được định nghĩa chẳng hạn linux, nhưng không xterm, đó là những gì được thiết lập bởi TERM.

Ví dụ mũi tên trái:

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

Tôi đang thiếu gì?


không cần phải phát minh lại bánh xe, iselect đã thực hiện điều này. Ngoài ra, sử dụng một trong các dialogbiến thể hoặc sử dụng ngôn ngữ có ncursessự hỗ trợ tốt (ví dụ: perl hoặc python, nếu bạn muốn gắn bó với các ngôn ngữ "scripting").
cas

1
Lưu ý rằng zshcó hỗ trợ lời nguyền dựng sẵn (trong mô-đun zsh / curses) ngoài truy vấn terminfo cơ bản với mảng echotidựng sẵn và $terminfoliên kết.
Stéphane Chazelas 16/07/2016

Câu trả lời:


5

Những gì bạn đang thiếu là hầu hết các mô tả thiết bị đầu cuối ( linuxthuộc nhóm thiểu số ở đây, do việc sử dụng phổ biến các chuỗi mã hóa cứng .inputrc) sử dụng chế độ ứng dụng cho các khóa đặc biệt. Điều đó làm cho các phím con trỏ như được hiển thị bởi tputinfocmpkhác với những gì thiết bị đầu cuối (chưa được khởi tạo) của bạn gửi. nguyền rủa các ứng dụng luôn khởi tạo thiết bị đầu cuối và cơ sở dữ liệu đầu cuối được sử dụng cho mục đích đó .

dialogcó công dụng của nó, nhưng không trực tiếp giải quyết câu hỏi này. Mặt khác, nó là cồng kềnh (về mặt kỹ thuật có thể làm được , hiếm khi thực hiện ) để cung cấp một giải pháp bash-only. Nói chung, chúng tôi sử dụng các ngôn ngữ khác để làm điều này.

Vấn đề với việc đọc các khóa đặc biệt là chúng thường có nhiều byte, bao gồm các ký tự khó xử như escape~. Bạn có thể làm điều này với bash, nhưng sau đó bạn phải giải quyết vấn đề xác định chính xác khóa này là gì.

dialogcả hai đều xử lý đầu vào của các phím đặc biệt và chiếm (tạm thời) màn hình của bạn. Nếu bạn thực sự muốn một chương trình dòng lệnh đơn giản, thì không dialog.

Đây là một chương trình đơn giản trong C đọc một khóa đặc biệt và in nó ở dạng có thể in (và di động):

#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;
}

Giả sử điều này được gọi tgetch, bạn sẽ sử dụng nó trong kịch bản của mình như thế này:

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

Đọc thêm:


Cảm ơn bạn. Vâng, inputrcthực sự là thủ phạm mà tôi đang tìm kiếm. Phải nhìn vào nó thêm một số. Đã xem xét việc đi python hoặc C, nhưng cũng thấy vui khi hack vào như một tập lệnh bash. Tôi cũng đã cố gắng xem xét nguồn ncurses để xem liệu tôi có thể trích xuất các bit tôi cần không - nhưng sau một thời gian đào nguồn tôi đã để nó trên băng. Các "dự án" bắt đầu ra như là một lệnh đơn giản, sau đó trở thành một kịch bản tương tác đơn giản, và sau đó mở rộng trên đó một lần nữa. Một nơi nào đó trên đường tôi đáng lẽ phải đi ngôn ngữ khác , nhưng có một chút bướng bỉnh (và như đã đề cập, thật vui khi hack trong bash 2 :)
user367890

Tìm thấy các trình tự trong, trong số những người khác , /usr/share/doc/readline-common/inputrc.arrows. Vì tôi đã có một hàm "read_key" chung mà tôi sử dụng trên toàn bộ tập lệnh, tôi hy vọng có một cách dễ dàng hơn để xác định các chuỗi (trong tập lệnh) từ những gì được trình bày thực sự khi nhấn phím. Tức là tương tự như trích xuất định nghĩa từ infocmp. Nhưng đoán không và phải để nguyên như vậy hoặc chuyển sang ngôn ngữ khác. Một sự thỏa hiệp tất nhiên có thể là sử dụng đoạn mã C hay, đẹp của bạn. Nhưng sau đó tôi có thể viết toàn bộ bằng C thay thế. (Xin lỗi vì
quá đắt

Đó có phải là mã C hoàn chỉnh không? Tôi nhận được khoảng một chục lỗi khi tôi cố gắng biên soạn này sử dụng gcc trên Debian 9
liên kết với nhau

Bạn có thể đã bỏ qua -lncurses, v.v.
Thomas Dickey

6

Bạn đã thử sử dụng dialog? Nó có tiêu chuẩn với hầu hết các bản phân phối Linux và có thể tạo tất cả các loại hộp thoại dựa trên văn bản, bao gồm cả danh sách kiểm tra.

Ví dụ:

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

Bạn sẽ nhận được một cái gì đó như thế này:

nhập mô tả hình ảnh ở đây

Và đầu ra sẽ là:

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

(hoặc bất cứ mục nào bạn đã chọn).

man dialog sẽ cung cấp cho bạn thông tin về các loại hộp thoại khác mà bạn có thể tạo và cách tùy chỉnh giao diện.


+1 cho nỗ lực, nhưng Dickey quan trọng hơn những gì tôi đang hỏi. Đối với một vấn đề được mô tả là - theo nghĩa chung hơn, danh sách chỉ đơn thuần là đưa ra một số bối cảnh. Thứ hai, tôi đã có một cái nhìn nhanh về hộp thoại - và phải thừa nhận rằng tôi đã không xem xét kỹ, trường hợp của tôi, để mở rộng trên đó, là một cơ sở dữ liệu sqlite với hàng ngàn bản ghi mà tôi có ví dụ như Page-Up / Down to lựa chọn máng di chuyển. Vùng cuộn, bộ đệm cuộn, một dòng trạng thái, một dòng cũ với đầu vào phương thức, các chức năng phụ để lọc, v.v. Nói tóm lại, nó có vẻ phức tạp, nhưng khá đơn giản
dùng367890

Tuy nhiên, hộp thoại dường như không đáp ứng được nhu cầu, hoặc hơi cồng kềnh cho trường hợp của tôi.
dùng367890

@ user367890 ứng dụng của bạn âm thanh như một trận đấu hoàn hảo cho perl Curses, DBIDBD::SQLitemô-đun. hoặc tương đương trăn của họ.
cas

@cas: Vâng. Đã viết các ứng dụng tương tự bằng python và C trước đó - mặc dù tôi phải học lại rất nhiều về nó. "Dự án" này giống như một cuộc phiêu lưu vào các khả năng bash và "vì niềm vui của nó" :) Mặc dù tôi đang tiến gần đến việc từ bỏ nó hoặc chuyển nó sang ngôn ngữ khác. Cảm ơn cho đầu vào.
dùng367890
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.