Bagaimana Anda bisa membedakan dua saluran pipa di Bash?

143

Bagaimana Anda bisa membedakan dua pipeline tanpa menggunakan file sementara di Bash? Katakanlah Anda memiliki dua pipa perintah:

foo | bar
baz | quux

Dan Anda ingin menemukan diffdi outputnya. Satu solusi jelas akan menjadi:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

Apakah mungkin untuk melakukannya tanpa menggunakan file sementara di Bash? Anda dapat menyingkirkan satu file sementara dengan memipet salah satu pipa ke diff:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

Tetapi Anda tidak dapat menyalurkan kedua pipa ke dalam secara bersamaan (setidaknya tidak dengan cara yang jelas). Apakah ada beberapa trik pintar yang terlibat /dev/fduntuk melakukan ini tanpa menggunakan file sementara?

Adam Rosenfield
sumber

Jawaban:

146

Satu baris dengan 2 file tmp (bukan yang Anda inginkan) adalah:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

Dengan bash , Anda dapat mencoba:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

Versi ke-2 akan lebih jelas mengingatkan Anda input mana yang, dengan menunjukkan
-- /dev/stdinvs. ++ /dev/fd/63atau sesuatu, bukan dua fds bernomor.


Bahkan pipa bernama tidak akan muncul di sistem file, setidaknya pada OS di mana bash dapat mengimplementasikan substitusi proses dengan menggunakan nama file seperti /dev/fd/63untuk mendapatkan nama file yang dapat dibuka dan dibaca oleh perintah untuk benar-benar membaca dari deskriptor file yang sudah terbuka yang diatur oleh bash set sebelum menjalankan perintah. (Yaitu bash menggunakan pipe(2)sebelum garpu, dan kemudian dup2untuk mengalihkan dari output quuxke deskripsi file input untukdiff , pada fd 63.)

Pada sistem tanpa "ajaib" /dev/fdatau /proc/self/fd, bash mungkin menggunakan pipa bernama untuk mengimplementasikan substitusi proses, tetapi setidaknya akan mengelolanya sendiri, tidak seperti file sementara, dan data Anda tidak akan ditulis ke sistem file.

Anda dapat memeriksa bagaimana bash mengimplementasikan proses substitusi dengan echo <(true)mencetak nama file alih-alih membacanya. Mencetak /dev/fd/63pada sistem Linux yang khas. Atau untuk detail lebih lanjut tentang apa tepatnya panggilan sistem yang digunakan bash, perintah ini pada sistem Linux akan melacak panggilan sistem dan file-deskriptor panggilan

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

Tanpa bash, Anda bisa membuat pipa bernama . Gunakan -untuk memberi tahu diffuntuk membaca satu input dari STDIN, dan menggunakan pipa bernama sebagai yang lain:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

Perhatikan bahwa Anda hanya dapat menyalurkan satu output ke beberapa input dengan perintah tee:

ls *.txt | tee /dev/tty txtlist.txt 

Perintah di atas menampilkan output dari ls * .txt ke terminal dan outputnya ke file teks txtlist.txt.

Tetapi dengan substitusi proses, Anda dapat menggunakan teeuntuk memberi makan data yang sama ke beberapa saluran pipa:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
VONC
sumber
5
bahkan tanpa bash, Anda dapat menggunakan fifo sementaramkfifo a; cmd >a& cmd2|diff a -; rm a
mudah
Anda dapat menggunakan pipa biasa untuk salah satu args: pipeline1 | diff -u - <(pipeline2). Maka output akan lebih jelas mengingatkan Anda input mana yang, dengan menunjukkan -- /dev/stdinvs. ++ /dev/fd/67atau sesuatu, bukan dua fds bernomor.
Peter Cordes
subtitusi proses ( foo <( pipe )) tidak mengubah sistem file. Pipa itu anonim ; tidak memiliki nama di sistem file . Shell menggunakan pipesystem call untuk membuatnya, bukan mkfifo. Gunakan strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'untuk melacak panggilan sistem dan file-deskriptor sistem jika Anda ingin melihatnya sendiri. Di Linux, /dev/fd/63adalah bagian dari /procsistem file virtual; secara otomatis memiliki entri untuk setiap deskriptor file, dan itu bukan salinan konten. Jadi Anda tidak dapat menyebutnya "file sementara" kecuali foo 3<bar.txtdiperhitungkan
Peter Cordes
@PeterCordes Poin bagus. Saya telah memasukkan komentar Anda dalam jawaban untuk lebih banyak visibilitas.
VonC
1
@PeterCordes Saya akan memberikan editan kepada Anda: itulah yang membuat Stack Overflow menarik: siapa pun dapat "memperbaiki" jawaban.
VonC
127

Dalam bash Anda dapat menggunakan subkulit, untuk mengeksekusi pipa perintah secara terpisah, dengan melampirkan pipa dalam tanda kurung. Anda kemudian dapat mengawali ini dengan <untuk membuat pipa bernama anonim yang kemudian dapat Anda lewatkan ke diff.

Sebagai contoh:

diff <(foo | bar) <(baz | quux)

Pipa bernama anonim dikelola oleh bash sehingga mereka dibuat dan dihancurkan secara otomatis (tidak seperti file sementara).

BenM
sumber
1
Jauh lebih rinci daripada redaksi saya pada solusi yang sama - batch anonim -. +1
VonC
4
Ini disebut proses substitusi di Bash.
Franklin Yu
5

Beberapa orang yang tiba di halaman ini mungkin mencari diff baris-demi-baris, untuk yang mana commataugrep -f harus digunakan.

Satu hal yang perlu diperhatikan adalah bahwa, dalam semua contoh jawaban, perbedaan tidak akan benar-benar dimulai sampai kedua aliran selesai. Uji ini dengan misalnya:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

Jika ini merupakan masalah, Anda bisa mencoba sd (stream diff), yang tidak memerlukan pengurutan (seperti commhalnya) atau memproses substitusi seperti contoh di atas, apakah pesanan atau besarnya lebih cepat dari grep -f dan mendukung aliran yang tak terbatas.

Contoh tes yang saya usulkan akan ditulis seperti ini di sd:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

Tetapi perbedaannya adalah bahwa seq 100akan dibedakan dengan seq 10segera. Perhatikan bahwa, jika salah satu stream adalah a tail -f, diff tidak dapat dilakukan dengan substitusi proses.

Berikut adalah blogpost yang saya tulis tentang perbedaan aliran pada terminal, yang memperkenalkan sd.

mlg
sumber