Catatan: Jawabannya mencerminkan pemahaman saya sendiri tentang mekanisme-mekanisme ini terkini, terakumulasi dalam penelitian dan membaca jawaban oleh rekan-rekan di situs ini dan unix.stackexchange.com , dan akan diperbarui seiring berjalannya waktu. Jangan ragu untuk bertanya atau menyarankan peningkatan dalam komentar. Saya juga menyarankan Anda mencoba untuk melihat bagaimana syscalls bekerja di shell dengan strace
perintah. Juga tolong jangan diintimidasi oleh gagasan internal atau syscalls - Anda tidak perlu tahu atau dapat menggunakannya untuk memahami bagaimana shell melakukan sesuatu, tetapi mereka pasti membantu memahami.
TL; DR
|
pipa tidak terkait dengan entri pada disk, oleh karena itu tidak memiliki nomor inode dari sistem file disk (tetapi memiliki inode dalam sistem file virtual pipefs dalam ruang kernel), tetapi pengalihan sering melibatkan file, yang memiliki entri disk dan oleh karena itu telah sesuai inode.
- pipa tidak
lseek()
dapat sehingga perintah tidak dapat membaca beberapa data dan kemudian mundur kembali, tetapi ketika Anda mengarahkan ulang dengan >
atau <
biasanya itu adalah file yang lseek()
dapat objek, jadi perintah dapat menavigasi bagaimanapun mereka silakan.
- pengalihan adalah manipulasi pada deskriptor file, yang bisa banyak; pipa hanya memiliki dua deskriptor file - satu untuk perintah kiri dan satu untuk perintah kanan
- pengalihan pada aliran standar dan pipa keduanya buffered.
- pipa hampir selalu melibatkan forking dan karena itu pasangan proses terlibat; pengalihan - tidak selalu, meskipun dalam kedua kasus, deskriptor file yang dihasilkan diwarisi oleh sub-proses.
- pipa selalu menghubungkan deskriptor file (sepasang), pengalihan - baik menggunakan pathname atau deskriptor file.
- pipa adalah metode Komunikasi Antar-Proses, sedangkan pengalihan hanya manipulasi pada file terbuka atau objek seperti file
- keduanya menggunakan
dup2()
syscalls di bawah tenda untuk memberikan salinan deskriptor file, tempat aliran data aktual terjadi.
- pengalihan dapat diterapkan "secara global" dengan
exec
perintah bawaan (lihat ini dan ini ), jadi jika Anda melakukan exec > output.txt
setiap perintah akan menulis output.txt
sejak saat itu. |
pipa hanya diterapkan untuk perintah saat ini (yang berarti perintah sederhana atau perintah subkulit suka seq 5 | (head -n1; head -n2)
atau majemuk.
Ketika pengalihan dilakukan pada file, hal-hal seperti echo "TEST" > file
dan echo "TEST" >> file
keduanya menggunakan open()
syscall pada file itu ( lihat juga ) dan dapatkan deskriptor file darinya untuk diteruskan dup2()
. Pipa |
hanya menggunakan pipe()
dan dup2()
syscall.
Sejauh perintah dieksekusi, pipa dan pengalihan tidak lebih dari deskriptor file - objek seperti file, yang mereka dapat tulis secara membabi buta, atau memanipulasi mereka secara internal (yang dapat menghasilkan perilaku tak terduga; apt
misalnya, cenderung bahkan tidak menulis ke stdout jika tahu ada pengalihan).
pengantar
Untuk memahami bagaimana kedua mekanisme ini berbeda, penting untuk memahami sifat esensial mereka, sejarah di balik keduanya, dan akarnya dalam bahasa pemrograman C. Faktanya, mengetahui apa itu deskriptor file, dan bagaimana dup2()
dan pipe()
system call berfungsi, juga penting lseek()
. Shell dimaksudkan sebagai cara membuat mekanisme ini abstrak bagi pengguna, tetapi menggali lebih dalam dari abstraksi membantu memahami sifat sebenarnya dari perilaku shell.
Asal-usul Pengalihan dan Pipa
Menurut artikel Dennis Ritche Prophetic Petroglyphs , pipa berasal dari memo internal tahun 1964 oleh Malcolm Douglas McIlroy , pada saat mereka sedang mengerjakan sistem operasi Multics . Mengutip:
Singkatnya, untuk menyuarakan keprihatinan terkuat saya:
- Kita harus memiliki beberapa cara untuk menghubungkan program-program seperti selang taman - sekrup di segmen lain ketika menjadi ketika menjadi perlu untuk memijat data dengan cara lain. Ini adalah cara IO juga.
Yang jelas adalah bahwa pada saat itu program mampu menulis ke disk, namun itu tidak efisien jika outputnya besar. Mengutip penjelasan Brian Kernighan dalam video Unix Pipeline :
Pertama, Anda tidak perlu menulis satu program besar besar - Anda punya program kecil yang sudah ada yang mungkin sudah melakukan bagian pekerjaan ... Yang lain adalah mungkin bahwa jumlah data yang Anda proses tidak akan cocok jika Anda menyimpannya dalam file ... karena ingat, kita kembali pada hari-hari ketika disk pada hal-hal ini memiliki, jika Anda beruntung, satu atau dua Megabyte data ... Jadi pipa tidak pernah harus instantiate seluruh output .
Dengan demikian perbedaan konseptual tampak jelas: pipa adalah mekanisme untuk membuat program berbicara satu sama lain. Pengalihan - adalah cara penulisan ke file di tingkat dasar. Dalam kedua kasus, shell membuat kedua hal ini mudah, tetapi di balik tudungnya, ada banyak hal yang terjadi.
Pergi lebih dalam: syscalls dan cara kerja internal shell
Kita mulai dengan gagasan deskriptor file . Deskriptor file pada dasarnya menggambarkan file terbuka (apakah itu file pada disk, atau dalam memori, atau file anonim), yang diwakili oleh angka integer. Dua stream data standar (stdin, stdout, stderr) adalah file deskriptor 0,1, dan 2 masing-masing. Mereka berasal dari mana ? Nah, pada perintah shell, deskriptor file diwarisi dari shell induknya. Dan memang benar secara umum untuk semua proses - proses anak mewarisi deskriptor file orang tua. Untuk daemon biasanya menutup semua deskriptor file yang diwarisi dan / atau mengarahkan ke tempat lain.
Kembali ke pengalihan. Apa itu sebenarnya Ini adalah mekanisme yang memberi tahu shell untuk menyiapkan deskriptor file untuk perintah (karena pengalihan dilakukan oleh shell sebelum perintah dijalankan), dan arahkan mereka ke tempat yang disarankan pengguna. The definisi standar output redirection adalah
[n]>word
Itu [n]
ada nomor deskriptor file. Ketika Anda melakukan echo "Something" > /dev/null
angka 1 tersirat di sana, dan echo 2> /dev/null
.
Di bawah tenda ini dilakukan dengan menduplikasi file deskriptor melalui dup2()
system call. Ayo ambil df > /dev/null
. Shell akan membuat proses anak di mana df
berjalan, tetapi sebelum itu akan terbuka /dev/null
sebagai file deskriptor # 3, dan dup2(3,1)
akan dikeluarkan, yang membuat salinan file deskriptor 3 dan salinannya adalah 1. Anda tahu bagaimana Anda memiliki dua file file1.txt
dan file2.txt
, dan ketika Anda melakukannya, cp file1.txt file2.txt
Anda akan memiliki dua file yang sama, tetapi Anda dapat memanipulasinya secara independen? Hal yang sama terjadi di sini. Seringkali Anda dapat melihat bahwa sebelum menjalankan, bash
akan dilakukan dup(1,10)
untuk membuat salinan file descriptor # 1 yang stdout
(dan salinan itu akan menjadi fd # 10) untuk mengembalikannya nanti. Penting untuk dicatat bahwa ketika Anda mempertimbangkan perintah bawaan(yang merupakan bagian dari shell itu sendiri, dan tidak memiliki file di dalam /bin
atau di tempat lain) atau perintah sederhana di shell non-interaktif , shell tidak membuat proses anak.
Dan kemudian kita memiliki hal-hal seperti [n]>&[m]
dan [n]&<[m]
. Ini adalah duplikator deskriptor file, yang mekanisme yang sama seperti dup2()
hanya sekarang ini ada di sintaksis shell, tersedia untuk pengguna.
Salah satu hal penting yang perlu diperhatikan tentang pengalihan adalah bahwa pesanan mereka tidak diperbaiki, tetapi penting untuk bagaimana shell menginterpretasikan apa yang diinginkan pengguna. Bandingkan yang berikut ini:
# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/ 3>&2 2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd
# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/ 2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
Penggunaan praktis ini dalam skrip shell dapat menjadi serbaguna:
dan banyak lainnya.
Plumbing dengan pipe()
dandup2()
Jadi bagaimana cara membuat pipa? Melalui pipe()
syscall , yang akan mengambil input array (alias daftar) yang disebut pipefd
dua item bertipe int
(integer). Kedua bilangan bulat itu adalah deskriptor file. Itu pipefd[0]
akan menjadi ujung baca pipa dan pipefd[1]
akan menjadi akhir tulis. Jadi df | grep 'foo'
, grep
akan mendapatkan salinan pipefd[0]
dan df
akan mendapatkan salinan pipefd[1]
. Tapi bagaimana caranya ? Tentu saja, dengan keajaiban dup2()
syscall. Karena df
dalam contoh kita, katakanlah pipefd[1]
memiliki # 4, jadi shell akan membuat anak, lakukan dup2(4,1)
(ingat cp
contoh saya ?), Dan kemudian lakukan execve()
untuk benar-benar berjalan df
. Tentu saja,df
akan mewarisi deskriptor file # 1, tetapi tidak akan menyadari bahwa itu tidak lagi menunjuk ke terminal, tetapi sebenarnya fd # 4, yang sebenarnya merupakan akhir dari pipa. Secara alami, hal yang sama akan terjadi dengan grep 'foo'
kecuali dengan jumlah deskriptor file yang berbeda.
Sekarang, pertanyaan menarik: bisakah kita membuat pipa yang mengarahkan ulang fd # 2 juga, bukan hanya fd # 1? Ya, sebenarnya itulah yang |&
dilakukan bash. POSIX standar memerlukan bahasa perintah shell untuk mendukung df 2>&1 | grep 'foo'
sintaks untuk tujuan itu, tetapi bash
tidak |&
juga.
Yang penting untuk dicatat adalah bahwa pipa selalu berurusan dengan deskriptor file. Ada pipa bernamaFIFO
atau bernama , yang memiliki nama file pada disk dan mari kita gunakan sebagai file, tetapi berperilaku seperti pipa. Tetapi |
jenis - jenis pipa itu adalah apa yang dikenal sebagai pipa anonim - mereka tidak memiliki nama file, karena mereka benar-benar hanya dua objek yang terhubung bersama. Fakta bahwa kita tidak berurusan dengan file juga membuat implikasi penting: pipa tidak lseek()
bisa. File, baik di memori atau di disk, bersifat statis - program dapat menggunakan lseek()
syscall untuk melompat ke byte 120, lalu kembali ke byte 10, lalu meneruskan semua jalan ke akhir. Pipa tidak statis - mereka berurutan, dan karena itu Anda tidak dapat memundurkan data yang Anda dapatkan darinyalseek()
. Inilah yang membuat beberapa program sadar jika mereka membaca dari file atau dari pipa, dan dengan demikian mereka dapat membuat penyesuaian yang diperlukan untuk kinerja yang efisien; dengan kata lain, a prog
dapat mendeteksi jika saya melakukan cat file.txt | prog
atau prog < input.txt
. Contoh nyata dari pekerjaan itu adalah ekor .
Dua properti pipa yang sangat menarik lainnya adalah mereka memiliki buffer, yang pada Linux 4096 bytes , dan mereka sebenarnya memiliki sistem file seperti yang didefinisikan dalam kode sumber Linux ! Mereka bukan hanya objek untuk mengirimkan data, mereka adalah struktur data sendiri! Faktanya, karena terdapat filesystem pipefs, yang mengelola pipa dan FIFO, pipa memiliki nomor inode pada sistem file masing-masing:
# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/ | cat
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
Di Linux, pipa bersifat uni-directional, seperti halnya pengalihan. Pada beberapa implementasi mirip Unix - ada pipa dua arah. Meskipun dengan keajaiban skrip shell, Anda dapat membuat pipa dua arah di Linux juga.
Lihat juga:
thing1 > temp_file && thing2 < temp_file
untuk lebih mudah menggunakan pipa. Tetapi mengapa tidak menggunakan kembali>
operator untuk melakukan ini, misalnyathing1 > thing2
untuk perintahthing1
danthing2
? Mengapa ada operator tambahan|
?