Apa perbedaan antara "Redirection" dan "Pipe"?

205

Pertanyaan ini mungkin terdengar agak bodoh, tetapi saya tidak bisa benar-benar melihat perbedaan antara pengalihan dan pipa.

Redirection digunakan untuk mengarahkan stdout / stdin / stderr, misalnya ls > log.txt.

Pipa digunakan untuk memberikan output dari suatu perintah sebagai input ke perintah lain, misalnya ls | grep file.txt.

Tetapi mengapa ada dua operator untuk hal yang sama?

Mengapa tidak hanya menulis ls > grepuntuk meneruskan output, bukankah ini juga semacam pengalihan? Apa yang saya lewatkan?

John Threepwood
sumber

Jawaban:

224

Pipa digunakan untuk meneruskan output ke program atau utilitas lain .

Redirect digunakan untuk meneruskan output ke file atau stream .

Contoh: thing1 > thing2vs.thing1 | thing2

thing1 > thing2

  1. Shell Anda akan menjalankan program bernama thing1
  2. Segala sesuatu yang thing1keluaran akan ditempatkan di file yang disebut thing2. (Catatan - jika thing2ada, itu akan ditimpa)

Jika Anda ingin meneruskan output dari program thing1ke program yang disebut thing2, Anda dapat melakukan hal berikut:

thing1 > temp_file && thing2 < temp_file

yang akan

  1. jalankan program bernama thing1
  2. simpan output ke dalam file bernama temp_file
  3. menjalankan program bernama thing2, berpura-pura bahwa orang di keyboard mengetik konten temp_filesebagai input.

Namun, itu kikuk, sehingga mereka membuat pipa sebagai cara yang lebih sederhana untuk melakukannya. thing1 | thing2melakukan hal yang sama denganthing1 > temp_file && thing2 < temp_file

EDIT untuk memberikan rincian lebih lanjut untuk ditanyakan dalam komentar:

Jika >dicoba menjadi "pass to program" dan "write to file", ini dapat menyebabkan masalah di kedua arah.

Contoh pertama: Anda mencoba menulis ke file. Sudah ada file dengan nama yang ingin Anda timpa. Namun, file tersebut dapat dieksekusi. Agaknya, ia akan mencoba untuk mengeksekusi file ini, melewati input. Anda harus melakukan sesuatu seperti menulis keluaran ke nama file baru, lalu mengganti nama file.

Contoh kedua: Seperti yang ditunjukkan Florian Diesch, bagaimana jika ada perintah lain di sistem dengan nama yang sama (yang ada di jalur eksekusi). Jika Anda bermaksud membuat file dengan nama itu di folder Anda saat ini, Anda akan mandek.

Ketiga: jika Anda salah ketik perintah, itu tidak akan memperingatkan Anda bahwa perintah itu tidak ada. Saat ini, jika Anda mengetiknya ls | gerp log.txtakan memberi tahu Anda bash: gerp: command not found. Jika >maksud keduanya, itu hanya akan membuat file baru untuk Anda (maka peringatkan itu tidak tahu apa yang harus dilakukan dengan log.txt).

David Oneill
sumber
Terima kasih. Anda mengatakan thing1 > temp_file && thing2 < temp_fileuntuk lebih mudah menggunakan pipa. Tetapi mengapa tidak menggunakan kembali >operator untuk melakukan ini, misalnya thing1 > thing2untuk perintah thing1dan thing2? Mengapa ada operator tambahan |?
John Threepwood
1
"Ambil output dan tulis ke file" adalah tindakan yang berbeda dari "Ambil output dan berikan ke program yang berbeda". Saya akan mengedit lebih banyak pemikiran ke dalam jawaban saya ...
David Oneill
1
@JohnThreepwood Mereka memiliki arti yang berbeda. Bagaimana jika saya ingin mengarahkan sesuatu ke file bernama less, misalnya? thing | lessdan thing > lesssangat berbeda, karena mereka melakukan hal yang berbeda. Apa yang Anda usulkan akan menciptakan ambiguitas.
Darkhogg
Apakah akurat untuk mengatakan bahwa "thing1> temp_file" hanyalah gula sintaksis untuk "thing1 | tee temp_file"? Sejak mencari tahu tentang tee saya hampir tidak pernah menggunakan arahan ulang.
Sridhar Sarnobat
2
@ Sridhar-Sarnobat no, teeperintahnya melakukan sesuatu yang berbeda. teemenulis output ke layar ( stdout) dan file. Redirect tidak hanya file.
David Oneill
22

Jika maknanya foo > barakan tergantung pada apakah ada perintah bernama baryang akan membuat menggunakan pengalihan jauh lebih sulit dan lebih banyak kesalahan cenderung: Setiap kali saya ingin mengarahkan ke file saya pertama-tama harus memeriksa apakah ada perintah bernama seperti file tujuan saya.

Florian Diesch
sumber
Ini akan menjadi masalah hanya jika Anda menulis bardi direktori yang merupakan bagian dari $PATHvariabel env Anda . Jika Anda berada di sesuatu seperti / bin, maka ot bisa menjadi masalah. Tetapi bahkan kemudian, barharus memiliki set izin yang dapat dieksekusi, sehingga shell memeriksa tidak hanya untuk menemukan executable bartetapi benar-benar dapat mengeksekusinya. Dan jika masalah terkait dengan menimpa file yang ada, nocloberopsi shell harus mencegah menimpa file yang ada dalam pengalihan.
Sergiy Kolodyazhnyy
13

Dari Buku Pegangan Administrasi Sistem Unix dan Linux:

Pengalihan

Shell mengartikan simbol <,>, dan >> sebagai instruksi untuk mengubah rute input atau output perintah ke atau dari file .

Pipa

Untuk menghubungkan STDOUT dari satu perintah ke STDIN yang lain, gunakan | simbol, umumnya dikenal sebagai pipa.

Jadi interpretasi saya adalah: Jika itu perintah ke perintah, gunakan pipa. Jika Anda menghasilkan ke atau dari file, gunakan redirect.

Tuan Terserah
sumber
12

Ada perbedaan penting antara kedua operator:

  1. ls > log.txt -> Perintah ini mengirimkan output ke file log.txt.

  2. ls | grep file.txt-> Perintah ini mengirimkan output dari perintah ls to grep melalui penggunaan pipe ( |), dan perintah grep mencari file.txt di input yang disediakan oleh perintah sebelumnya.

Jika Anda harus melakukan tugas yang sama menggunakan skenario pertama, maka itu akan menjadi:

ls > log.txt; grep 'file.txt' log.txt

Jadi pipa (with |) digunakan untuk mengirim output ke perintah lain, sedangkan redirection (with >) digunakan untuk mengarahkan output ke beberapa file.

Ankit
sumber
3

Ada perbedaan sintaksis yang besar antara keduanya:

  1. Redirect adalah argumen ke suatu program
  2. Sebuah pipa memisahkan dua perintah

Anda bisa memikirkan pengalihan seperti ini: cat [<infile] [>outfile]. Ini menyiratkan pesanan tidak masalah: cat <infile >outfilesama dengan cat >outfile <infile. Anda bahkan dapat mencampur arahan ulang dengan argumen lain: cat >outfile <infile -bdan cat <infile -b >outfilekeduanya baik-baik saja. Anda juga dapat string bersama lebih dari satu input atau output (input akan dibaca berurutan dan semua output akan ditulis ke setiap file output): cat >outfile1 >outfile2 <infile1 <infile2. Target atau sumber pengalihan dapat berupa nama file atau nama aliran (seperti & 1, setidaknya dalam bash).

Tapi pipa benar-benar memisahkan satu perintah dari perintah lain, Anda tidak dapat mencampurnya dengan argumen:

[command1] | [command2]

Pipa mengambil semua yang ditulis ke output standar dari command1 dan mengirimkannya ke input standar dari command2.

Anda juga dapat menggabungkan perpipaan dan pengalihan. Sebagai contoh:

cat <infile >outfile | cat <infile2 >outfile2

Yang pertama catakan membaca baris dari infile, lalu secara bersamaan menulis setiap baris ke outfile dan mengirimkannya ke baris kedua cat.

Dalam yang kedua cat, input standar pertama kali dibaca dari pipa (isi infile), kemudian membaca dari infile2, menulis setiap baris menjadi outfile2. Setelah menjalankan ini, outfile akan menjadi salinan infile, dan outfile2 akan berisi infile diikuti oleh infile2.

Akhirnya, Anda benar-benar melakukan sesuatu yang sangat mirip dengan contoh Anda menggunakan pengalihan "di sini string" (khusus keluarga) dan backticks:

grep blah <<<`ls`

akan memberikan hasil yang sama dengan

ls | grep blah

Tapi saya pikir versi pengalihan pertama akan membaca semua output ls menjadi buffer (dalam memori), dan kemudian memberi makan buffer itu untuk menangkap satu baris pada satu waktu, sedangkan versi piped akan mengambil setiap baris dari ls saat muncul, dan melewati garis itu untuk menangkap.

pengguna319857
sumber
1
Nitpick: urutan masalah dalam pengalihan jika Anda mengalihkan satu fd ke yang lain: echo yes 1>&2 2>/tmp/blah; wc -l /tmp/blah; echo yes 2>/tmp/blah 1>&2; wc -l /tmp/blahSelanjutnya, pengalihan ke file hanya akan menggunakan pengalihan terakhir. echo yes >/tmp/blah >/tmp/blah2hanya akan menulis /tmp/blah2.
muru
2
Redirect sebenarnya bukan argumen untuk program. Program tidak akan tahu atau peduli ke mana outputnya (atau input berasal). Itu hanya cara memberitahu bash bagaimana mengatur sesuatu sebelum menjalankan program.
Alois Mahdal
3

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 straceperintah. 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 execperintah bawaan (lihat ini dan ini ), jadi jika Anda melakukan exec > output.txtsetiap perintah akan menulis output.txtsejak 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" > filedan echo "TEST" >> filekeduanya 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; aptmisalnya, 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:

  1. 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/nullangka 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 dfberjalan, tetapi sebelum itu akan terbuka /dev/nullsebagai 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.txtdan file2.txt, dan ketika Anda melakukannya, cp file1.txt file2.txtAnda 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, bashakan 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 /binatau 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 pipefddua 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', grepakan mendapatkan salinan pipefd[0]dan dfakan mendapatkan salinan pipefd[1]. Tapi bagaimana caranya ? Tentu saja, dengan keajaiban dup2()syscall. Karena dfdalam contoh kita, katakanlah pipefd[1]memiliki # 4, jadi shell akan membuat anak, lakukan dup2(4,1)(ingat cpcontoh saya ?), Dan kemudian lakukan execve()untuk benar-benar berjalan df. Tentu saja,dfakan 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 bashtidak |&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 progdapat mendeteksi jika saya melakukan cat file.txt | progatau 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:

Sergiy Kolodyazhnyy
sumber
2

Untuk menambah jawaban lain, ada perbedaan semantik yang halus juga - mis. Pipa ditutup lebih mudah daripada pengalihan:

seq 5 | (head -n1; head -n1)                # just 1
seq 5 > tmp5; (head -n1; head -n1) < tmp5   # 1 and 2
seq 5 | (read LINE; echo $LINE; head -n1)   # 1 and 2

Pada contoh pertama, ketika panggilan pertama headselesai, ia menutup pipa, dan seqberakhir, jadi tidak ada input yang tersedia untuk yang kedua head.

Dalam contoh kedua, head mengkonsumsi baris pertama, tetapi ketika itu menutup sendiri stdin pipanya , file tetap terbuka untuk panggilan berikutnya untuk digunakan.

Contoh ketiga menunjukkan bahwa jika kita gunakan readuntuk menghindari menutup pipa itu masih tersedia dalam subproses.

Jadi "aliran" adalah hal yang kita singkirkan data (stdin dll), dan sama dalam kedua kasus, tetapi pipa menghubungkan aliran dari dua proses, di mana pengalihan menghubungkan aliran antara proses dan file, sehingga Anda dapat melihat sumber persamaan dan perbedaan.

PS Jika Anda penasaran dan / atau terkejut dengan contoh-contoh itu seperti saya, Anda bisa menggali lebih dalam menggunakan trapuntuk melihat bagaimana proses menyelesaikan, Misalnya:

(trap 'echo seq EXITed >&2' EXIT; seq 5) | (trap 'echo all done' EXIT; (trap 'echo first head exited' EXIT; head -n1)
echo '.'
(trap 'echo second head exited' EXIT; head -n1))

Terkadang proses pertama ditutup sebelum 1dicetak, kadang-kadang sesudahnya.

Saya juga menemukan itu menarik untuk digunakan exec <&-untuk menutup aliran dari pengalihan untuk memperkirakan perilaku pipa (meskipun dengan kesalahan):

seq 5 > tmp5
(trap 'echo all done' EXIT
(trap 'echo first head exited' EXIT; head -n1)
echo '.'
exec <&-
(trap 'echo second head exited' EXIT; head -n1)) < tmp5`
Julian de Bhal
sumber
"ketika panggilan pertama untuk selesai, itu menutup pipa" Ini sebenarnya tidak akurat karena dua alasan. Satu, (head -n1; head -n1) adalah subkulit dengan dua perintah, yang masing-masing mewarisi ujung baca pipa sebagai deskriptor 0, dan dengan demikian subkulit DAN setiap perintah memiliki deskriptor file yang terbuka. Alasan kedua, Anda dapat melihat bahwa dengan strace -f bash -c 'seq 5 | (head -n1; head -n1) '. Jadi kepala pertama hanya menutup salinan deskriptor file
Sergiy Kolodyazhnyy
Contoh ketiga juga tidak akurat, karena readhanya mengkonsumsi baris pertama (yaitu satu byte untuk 1dan baris baru). seqmengirim total 10 byte (5 angka dan 5 baris baru). Jadi ada 8 byte yang tersisa di buffer pipa, dan itulah sebabnya kedua headberfungsi - ada data yang masih tersedia di buffer pipa. Btw, kepala keluar hanya jika ada 0 byte dibaca, agak seperti dihead /dev/null
Sergiy Kolodyazhnyy
Terimakasih atas klarifikasinya. Apakah saya mengerti benar bahwa pada seq 5 | (head -n1; head -n1)panggilan pertama mengosongkan pipa, sehingga masih ada dalam keadaan terbuka tetapi tanpa data untuk panggilan kedua head? Jadi perbedaan perilaku antara pipa dan pengalihan adalah karena kepala menarik semua data dari pipa, tetapi hanya 2 baris yang keluar dari pegangan file?
Julian de Bhal
Itu benar. Dan itu adalah sesuatu yang bisa dilihat dengan straceperintah yang saya berikan di komentar pertama. Dengan redirection, file tmp ada pada disk yang membuatnya dapat dicari (karena mereka menggunakan lseek()syscall - perintah dapat melompati file dari byte pertama hingga yang terakhir yang mereka inginkan. Tetapi pipa berurutan dan tidak dapat dicari. Jadi satu-satunya cara bagi kepala untuk melakukan nya tugasnya adalah untuk membaca semuanya terlebih dahulu, atau jika file besar - petakan beberapa ke RAM melalui mmap()panggilan. Saya pernah melakukannya sendiri taildengan Python, dan mengalami masalah yang sama persis.
Sergiy Kolodyazhnyy
Penting juga untuk diingat bahwa ujung baca pipa (file descriptor) diberikan kepada subshell terlebih dahulu (...), dan subshell akan membuat salinan stdin sendiri untuk setiap perintah di dalamnya (...). Jadi mereka secara teknis membaca dari objek yang sama. Pertama head berpikir itu membaca dari stdin itu sendiri. Kedua headmengira ia memiliki stdin sendiri. Tetapi pada kenyataannya fd # 1 (stdin) mereka hanyalah salinan dari fd yang sama, yang dibaca ujung pipa. Juga, saya sudah mengirim jawaban, jadi mungkin itu akan membantu memperjelas beberapa hal.
Sergiy Kolodyazhnyy
1

Saya mengalami masalah dengan ini di C hari ini. Pada dasarnya Pipe memiliki semantik berbeda untuk dialihkan juga, bahkan ketika dikirim ke stdin. Sungguh saya pikir mengingat perbedaan, pipa harus pergi ke tempat lain selain stdin, sehingga stdindan sebut saja stdpipe(untuk membuat diferensial sewenang-wenang) dapat ditangani dengan cara yang berbeda.

Pertimbangkan ini. Ketika piping satu output program ke yang lain fstattampaknya mengembalikan nol sebagai st_sizemeskipun ls -lha /proc/{PID}/fdmenunjukkan bahwa ada file. Saat mengarahkan file, ini bukan masalahnya (setidaknya pada debian wheezy, stretchdan jessievanilla dan ubuntu 14.04, 16.04vanilla.

Jika Anda cat /proc/{PID}/fd/0dengan pengalihan Anda akan dapat mengulang membaca sebanyak yang Anda suka. Jika Anda melakukan ini dengan sebuah pipa Anda akan melihat bahwa kedua kalinya Anda menjalankan tugas secara berurutan, Anda tidak mendapatkan hasil yang sama.

MrMesees
sumber