Mengapa $ ls > ls.out
'ls.out' dimasukkan ke dalam daftar nama file di direktori saat ini? Mengapa ini dipilih? Kenapa tidak sebaliknya?
ls > ../ls.out
Mengapa $ ls > ls.out
'ls.out' dimasukkan ke dalam daftar nama file di direktori saat ini? Mengapa ini dipilih? Kenapa tidak sebaliknya?
ls > ../ls.out
Jawaban:
Ketika mengevaluasi perintah, >
pengalihan diselesaikan terlebih dahulu: sehingga pada saat ls
berjalan 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 ls
jalankan sebelum ls.out
dibuat:
$ 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)
sponge
menulis ke file hanya ketika sisa pipa telah selesai dieksekusi, jadi ls
jalankan sebelum ls.out
dibuat ( sponge
disediakan dengan moreutils
paket):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(berfungsi untuk ls > ls.out
kasus khusus)
Ekspansi nama file dilakukan sebelum pengalihan diselesaikan, sehingga ls
akan 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.
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 stdin
dengan 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?
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.out
kita bisa melihat sorotan berikut:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Ini terbuka ls.out
sebagai 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.out
dan 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 sponge
dan itu tidak akan terlihat dari perintah melanjutkan. Kami masih memengaruhi deskriptor file, tetapi tidak dengan cara sistem file yang terlihat.
ls | sponge ls.out
Ada juga artikel yang bagus tentang Implementasi pengalihan dan operator pipa di shell . Yang menunjukkan bagaimana pengalihan dapat diterapkan sehingga $ ls > ls.out
dapat 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");
}
}