Cara membaca input pengguna saat menggunakan skrip dalam pipa


10

Masalah umum

Saya ingin menulis skrip yang berinteraksi dengan pengguna meskipun itu di tengah-tengah rantai pipa.

Contoh nyata

Konkretnya, diperlukan a fileatau stdin, menampilkan garis (dengan nomor baris), meminta pengguna untuk memasukkan pilihan atau nomor baris, dan kemudian mencetak baris yang sesuai untuk stdout. Sebut skrip ini selector. Maka pada dasarnya, saya ingin bisa melakukannya

grep abc foo | selector > myfile.tmp

Jika fooberisi

blabcbla
foo abc bar
quux
xyzzy abc

kemudian memberi selectorsaya (di terminal, bukan di myfile.tmp!) dengan opsi

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

setelah itu saya ketik

2-3

dan berakhir dengan

foo abc bar
xyzzy abc

sebagai isi myfile.tmp.

Saya menjalankan skrip pemilih dan menjalankannya, dan pada dasarnya skrip ini berfungsi dengan baik jika saya tidak mengarahkan input dan output. Begitu

selector foo

berperilaku seperti yang saya inginkan. Namun, ketika menyatukan hal-hal seperti pada contoh di atas, selectormencetak opsi yang disajikan ke myfile.tmpdan mencoba membaca pilihan dari input yang diterima.

Pendekatan saya

Saya sudah mencoba menggunakan -ubendera read, seperti pada

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

tapi ini tidak melakukan apa yang saya harapkan.

T: Bagaimana cara saya mendapatkan interaksi pengguna yang sebenarnya?


membuat skrip dan menyimpan output dalam variabel, dan kemudian menyajikan pengguna yang Anda inginkan ??
Hackaholic

@ Hackaholic - Saya tidak yakin apa yang Anda maksud. Saya ingin skrip yang dapat ditempatkan dalam segala jenis urutan pipa (yaitu cara Unix). Saya memberikan contoh yang rumit di atas, tetapi tentu saja itu bukan satu-satunya use case yang saya pikirkan.
jmc

1
Gunakancmd | { some processing; read var </dev/tty; } | cmd
mikeserv

@mikeserv - Menarik! Saya sekarang memiliki 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; }'yang berfungsi cukup baik. Namun grep b foo | selector | wc -listirahat di sini. Ada ide bagaimana cara memperbaikinya? Omong-omong, rangeselectyang saya gunakan dapat ditemukan di pastebin.com/VAxTSSHs . Ini adalah skrip AWK sederhana yang mencetak garis-garis file yang sesuai dengan sejumlah linenumber tertentu. (Rentang bisa berupa "3-10, 12,14,16-20".)
jmc

1
Jangan lakukan aliasitu, lebih selector() { all of that stuff...; }ke fungsi. aliases mengubah nama perintah sederhana sedangkan fungsi mengemas perintah gabungan menjadi satu perintah sederhana .
mikeserv

Jawaban:


8

Penggunaan /proc/$PPID/fd/0tidak dapat diandalkan: induk dari selectorproses mungkin tidak memiliki terminal sebagai inputnya.

Ada jalur standar yang selalu mengacu pada terminal proses saat ini: /dev/tty.

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

atau

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

1
Terima kasih, itu menyelesaikan masalah saya. Jawabannya agak minimalis. Saya kira itu mungkin mendapat manfaat dari memasukkan beberapa saran mikeserv dalam komentar ke pertanyaan.
jmc

2

Saya telah menulis fungsi kecil: itu tidak akan menjawab tentang apa yang Anda minta chaining pipa tetapi akan menyelesaikan masalah Anda.

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

Fungsi membalik semua argumen yang Anda berikan segera grep. Jika Anda menggunakan shell glob untuk menentukan file yang harus dibaca dari itu akan mengembalikan semua pertandingan di semua file, mulai dengan yang pertama dalam urutan glob dan berakhir dengan pertandingan terakhir.

grepmeneruskan outputnya ke nlnomor mana setiap baris dan yang melewati outputnya teeyang menggandakan outputnya ke stdoutdan ke /dev/tty. Ini berarti bahwa output dari pipa secara bersamaan dicetak baik ke array argumen fungsi di mana ia dibagi pada \nbaris baru dan ke terminal saat ia bekerja.

Selanjutnya _in()fungsi mencoba untuk readmemilih jika setidaknya ada 1 hasil dari tindakan sebelumnya maksimum lima kali. Pilihan dapat terdiri dari hanya angka yang dipisahkan oleh spasi, atau rentang angka yang dipisahkan oleh -. Jika ada yang lain read (termasuk baris kosong) itu akan mencoba lagi - tetapi hanya, seperti sebelumnya, maksimum lima kali.

Terakhir _out()fungsi mem-parsing pilihan pengguna dan memperluas rentang apa pun di dalamnya. Ini mencetak hasilnya dalam bentuk "${[num]}"untuk masing-masing - dengan demikian cocok dengan nilai dari garis yang disimpan dalam inf()array arg. Output ini adalah evaled sebagai argumen printfyang karenanya hanya mencetak garis yang telah dipilih pengguna.

Ini secara eksplisit readdari terminal dan hanya mencetak Select:menu ke stderrdan jadi itu banyak pipa ramah. Misalnya, yang berikut ini berfungsi:

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

Tetapi Anda dapat menggunakan opsi apa pun yang akan Anda berikan grepdan sejumlah nama file yang mungkin Anda berikan juga. Artinya, Anda dapat menggunakan salah satu dari satu jenis - sebagai efek samping dari input parsing $IFSdengannya tidak akan berfungsi jika Anda mencari baris kosong. Tetapi siapa yang ingin memilih dari daftar nomor baris kosong?

Catatan terakhir bahwa karena ini bekerja dengan secara langsung menerjemahkan input pengguna numerik ke dalam parameter posisi numerik yang disimpan dalam array argumen fungsi, maka output akan menjadi apa pun yang dipilih pengguna, sebanyak yang dipilih pengguna, dan dalam urutan apa pun pengguna memilih Itu.

Sebagai contoh:

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

@ mikeserv itu hanya sebuah ide, bukan keseluruhan skrip, dan satu hal, Anda berbicara tentang tes, file asli di disk saja, jadi Anda mengambil dari mereka. jadi saya pikir itu bukan masalah atau usaha ekstra untuk mengujinya
Hackaholic

@ mikeserv ya Anda benar, saya belum memvalidasi semua hal, seperti input yang tidak tepat dan semua. terima kasih untuk poin Anda
Hackaholic

@mikeserv, saya tahu semua dasar pemrograman shell, dapatkah saya membimbing saya bagaimana menjadi maju
Hackaholic

ya tentu saya akan senang mengeditnya
Hackaholic
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.