Mengapa 'ls> ls.out' menyebabkan 'ls.out' dimasukkan dalam daftar nama?

26

Mengapa $ ls > ls.out'ls.out' dimasukkan ke dalam daftar nama file di direktori saat ini? Mengapa ini dipilih? Kenapa tidak sebaliknya?

Edward Torvalds
sumber
3
mungkin karena pertama kali membuat file ls.out dan kemudian menulis output ke sana
Dimitri Podborski
1
Jika Anda ingin menghindari penyertaan, Anda selalu dapat menyimpan file output di direktori yang berbeda. Misalnya Anda dapat memilih direktori induk (asalkan Anda tidak berada di root sistem file) dengan ls > ../ls.out
Elder Geek

Jawaban:

36

Ketika mengevaluasi perintah, >pengalihan diselesaikan terlebih dahulu: sehingga pada saat lsberjalan file output telah dibuat.

Ini juga merupakan alasan mengapa membaca dan menulis ke file yang sama menggunakan >pengalihan dalam perintah yang sama memotong file; pada saat perintah dijalankan file sudah terpotong:

$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$ 

Trik untuk menghindari ini:

  • <<<"$(ls)" > ls.out (berfungsi untuk perintah apa pun yang perlu dijalankan sebelum pengalihan diselesaikan)

    Substitusi perintah dijalankan sebelum perintah luar dievaluasi, jadi lsjalankan sebelum ls.outdibuat:

    $ ls
    bar  foo
    $ <<<"$(ls)" > ls.out
    $ cat ls.out 
    bar
    foo
  • ls | sponge ls.out (berfungsi untuk perintah apa pun yang perlu dijalankan sebelum pengalihan diselesaikan)

    spongemenulis ke file hanya ketika sisa pipa telah selesai dieksekusi, jadi lsjalankan sebelum ls.outdibuat ( spongedisediakan dengan moreutilspaket):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
  • ls * > ls.out(berfungsi untuk ls > ls.outkasus khusus)

    Ekspansi nama file dilakukan sebelum pengalihan diselesaikan, sehingga lsakan berjalan pada argumennya, yang tidak akan berisi ls.out:

    $ ls
    bar  foo
    $ ls * > ls.out
    $ cat ls.out 
    bar
    foo
    $

Tentang mengapa pengalihan diselesaikan sebelum program / skrip / apa pun dijalankan, saya tidak melihat alasan spesifik mengapa itu wajib untuk dilakukan, tetapi saya melihat dua alasan mengapa lebih baik untuk melakukannya:

  • tidak mengarahkan STDIN sebelumnya akan membuat program / skrip / apa pun yang ditahan sampai STDIN dialihkan;

  • tidak mengarahkan STDOUT sebelumnya harus membuat shell buffer keluaran program / script / apa pun hingga STDOUT dialihkan;

Jadi buang-buang waktu dalam kasus pertama dan buang-buang waktu dan memori dalam kasus kedua.

Inilah yang terjadi pada saya, saya tidak mengklaim ini adalah alasan sebenarnya; tapi saya kira semuanya, jika seseorang memiliki pilihan, mereka akan pergi dengan mengarahkan ulang sebelumnya karena alasan yang disebutkan di atas.

kos
sumber
1
Perhatikan bahwa selama pengalihan shell tidak benar-benar menyentuh data (pada pengalihan input atau pengalihan output). Itu hanya membuka file dan meneruskan deskriptor file ke program.
Peter Green
11

Dari man bash:

REDIRECTION

Sebelum perintah dieksekusi, input dan outputnya dapat dialihkan menggunakan notasi khusus yang ditafsirkan oleh shell. Redirection memungkinkan file menangani perintah digandakan, dibuka, ditutup, dibuat untuk merujuk ke file yang berbeda, dan dapat mengubah file perintah membaca dan menulis.

Kalimat pertama, menunjukkan bahwa output dibuat untuk pergi ke tempat lain selain stdindengan pengalihan tepat sebelum perintah dijalankan. Dengan demikian, agar dapat diarahkan ke file, file harus terlebih dahulu dibuat oleh shell itu sendiri.

Untuk menghindari memiliki file, saya sarankan Anda mengarahkan output ke pipa bernama pertama, dan kemudian ke file. Perhatikan penggunaan &untuk mengembalikan kontrol atas terminal ke pengguna

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

Tapi kenapa?

Pikirkan tentang ini - di mana hasilnya? Sebuah program memiliki fungsi seperti printf, sprintf, puts, yang semua secara default pergi ke stdout, tapi bisa output mereka akan pergi ke file jika file tidak ada di tempat pertama? Ini seperti air. Bisakah Anda mendapatkan segelas air tanpa meletakkan gelas di bawah faucet terlebih dahulu?

Sergiy Kolodyazhnyy
sumber
10

Saya tidak setuju dengan jawaban saat ini. File output harus dibuka sebelum perintah dijalankan atau perintah tidak akan memiliki tempat untuk menulis outputnya.

Ini karena "semuanya adalah file" di dunia kita. Output ke layar adalah SDOUT (alias file deskriptor 1). Untuk aplikasi yang menulis ke terminal, ia membuka fd1 dan menulisnya seperti file.

Ketika Anda mengarahkan output aplikasi dalam sebuah shell, Anda mengubah fd1 sehingga sebenarnya menunjuk ke file. Ketika Anda mem-pipe Anda mengubah STDOUT satu aplikasi menjadi STDIN yang lain (fd0).


Tapi itu semua baik mengatakan itu, tetapi Anda dapat dengan mudah melihat bagaimana ini bekerja strace. Ini hal yang cukup berat tetapi contoh ini cukup singkat.

strace sh -c "ls > ls.out" 2> strace.out

Di dalamnya strace.outkita bisa melihat sorotan berikut:

open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

Ini terbuka ls.outsebagai fd3. Tulis saja. Memotong (menimpa) jika ada, jika tidak menciptakan.

fcntl(1, F_DUPFD, 10)                   = 10
close(1)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0

Ini sedikit juggling. Kami shunt STDOUT (fd1) ke fd10 dan menutupnya. Ini karena kami tidak mengeluarkan apa pun ke STDOUT yang asli dengan perintah ini. Itu selesai dengan menduplikasi pegangan tulis ke ls.outdan menutup yang asli.

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

Ini dia mencari executable. Sebuah pelajaran mungkin untuk tidak memiliki jalan yang panjang;)

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

Kemudian perintah dijalankan dan orang tua menunggu. Selama operasi ini setiap STDOUT akan benar-benar dipetakan ke pegangan file terbuka ls.out. Ketika masalah anak SIGCHLD, ini memberitahu orang tua prosesnya selesai dan itu dapat dilanjutkan. Itu berakhir dengan sedikit lebih juggling dan penutupan ls.out.

Mengapa ada begitu banyak juggling? Tidak, saya juga tidak sepenuhnya yakin.


Tentu saja Anda dapat mengubah perilaku ini. Anda dapat buffer ke memori dengan sesuatu seperti spongedan itu tidak akan terlihat dari perintah melanjutkan. Kami masih memengaruhi deskriptor file, tetapi tidak dengan cara sistem file yang terlihat.

ls | sponge ls.out
Oli
sumber
6

Ada juga artikel yang bagus tentang Implementasi pengalihan dan operator pipa di shell . Yang menunjukkan bagaimana pengalihan dapat diterapkan sehingga $ ls > ls.outdapat terlihat seperti:

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"); 
    }
}
Dimitri Podborski
sumber