เหตุใดจึง$ ls > ls.out
ทำให้ 'ls.out' รวมอยู่ในรายการชื่อไฟล์ในไดเรกทอรีปัจจุบัน ทำไมสิ่งนี้จึงถูกเลือกให้เป็น ทำไมไม่เป็นอย่างอื่น?
ls > ../ls.out
เหตุใดจึง$ ls > ls.out
ทำให้ 'ls.out' รวมอยู่ในรายการชื่อไฟล์ในไดเรกทอรีปัจจุบัน ทำไมสิ่งนี้จึงถูกเลือกให้เป็น ทำไมไม่เป็นอย่างอื่น?
ls > ../ls.out
คำตอบ:
เมื่อประเมินคำสั่งการ>
เปลี่ยนเส้นทางจะได้รับการแก้ไขก่อน: ดังนั้นเมื่อถึงเวลาls
ที่ไฟล์เอาต์พุตถูกสร้างขึ้นแล้ว
นี่คือเหตุผลว่าทำไมการอ่านและเขียนไปยังไฟล์เดียวกันโดยใช้การ>
เปลี่ยนเส้นทางภายในคำสั่งเดียวกันจะตัดทอนไฟล์; ตามเวลาที่คำสั่งรันไฟล์ถูกตัดทอนแล้ว:
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
เทคนิคเพื่อหลีกเลี่ยงปัญหานี้:
<<<"$(ls)" > ls.out
(ใช้ได้กับคำสั่งใด ๆ ที่จำเป็นต้องเรียกใช้ก่อนที่การเปลี่ยนเส้นทางจะได้รับการแก้ไข)
การทดแทนคำสั่งจะรันก่อนที่คำสั่ง outer จะถูกประเมินค่าดังนั้นls
จะรันก่อนที่จะls.out
ถูกสร้าง:
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(ใช้ได้กับคำสั่งใด ๆ ที่จำเป็นต้องเรียกใช้ก่อนที่การเปลี่ยนเส้นทางจะได้รับการแก้ไข)
sponge
เขียนไปยังไฟล์เมื่อส่วนที่เหลือของไพพ์เสร็จสิ้นการดำเนินการดังนั้นls
จะถูกเรียกใช้ก่อนที่จะls.out
ถูกสร้างขึ้น ( sponge
มาพร้อมกับmoreutils
แพ็คเกจ):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(ใช้สำหรับls > ls.out
กรณีเฉพาะของ)
การขยายชื่อไฟล์จะดำเนินการก่อนการเปลี่ยนเส้นทางได้รับการแก้ไขดังนั้นls
จะทำงานกับข้อโต้แย้งของมันซึ่งจะไม่มีls.out
:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
ในกรณีที่การเปลี่ยนเส้นทางได้รับการแก้ไขก่อนที่โปรแกรม / สคริปต์ / อะไรก็ตามที่ทำงานฉันไม่เห็นเหตุผลเฉพาะที่บังคับให้ทำเช่นนั้น แต่ฉันเห็นเหตุผลสองประการที่ทำให้ดีกว่า :
การไม่เปลี่ยนเส้นทาง STDIN ล่วงหน้าจะทำให้โปรแกรม / สคริปต์ / สิ่งที่ถูกพักไว้จนกว่า STDIN จะถูกเปลี่ยนเส้นทาง;
การไม่เปลี่ยนทิศทาง STDOUT ล่วงหน้าควรทำให้เชลล์บัฟเฟอร์เป็นโปรแกรม / สคริปต์ / / เอาต์พุตใด ๆ จนกว่า STDOUT จะถูกเปลี่ยนทิศทาง
ดังนั้นเสียเวลาในกรณีแรกและเสียเวลาและหน่วยความจำในกรณีที่สอง
นี่คือสิ่งที่เกิดขึ้นกับฉันฉันไม่ได้อ้างว่าสิ่งเหล่านี้เป็นเหตุผลที่แท้จริง แต่ฉันเดาว่าทั้งหมดถ้ามีตัวเลือกพวกเขาจะไปกับการเปลี่ยนเส้นทางมาก่อนด้วยเหตุผลดังกล่าวข้างต้น
จากman bash
:
การเปลี่ยนเส้นทาง
ก่อนดำเนินการคำสั่งอินพุตและเอาต์พุตอาจถูกเปลี่ยนทิศทางโดยใช้สัญลักษณ์พิเศษที่ตีความโดยเชลล์ การเปลี่ยนเส้นทางช่วยให้การจัดการไฟล์ของคำสั่งทำซ้ำเปิดปิดทำเพื่ออ้างถึงไฟล์ต่าง ๆ และสามารถเปลี่ยนไฟล์ที่คำสั่งอ่านและเขียนเป็น
ประโยคแรกแสดงให้เห็นว่าเอาต์พุตถูกสร้างขึ้นเพื่อไปที่อื่นนอกเหนือstdin
จากการเปลี่ยนทิศทางก่อนที่จะดำเนินการคำสั่ง ดังนั้นเพื่อที่จะเปลี่ยนเส้นทางไปยังไฟล์ไฟล์จะต้องถูกสร้างขึ้นโดยเชลล์เองก่อน
เพื่อหลีกเลี่ยงการมีไฟล์ฉันขอแนะนำให้คุณเปลี่ยนเส้นทางไปยัง pipe ที่มีชื่อก่อนจากนั้นจึงไปยังไฟล์ หมายเหตุการใช้&
เพื่อส่งคืนการควบคุมเทอร์มินัลกับผู้ใช้
DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out
แต่ทำไม
คิดเกี่ยวกับสิ่งนี้ - ผลลัพธ์จะอยู่ที่ไหน โปรแกรมมีฟังก์ชั่นเช่นprintf
, sprintf
, puts
ซึ่งทั้งหมดเป็นค่าเริ่มต้นการเดินทางไปstdout
แต่สามารถผลผลิตของพวกเขาจะไปหากไฟล์ไฟล์ไม่ได้อยู่ในสถานที่แรก? มันเหมือนน้ำ คุณสามารถรับน้ำหนึ่งแก้วโดยไม่ต้องวางแก้วไว้ใต้ก๊อกน้ำก่อนได้หรือไม่
ฉันไม่เห็นด้วยกับคำตอบปัจจุบัน ไฟล์เอาต์พุตต้องถูกเปิดก่อนที่คำสั่งจะรันหรือคำสั่งจะไม่มีที่ใดก็ได้เพื่อเขียนเอาต์พุต
นี่เป็นเพราะ"ทุกอย่างเป็นไฟล์"ในโลกของเรา ผลลัพธ์ไปที่หน้าจอคือ SDOUT (aka file descriptor 1) เพื่อให้แอปพลิเคชันเขียนไปยังเทอร์มินัลจะเปิด fd1 และเขียนลงในไฟล์
เมื่อคุณเปลี่ยนเส้นทางเอาต์พุตของแอปพลิเคชั่นในเชลล์คุณกำลังแก้ไข fd1 ดังนั้นมันจึงชี้ไปที่ไฟล์ เมื่อคุณไพพ์คุณเปลี่ยน STDOUT ของแอปพลิเคชันหนึ่งให้เป็น STDIN ของแอปพลิเคชันอื่น (fd0)
แต่มันก็เป็นการดีที่บอกว่า แต่คุณสามารถดูได้อย่างง่ายดายว่ามันทำงานstrace
อย่างไร มันค่อนข้างหนัก แต่ตัวอย่างนี้ค่อนข้างสั้น
strace sh -c "ls > ls.out" 2> strace.out
ภายในstrace.out
เราสามารถดูไฮไลท์ต่อไปนี้:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
นี้จะเปิดเป็นls.out
fd3
เขียนเท่านั้น ตัด (เขียนทับ) หากมีอยู่มิฉะนั้นจะสร้าง
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
นี่เป็นการเล่นปาหี่เล็กน้อย เราแบ่ง STDOUT (fd1) ออกเป็น fd10 และปิด นี่เป็นเพราะเราไม่ได้แสดงผลอะไรกับ STDOUT จริงด้วยคำสั่งนี้ มันเสร็จสิ้นโดยการทำซ้ำการจัดการการเขียนls.out
และปิดเดิม
stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
นี่คือการค้นหาไฟล์ปฏิบัติการ บทเรียนอาจไม่มีเส้นทางยาว)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn() = 31933
dup2(10, 1) = 1
close(10) = 0
จากนั้นคำสั่งจะรันและพาเรนต์จะรอ ในระหว่างการดำเนินการนี้ STDOUT ใด ๆ ls.out
ที่จะมีแมปจริงที่จับเปิดแฟ้มบน เมื่อปัญหาย่อยSIGCHLD
นี้จะแจ้งให้กระบวนการแม่ดำเนินการเสร็จสิ้นและสามารถดำเนินการต่อได้ มันเสร็จสิ้นออกด้วยการเล่นกลเล็ก ๆ น้อย ๆ ls.out
และใกล้ของ
เหตุใดจึงมีเพื่อให้การเล่นกลมาก? ไม่ฉันไม่แน่ใจเหมือนกันทั้งหมด
แน่นอนคุณสามารถเปลี่ยนพฤติกรรมนี้ คุณสามารถบัฟเฟอร์หน่วยความจำด้วยสิ่งที่ชอบsponge
และที่จะมองไม่เห็นจากคำสั่งดำเนินการ เรายังคงส่งผลกระทบต่อ file descriptor แต่ไม่ได้อยู่ในระบบไฟล์ที่มองเห็นได้
ls | sponge ls.out
นอกจากนี้ยังมีบทความที่ดีเกี่ยวกับการดำเนินงานของการเปลี่ยนเส้นทางและท่อประกอบการในเปลือก สิ่งใดที่แสดงให้เห็นว่าการเปลี่ยนเส้นทางสามารถทำได้อย่างไรจึง$ ls > ls.out
มีลักษณะดังนี้:
main(){
close(1); // Release fd no - 1
open("ls.out", "w"); // Open a file with fd no = 1
// Child process
if (fork() == 0) {
exec("ls");
}
}