Cách đọc đầu vào của người dùng khi sử dụng script trong pipe


10

Vấn đề chung

Tôi muốn viết một kịch bản tương tác với người dùng mặc dù nó ở giữa một chuỗi các đường ống.

Ví dụ cụ thể

Cụ thể, phải mất một filehoặc stdin, hiển thị các dòng (có số dòng), yêu cầu người dùng nhập một lựa chọn hoặc số dòng, sau đó in các dòng tương ứng stdout. Hãy gọi kịch bản này selector. Về cơ bản, tôi muốn có thể làm

grep abc foo | selector > myfile.tmp

Nếu foochứa

blabcbla
foo abc bar
quux
xyzzy abc

sau đó selectortrình bày cho tôi (trên thiết bị đầu cuối, không phải trong myfile.tmp!) với các tùy chọn

1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:

sau đó tôi gõ vào

2-3

và kết thúc với

foo abc bar
xyzzy abc

như nội dung của myfile.tmp.

Tôi đã có một tập lệnh chọn và chạy, và về cơ bản nó đang hoạt động hoàn hảo nếu tôi không chuyển hướng đầu vào và đầu ra. Vì thế

selector foo

cư xử như tôi muốn Tuy nhiên, khi kết hợp các thứ lại với nhau như trong ví dụ trên, hãy selectorin các tùy chọn được trình bày sang myfile.tmpvà cố gắng đọc một lựa chọn từ đầu vào được grepping.

Cách tiếp cận của tôi

Tôi đã thử sử dụng -ucờ của read, như trong

exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "

nhưng điều này không làm những gì tôi hy vọng nó sẽ làm.

Q: Làm thế nào để tôi có được tương tác người dùng thực tế?


tạo một kịch bản và lưu đầu ra trong biến, và sau đó người dùng hiện tại muốn bạn muốn ??
Hackaholic

@Hackaholic - Tôi không chắc ý của bạn là gì. Tôi muốn một tập lệnh có thể được đặt trong bất kỳ loại trình tự đường ống nào (tức là cách Unix). Tôi đã đưa ra một ví dụ công phu ở trên, nhưng đó chắc chắn không phải là trường hợp sử dụng duy nhất tôi có trong đầu.
jmc

1
Sử dụngcmd | { some processing; read var </dev/tty; } | cmd
mikeerv

@mikeerv - Thú vị! Bây giờ tôi có alias selector='{ TMPFILE=$(mktemp); cat > $TMPFILE; nl -s") " $TMPFILE | column -c $(tput cols); read -e -p"Select options: " < /dev/tty; rangeselect -v range="$REPLY" $TMPFILE; rm $TMPFILE; }'hoạt động khá tốt. Tuy nhiên grep b foo | selector | wc -lphá vỡ ở đây. Bất kỳ ý tưởng làm thế nào để khắc phục điều đó? Nhân tiện, cái rangeselectmà tôi đã sử dụng có thể được tìm thấy tại pastebin.com/VAxTSSHs . Đó là một tập lệnh AWK đơn giản in các dòng của một tệp tương ứng với một phạm vi vải lanh nhất định. (Phạm vi có thể là những thứ như "3-10, 12,14,16-20".)
jmc

1
Đừng aliasnhư vậy, thay selector() { all of that stuff...; }vào đó là một chức năng. aliases đổi tên các lệnh đơn giản trong khi các hàm đóng gói một lệnh ghép thành một lệnh đơn giản .
mikeerv

Câu trả lời:


8

Việc sử dụng /proc/$PPID/fd/0là không đáng tin cậy: cha mẹ của selectorquá trình có thể không có thiết bị đầu cuối làm đầu vào của nó.

Có một đường dẫn chuẩn luôn đề cập đến thiết bị đầu cuối của quy trình hiện tại : /dev/tty.

nl "$INPUT" >/dev/tty
read -p"Select options: " </dev/tty

hoặc là

exec </dev/tty >/dev/tty
nl "$INPUT"
read -p"Select options: "

1
Cảm ơn, điều đó giải quyết vấn đề của tôi. Câu trả lời là một chút tối giản mặc dù. Tôi đoán nó có thể có lợi từ việc kết hợp một số lời khuyên của mikeerv trong các bình luận cho câu hỏi.
jmc

2

Tôi đã viết một chức năng nhỏ: nó sẽ không trả lời về những gì bạn đã hỏi về đường ống nhưng sẽ giải quyết vấn đề của bạn.

inf() ( [ -n "$ZSH_VERSION" ] && emulate sh
        unset n i c; set -f; tab='      ' IFS='
';      _in()   until [ "$((i+=1))" -gt 5 ] && exit 1
                printf '\nSelect: '
                read -r c && [ -n "${c##*[!- 0-9]*}" ]
                do echo "Invalid selection."
                done
        _out()  for n do i=; [ "$n" = . ]  &&
                printf '"${%d#*$tab}" ' $c ||
                until c="${c#*.} ${i:=${n%%-*}}"
                [ "$((i+=1))" -gt "${n#*-}" ]
                do :; done; done
set -- $(grep "$@"|nl -w1 -s "$tab"|tee /dev/tty)
i=$((($#<1)*5)); _in </dev/tty >/dev/tty
eval "printf '%s\n' $(c=$c\ . IFS=\ ;_out $c)"
)

Hàm này chuyển qua tất cả các đối số bạn đưa ra ngay lập tức grep. Nếu bạn sử dụng shell toàn cầu để chỉ định các tệp nên đọc từ nó sẽ trả về tất cả các kết quả khớp trong tất cả các tệp, bắt đầu bằng lần đầu tiên theo thứ tự toàn cầu và kết thúc bằng kết quả khớp cuối cùng.

grepchuyển đầu ra của nó tới nlsố nào mỗi dòng và vượt qua đầu ra của nó để teesao chép đầu ra của nó cả stdoutvà tới /dev/tty. Điều này có nghĩa là đầu ra từ đường ống được in đồng thời cả mảng đối số của hàm nơi nó được phân chia trên \newlines và tới terminal khi nó hoạt động.

Tiếp theo, _in()hàm cố gắng readtrong một lựa chọn nếu có ít nhất 1 kết quả từ hành động trước đó tối đa năm lần. Việc lựa chọn có thể chỉ bao gồm các số được phân tách bằng dấu cách hoặc các phạm vi số khác được phân tách bằng -. Nếu có bất cứ điều gì khác read (bao gồm một dòng trống), nó sẽ thử lại - nhưng chỉ, như trước đây, tối đa là năm lần.

Cuối cùng, _out()chức năng phân tích lựa chọn của người dùng và mở rộng bất kỳ phạm vi nào trong đó. Nó in kết quả của nó dưới dạng "${[num]}"cho từng mẫu - do đó khớp với giá trị của các dòng được lưu trữ trong inf()mảng arg. Đầu ra này là evaled dưới dạng đối printfsố do đó chỉ in các dòng mà người dùng đã chọn.

Nó rõ ràng readlà từ thiết bị đầu cuối và chỉ in Select:menu đến stderrvà vì vậy nó rất thân thiện với đường ống. Ví dụ, các công việc sau:

seq 100 |inf 3|grep 8
1       3
2       13
3       23
4       30
5       31
6       32
7       33
8       34
9       35
10      36
11      37
12      38
13      39
14      43
15      53
16      63
17      73
18      83
19      93

Select: 6 9 12-18
38
83

Nhưng bạn có thể sử dụng bất kỳ tùy chọn nào bạn sẽ cung cấp grepvà bất kỳ số tên tệp nào bạn cũng có thể sử dụng . Đó là, bạn có thể sử dụng bất kỳ loại nào - như một hiệu ứng phụ của đầu vào phân tích cú pháp của nó với $IFSnó sẽ không hoạt động nếu bạn đang tìm kiếm các dòng trống. Nhưng ai sẽ muốn chọn từ một danh sách các dòng trống?

Lưu ý cuối cùng bởi vì điều này hoạt động bằng cách dịch trực tiếp đầu vào của người dùng số sang các tham số vị trí số được lưu trữ trong mảng đối số của hàm, nên đầu ra sẽ là bất cứ thứ gì người dùng chọn, bao nhiêu lần người dùng chọn nó và theo bất kỳ thứ tự nào mà người dùng chọn nó

Ví dụ:

seq 1000 | inf 00\$

1       100
2       200
3       300
4       400
5       500
6       600
7       700
8       800
9       900
10      1000

Select: 4-8 1 1 3-6
400
500
600
700
800
100
100
300
400
500
600

@mikeerv đó chỉ là một ý tưởng, không phải toàn bộ tập lệnh, và một điều, bạn nói về kiểm tra, tệp gốc chỉ nằm trong đĩa, vì vậy bạn lấy từ chúng. Vì vậy, tôi nghĩ rằng nó không phải là một vấn đề hoặc nỗ lực thêm để kiểm tra nó
Hackaholic

@mikeerv yep bạn đúng, tôi chưa xác thực tất cả mọi thứ, như đầu vào không đúng và tất cả. cảm ơn vì quan điểm của bạn
Hackaholic

@mikeerv Tôi biết tất cả các cơ bản về lập trình shell, bạn có thể hướng dẫn tôi làm thế nào để tiến lên
Hackaholic

vâng chắc chắn tôi sẽ rất vui khi chỉnh sửa nó
Hackaholic
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.