การใช้เครื่องมือของไดอะล็อก: --output-fd flag
หากคุณอ่าน man page สำหรับไดอะล็อกจะมีตัวเลือก--output-fd
ที่ให้คุณตั้งค่าอย่างชัดเจนว่าเอาต์พุตไปที่ใด (STDOUT 1, STDERR 2) แทนที่จะเป็นค่าเริ่มต้นไปที่ STDERR
ร้องคุณสามารถเห็นฉันใช้dialog
คำสั่งตัวอย่างด้วยการระบุว่าการส่งออกอย่างชัดเจนจะต้องไปที่ file descriptor 1 ซึ่งช่วยให้ฉันบันทึกไว้ใน MYVAR
MYVAR=$(dialog --inputbox "THIS OUTPUT GOES TO FD 1" 25 25 --output-fd 1)
ใช้ชื่อ pipes
วิธีทางเลือกซึ่งมีจำนวนมากที่มีศักยภาพที่ซ่อนอยู่คือการใช้สิ่งที่เรียกไปป์ที่มีชื่อ
#!/bin/bash
mkfifo /tmp/namedPipe1 # this creates named pipe, aka fifo
# to make sure the shell doesn't hang, we run redirection
# in background, because fifo waits for output to come out
dialog --inputbox "This is an input box with named pipe" 40 40 2> /tmp/namedPipe1 &
# release contents of pipe
OUTPUT="$( cat /tmp/namedPipe1 )"
echo "This is the output " $OUTPUT
# clean up
rm /tmp/namedPipe1
คำตอบดั้งเดิมโดย user.dz และคำอธิบายของ ByteCommanderว่าทั้งสองเป็นโซลูชันที่ดีและภาพรวมของสิ่งที่ทำ อย่างไรก็ตามฉันเชื่อว่าการวิเคราะห์เชิงลึกอาจเป็นประโยชน์ในการอธิบายว่าทำไมจึงใช้งานได้
ก่อนอื่นสิ่งสำคัญคือต้องเข้าใจสองสิ่ง: ปัญหาที่เราพยายามแก้ไขคืออะไรและการทำงานพื้นฐานของกลไกเชลล์ที่เราจัดการอยู่คืออะไร ภารกิจคือการดักจับเอาต์พุตของคำสั่งผ่านการทดแทนคำสั่ง ภายใต้ภาพรวมแบบง่าย ๆ ที่ทุกคนรู้การแทนที่คำสั่งจะดักจับstdout
คำสั่งและปล่อยให้มันถูกนำกลับมาใช้โดยอย่างอื่น ในกรณีนี้result=$(...)
เป็นส่วนหนึ่งควรบันทึกผลลัพธ์ของสิ่งที่คำสั่งที่กำหนดโดยเข้าไปในตัวแปรที่เรียกว่า...
result
ภายใต้ประทุนแล้วการแทนที่คำสั่งจะถูกนำไปใช้จริงเป็นไพพ์ซึ่งมีกระบวนการลูก (คำสั่งจริงที่รัน) และกระบวนการอ่าน (ซึ่งจะบันทึกเอาต์พุตไปยังตัวแปร) เรื่องนี้เห็นได้ชัดว่ามีร่องรอยของการเรียกระบบ ขอให้สังเกตว่า file descriptor 3 เป็นจุดอ่านจบของไพพ์ในขณะที่ 4 คือปลายเขียน สำหรับกระบวนการ child ของecho
ซึ่งเขียนไปยังstdout
- file descriptor 1, descriptor ไฟล์นั้นจริง ๆ แล้ว copy ของ file descriptor 4, ซึ่งเป็นการเขียนสิ้นสุดของไพพ์ ขอให้สังเกตstderr
ว่าไม่ได้มีบทบาทในที่นี้เพียงเพราะเป็นท่อเชื่อมต่อstdout
เท่านั้น
$ strace -f -e pipe,dup2,write,read bash -c 'v=$(echo "X")'
...
pipe([3, 4]) = 0
strace: Process 6200 attached
[pid 6199] read(3, <unfinished ...>
[pid 6200] dup2(4, 1) = 1
[pid 6200] write(1, "X\n", 2 <unfinished ...>
[pid 6199] <... read resumed> "X\n", 128) = 2
[pid 6200] <... write resumed> ) = 2
[pid 6199] read(3, "", 128) = 0
[pid 6200] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6200, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
ลองกลับไปที่คำตอบดั้งเดิมสำหรับวินาที ตั้งแต่ตอนนี้เรารู้ว่าการdialog
เขียนกล่อง TUI ไปที่stdout
ตอบstderr
และภายในการแทนที่คำสั่ง stdout
จะถูกไพพ์ไปที่อื่นเรามีส่วนหนึ่งของการแก้ปัญหา - เราจำเป็นต้อง rewire file descriptors ในลักษณะที่stderr
จะถูกไพพ์ไปยังกระบวนการอ่าน นี่เป็น2>&1
ส่วนหนึ่งของคำตอบ อย่างไรก็ตามเราจะทำอะไรกับกล่อง TUI
นั่นคือสิ่งที่ file descriptor 3 เข้ามาdup2()
syscall ช่วยให้เราสามารถทำซ้ำ descriptor ไฟล์ทำให้พวกเขาอ้างถึงสถานที่เดียวกันได้อย่างมีประสิทธิภาพ ตัวอธิบายไฟล์ของกระบวนการที่มีการควบคุมเทอร์มินัลต่ออยู่จริง ๆ แล้วชี้ไปที่อุปกรณ์เทอร์มินัลเฉพาะ สิ่งนี้ชัดเจนถ้าคุณทำ
$ ls -l /proc/self/fd
total 0
lrwx------ 1 user1 user1 64 Aug 20 10:30 0 -> /dev/pts/5
lrwx------ 1 user1 user1 64 Aug 20 10:30 1 -> /dev/pts/5
lrwx------ 1 user1 user1 64 Aug 20 10:30 2 -> /dev/pts/5
lr-x------ 1 user1 user1 64 Aug 20 10:30 3 -> /proc/6424/fd
/dev/pts/5
อุปกรณ์เทอร์มินัลหลอกของฉันอยู่ที่ไหน ดังนั้นหากเราสามารถบันทึกปลายทางนี้เรายังสามารถเขียนกล่อง TUI ลงบนหน้าจอเทอร์มินัล นั่นคือสิ่งที่exec 3>&1
ทำ เมื่อคุณเรียกคำสั่งที่มีการเปลี่ยนเส้นทางcommand > /dev/null
เชลล์จะส่งผ่านตัวอธิบายไฟล์ stdout จากนั้นใช้dup2()
เพื่อเขียนไฟล์ descriptor /dev/null
นั้น exec
คำสั่งดำเนินการสิ่งที่คล้ายกับdup2()
อธิบายไฟล์สำหรับเซสชันเปลือกทั้งหมดจึงทำให้สืบทอดคำสั่งเปลี่ยนเส้นทางแล้วอธิบายไฟล์ exec 3>&1
เช่นเดียวกันกับ ตัวอธิบายไฟล์3
จะอ้างถึง / ชี้ไปที่เทอร์มินัลการควบคุมและคำสั่งใด ๆ ที่ทำงานในเชลล์เซสชั่นนั้นจะรู้เกี่ยวกับมัน
ดังนั้นเมื่อresult=$(dialog --inputbox test 0 0 2>&1 1>&3);
เกิดขึ้นเชลล์จะสร้างไพพ์สำหรับไดอะล็อกเพื่อเขียน แต่ยัง2>&1
จะทำให้ไฟล์ descriptor ของคำสั่ง 2 ถูกทำซ้ำไปยัง descriptor ไฟล์เขียนของไพพ์นั้น ในขณะที่ file descriptor 1 จะถูกทำซ้ำไปที่ 3 ซึ่งจะทำให้ file descriptor 1 ยังคงอ้างถึงเทอร์มินัลการควบคุมและกล่องโต้ตอบ TUI จะปรากฏขึ้นบนหน้าจอ
/dev/tty
ตอนนี้มีจริงสั้นมือสำหรับสถานีควบคุมปัจจุบันของกระบวนการซึ่งเป็น ดังนั้นวิธีการแก้ปัญหาสามารถลดความซับซ้อนโดยไม่ต้องใช้ตัวอธิบายไฟล์เพียงเข้าไปใน:
result=$(dialog --inputbox test 0 0 2>&1 1>/dev/tty);
echo "$result"
สิ่งสำคัญที่ควรจำ:
- file descriptors สืบทอดมาจาก shell โดยแต่ละคำสั่ง
- การทดแทนคำสั่งถูกนำไปใช้เป็นไพพ์
- ตัวอธิบายไฟล์ที่ซ้ำกันจะอ้างถึงตำแหน่งเดียวกันกับที่เป็นต้นฉบับ แต่เราสามารถจัดการแต่ละตัวให้คำอธิบายไฟล์แยกจากกัน
ดูสิ่งนี้ด้วย
mktemp
คำสั่งเพื่อสร้างไฟล์ชั่วคราว