วิธีอ่านอินพุตของผู้ใช้เมื่อใช้สคริปต์ในไพพ์


10

ปัญหาทั่วไป

ฉันต้องการเขียนสคริปต์ที่โต้ตอบกับผู้ใช้แม้ว่ามันจะอยู่กลางสายโซ่ของท่อ

ตัวอย่างคอนกรีต

รูปธรรมก็จะใช้เวลาfileหรือstdinแสดงเส้น (มีหมายเลขบรรทัด) stdoutขอให้ผู้ใช้ป้อนข้อมูลการเลือกหรือสายตัวเลขและจากนั้นพิมพ์เส้นที่สอดคล้องกับ มาเรียกสคริปต์นี้selectorกัน ถ้าอย่างนั้นฉันก็อยากจะทำ

grep abc foo | selector > myfile.tmp

ถ้าfooมี

blabcbla
foo abc bar
quux
xyzzy abc

จากนั้นselectorนำเสนอตัวเลือก (บนเทอร์มินัลไม่ใช่ในmyfile.tmp!)

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

หลังจากนั้นฉันพิมพ์

2-3

และจบลงด้วย

foo abc bar
xyzzy abc

myfile.tmpเป็นเนื้อหาของ

ฉันมีสคริปต์ตัวเลือกทำงานอยู่และโดยพื้นฐานแล้วมันจะทำงานได้อย่างสมบูรณ์แบบหากฉันไม่เปลี่ยนเส้นทางอินพุตและเอาต์พุต ดังนั้น

selector foo

ทำงานเหมือนที่ฉันต้องการ อย่างไรก็ตามเมื่อทำการรวมสิ่งต่าง ๆ เข้าด้วยกันดังตัวอย่างข้างต้นให้selectorพิมพ์ตัวเลือกที่นำเสนอไปที่myfile.tmpและพยายามอ่านสิ่งที่เลือกจากอินพุตที่มีสีเทา

แนวทางของฉัน

ฉันพยายามที่จะใช้-uธงของreadเช่นเดียวกับใน

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

แต่นี่ไม่ได้ทำในสิ่งที่ฉันหวังไว้

ถาม:ฉันจะรับการโต้ตอบจากผู้ใช้จริงได้อย่างไร


ทำสคริปต์และบันทึกผลลัพธ์ในตัวแปรแล้วนำเสนอผู้ใช้ที่คุณต้องการ?
Hackaholic

@Hackaholic - ฉันไม่แน่ใจว่าคุณหมายถึงอะไร ฉันต้องการสคริปต์ที่สามารถวางในลำดับของไพพ์ไลน์ใด ๆ (เช่นวิธี Unix) ฉันให้ตัวอย่างที่ซับซ้อนด้านบน แต่นั่นไม่ใช่กรณีการใช้งานอย่างเดียวที่ฉันมีอยู่ในใจ
jmc

1
ใช้cmd | { some processing; read var </dev/tty; } | cmd
mikeserv

@mikeserv - น่าสนใจ! ตอนนี้ฉัน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; }'ทำงานได้ดีมาก อย่างไรก็ตามgrep b foo | selector | wc -lพักที่นี่ ความคิดใดที่จะแก้ไขได้อย่างไร โดยวิธีการrangeselectที่ผมใช้สามารถพบได้ที่pastebin.com/VAxTSSHs มันเป็นสคริปต์ AWK ง่าย ๆ ที่พิมพ์บรรทัดของไฟล์ที่ตรงกับชุดของผ้าลินินที่กำหนด (ช่วงสามารถเป็นสิ่งต่าง ๆ เช่น "3-10, 12,14,16-20".)
jmc

1
ไม่อย่างaliasนั้นแทนที่จะselector() { all of that stuff...; }เป็นฟังก์ชั่น aliasES เปลี่ยนชื่อคำสั่งง่ายๆในขณะที่ฟังก์ชั่นแพ็คคำสั่งผสมเป็นหนึ่งคำสั่งง่ายๆ
mikeserv

คำตอบ:


8

การใช้/proc/$PPID/fd/0ไม่น่าเชื่อถือ: พาเรนต์ของselectorกระบวนการอาจไม่มีเทอร์มินัลเป็นอินพุต

มีความเป็นเส้นทางมาตรฐาน/dev/ttyที่มักจะหมายถึงขั้วกระบวนการปัจจุบัน:

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

หรือ

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

1
ขอบคุณที่แก้ปัญหาของฉัน คำตอบนั้นเรียบง่ายเล็กน้อย ฉันคิดว่ามันอาจได้รับประโยชน์จากการรวมคำแนะนำของ mikeserv ไว้ในข้อคิดเห็นกับคำถาม
jmc

2

ฉันได้เขียนฟังก์ชั่นเล็ก ๆ : มันจะไม่ตอบเกี่ยวกับสิ่งที่คุณถามการผูกมัดของท่อ แต่จะแก้ปัญหาของคุณ

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

grepผลัดฟังก์ชั่นมากกว่าการขัดแย้งทั้งหมดที่คุณให้ทันที หากคุณใช้เชลล์ glob เพื่อระบุไฟล์ที่ควรอ่านจากมันจะคืนค่าการจับคู่ทั้งหมดในไฟล์ทั้งหมดเริ่มต้นด้วยลำดับแรกในลำดับ glob และลงท้ายด้วยคู่สุดท้าย

grepส่งออกไปยังnlหมายเลขที่แต่ละบรรทัดและผ่านการส่งออกไปteeที่ซ้ำกันของการส่งออกทั้งไปstdoutและ/dev/ttyกลับ ซึ่งหมายความว่าเอาต์พุตจากไปป์ไลน์จะถูกพิมพ์พร้อมกันทั้งสองไปยังอาเรย์อาร์กิวเมนต์ของฟังก์ชั่นโดยที่มันถูกแบ่งบน\newlines และไปยังเทอร์มินัลขณะที่มันทำงาน

ถัดไป_in()ฟังก์ชั่นพยายามที่จะreadเลือกถ้ามีอย่างน้อย 1 ผลจากการกระทำก่อนหน้านี้สูงสุดห้าครั้ง -ตัวเลือกที่สามารถประกอบด้วยตัวเลขเท่านั้นคั่นด้วยช่องว่างหรือช่วงตัวเลขอื่นแยกจากกันโดย หากสิ่งอื่นคือread (รวมถึงบรรทัดว่าง)มันจะลองอีกครั้ง - แต่อย่างเดียวก่อนหน้านี้สูงสุดห้าครั้ง

สุดท้าย_out()ฟังก์ชันแยกวิเคราะห์การเลือกของผู้ใช้และขยายช่วงใด ๆ ในนั้น มันพิมพ์ผลลัพธ์ในรูปแบบ"${[num]}"สำหรับแต่ละ - ดังนั้นการจับคู่ค่าของบรรทัดที่เก็บไว้ในinf()อาร์จีของอาร์ เอาต์พุตนี้ถูกเขียนevalเป็น args printfซึ่งจะพิมพ์เฉพาะบรรทัดที่ผู้ใช้เลือก

มันreadมาจากเครื่องเทอร์มินัลอย่างชัดเจนและพิมพ์Select:เมนูไปstderrเท่านั้น ตัวอย่างเช่นงานดังต่อไปนี้:

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

แต่คุณสามารถใช้ตัวเลือกใด ๆ ที่คุณจะให้grepและชื่อไฟล์ใด ๆ ที่คุณอาจมอบให้ นั่นคือคุณสามารถใช้รูปแบบใดก็ได้ แต่เป็นผลข้างเคียงของการแยกวิเคราะห์อินพุตด้วย$IFSมันจะไม่ทำงานหากคุณค้นหาบรรทัดว่าง แต่ใครจะต้องการเลือกจากรายการหมายเลขบรรทัดว่าง?

บันทึกล่าสุดว่าเพราะสิ่งนี้ทำงานได้โดยตรงโดยการแปลอินพุตผู้ใช้ที่เป็นตัวเลขลงในพารามิเตอร์ตำแหน่งตัวเลขที่เก็บไว้ในอาเรย์อาร์กิวเมนต์ของฟังก์ชั่นจากนั้นเอาต์พุตจะเป็นทุกอย่างที่ผู้ใช้เลือก มัน.

ตัวอย่างเช่น:

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 มันเป็นเพียงความคิดไม่ใช่สคริปต์ทั้งหมดและอย่างหนึ่งคุณกำลังพูดถึงการทดสอบไฟล์ต้นฉบับอยู่ในดิสก์เท่านั้นดังนั้นคุณจึงนำมันมา ดังนั้นฉันคิดว่ามันไม่ใช่ปัญหาหรือความพยายามพิเศษในการทดสอบ
Hackaholic

@ mikeserv ใช่คุณถูกต้องฉันไม่ได้ตรวจสอบทุกสิ่งเช่นการป้อนข้อมูลที่ไม่เหมาะสมและทั้งหมด ขอบคุณสำหรับจุดของคุณ
Hackaholic

@mikeserv ฉันรู้ทุกขั้นพื้นฐานของการเขียนโปรแกรมเปลือก u สามารถแนะนำผมวิธีการที่จะไปสำหรับขั้นสูง
Hackaholic

ใช่แน่นอนฉันยินดีที่จะแก้ไขมัน
Hackaholic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.