ทำไม 'ls> ls.out' ทำให้ 'ls.out' รวมอยู่ในรายการชื่อ?


26

เหตุใดจึง$ ls > ls.outทำให้ 'ls.out' รวมอยู่ในรายการชื่อไฟล์ในไดเรกทอรีปัจจุบัน ทำไมสิ่งนี้จึงถูกเลือกให้เป็น ทำไมไม่เป็นอย่างอื่น?


3
อาจเป็นเพราะมันสร้างไฟล์ ls.out ก่อนแล้วจึงเขียนเอาต์พุตไปยังมัน
Dimitri Podborski

1
หากคุณต้องการหลีกเลี่ยงการรวมคุณสามารถจัดเก็บไฟล์ที่ส่งออกในไดเรกทอรีที่แตกต่างกัน ตัวอย่างเช่นคุณสามารถเลือกไดเรกทอรีหลัก (หากคุณไม่ได้อยู่ที่รูทของระบบไฟล์) ด้วย ls > ../ls.out
Elder Geek

คำตอบ:


36

เมื่อประเมินคำสั่งการ>เปลี่ยนเส้นทางจะได้รับการแก้ไขก่อน: ดังนั้นเมื่อถึงเวลา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 จะถูกเปลี่ยนทิศทาง

ดังนั้นเสียเวลาในกรณีแรกและเสียเวลาและหน่วยความจำในกรณีที่สอง

นี่คือสิ่งที่เกิดขึ้นกับฉันฉันไม่ได้อ้างว่าสิ่งเหล่านี้เป็นเหตุผลที่แท้จริง แต่ฉันเดาว่าทั้งหมดถ้ามีตัวเลือกพวกเขาจะไปกับการเปลี่ยนเส้นทางมาก่อนด้วยเหตุผลดังกล่าวข้างต้น


1
โปรดทราบว่าในระหว่างการเปลี่ยนเส้นทางเชลล์ไม่ได้สัมผัสข้อมูลจริง ๆ (ทั้งการเปลี่ยนเส้นทางอินพุตหรือการเปลี่ยนเส้นทางเอาต์พุต) เพียงแค่เปิดไฟล์และส่งไฟล์ descriptor ลงไปที่โปรแกรม
Peter Green

11

จาก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แต่สามารถผลผลิตของพวกเขาจะไปหากไฟล์ไฟล์ไม่ได้อยู่ในสถานที่แรก? มันเหมือนน้ำ คุณสามารถรับน้ำหนึ่งแก้วโดยไม่ต้องวางแก้วไว้ใต้ก๊อกน้ำก่อนได้หรือไม่


10

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

นี่เป็นเพราะ"ทุกอย่างเป็นไฟล์"ในโลกของเรา ผลลัพธ์ไปที่หน้าจอคือ 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

6

นอกจากนี้ยังมีบทความที่ดีเกี่ยวกับการดำเนินงานของการเปลี่ยนเส้นทางและท่อประกอบการในเปลือก สิ่งใดที่แสดงให้เห็นว่าการเปลี่ยนเส้นทางสามารถทำได้อย่างไรจึง$ 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"); 
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.