Proses penggantian dan pipa

86

Saya bertanya-tanya bagaimana memahami hal berikut:

Memipiskan stdout dari perintah ke stdin yang lain adalah teknik yang kuat. Tapi, bagaimana jika Anda perlu mengirim stdout ke beberapa perintah? Di sinilah proses substitusi masuk

Dengan kata lain, dapatkah proses substitusi melakukan apa pun yang dapat dilakukan pipa?

Apa yang bisa dilakukan proses substitusi, tetapi pipa tidak bisa?

Tim
sumber

Jawaban:

134

Cara yang baik untuk menemukan perbedaan di antara mereka adalah dengan melakukan sedikit percobaan pada baris perintah. Terlepas dari kesamaan visual dalam penggunaan <karakter, ia melakukan sesuatu yang sangat berbeda dari pengalihan atau pipa.

Mari kita gunakan dateperintah untuk pengujian.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Ini adalah contoh yang tidak berguna tetapi menunjukkan bahwa catmenerima output dari datepada STDIN dan meludahkannya kembali. Hasil yang sama dapat dicapai dengan proses substitusi:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

Namun apa yang baru saja terjadi di belakang layar berbeda. Alih-alih diberi aliran STDIN, catsebenarnya melewati nama file yang harus dibuka dan dibaca. Anda dapat melihat langkah ini dengan menggunakan echoalih-alih cat.

$ echo <(date)
/proc/self/fd/11

Ketika kucing menerima nama file, itu membaca konten file untuk kami. Di sisi lain, gema hanya menunjukkan kepada kita nama file yang dilewati. Perbedaan ini menjadi lebih jelas jika Anda menambahkan lebih banyak pergantian:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

Dimungkinkan untuk menggabungkan proses substitusi (yang menghasilkan file) dan pengalihan input (yang menghubungkan file ke STDIN):

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Itu terlihat hampir sama tetapi kali ini kucing dilewatkan aliran STDIN bukan nama file. Anda dapat melihat ini dengan mencobanya dengan gema:

$ echo < <(date)
<blank>

Karena gema tidak membaca STDIN dan tidak ada argumen yang disampaikan, kami tidak mendapatkan apa-apa.

Pipa dan pengalihan input mendorong konten ke aliran STDIN. Substitusi proses menjalankan perintah, menyimpan outputnya ke file sementara khusus dan kemudian meneruskan nama file itu sebagai pengganti perintah. Perintah apa pun yang Anda gunakan memperlakukannya sebagai nama file. Perhatikan bahwa file yang dibuat bukan file biasa tetapi pipa bernama yang akan dihapus secara otomatis setelah tidak lagi diperlukan.

Caleb
sumber
Jika saya mengerti dengan benar, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 mengatakan proses substitusi membuat file sementara, bukan bernama pipa. Sejauh yang saya tahu namanya tidak membuat file sementara. Menulis ke pipa tidak pernah melibatkan penulisan ke disk: stackoverflow.com/a/6977599/788700
Adobe
Saya tahu jawaban ini sah karena menggunakan kata grok : D
aqn
2
@Adobe Anda dapat mengkonfirmasi apakah proses berkas substitusi sementara menghasilkan adalah pipa bernama dengan: [[ -p <(date) ]] && echo true. Ini menghasilkan trueketika saya menjalankannya dengan bash 4.4 atau 3.2.
De Novo
24

Saya kira Anda berbicara tentang bashatau shell canggih lainnya, karena shell posix tidak memiliki proses substitusi .

bash laporan halaman manual:

Substitusi Proses Substitusi
proses didukung pada sistem yang mendukung pipa bernama (FIFO) atau metode / dev / fd untuk penamaan file yang terbuka. Ini mengambil bentuk <(daftar) atau> (daftar). Daftar proses dijalankan dengan input atau output yang terhubung ke FIFO atau beberapa file di / dev / fd. Nama file ini diteruskan sebagai argumen ke perintah saat ini sebagai hasil dari ekspansi. Jika formulir> (daftar) digunakan, menulis ke file akan memberikan input untuk daftar. Jika formulir <(daftar) digunakan, file yang dikirimkan sebagai argumen harus dibaca untuk mendapatkan output daftar.

Jika tersedia, proses substitusi dilakukan bersamaan dengan ekspansi parameter dan variabel, substitusi perintah, dan ekspansi aritmatika.

Dengan kata lain, dan dari sudut pandang praktis, Anda dapat menggunakan ekspresi seperti berikut ini

<(commands)

sebagai nama file untuk perintah lain yang membutuhkan file sebagai parameter. Atau Anda dapat menggunakan pengalihan untuk file seperti itu:

while read line; do something; done < <(commands)

Kembali ke pertanyaan Anda, menurut saya proses substitusi dan pipa tidak memiliki banyak kesamaan.

Jika Anda ingin memasang secara berurutan output dari beberapa perintah, Anda dapat menggunakan salah satu dari formulir berikut:

(command1; command2) | command3
{ command1; command2; } | command3

tetapi Anda juga dapat menggunakan pengalihan pada substitusi proses

command3 < <(command1; command2)

akhirnya, jika command3menerima parameter file (sebagai pengganti stdin)

command3 <(command1; command2)
enzotib
sumber
jadi <() dan <<() membuat efek yang sama, kan?
solfish
@solfish: not exacllty: firse dapat digunakan di mana saja nama file diharapkan, yang kedua adalah pengalihan input untuk nama file itu
enzotib
23

Berikut adalah tiga hal yang dapat Anda lakukan dengan proses penggantian yang tidak mungkin dilakukan sebaliknya.

Beberapa input proses

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Tidak ada cara untuk melakukan ini dengan pipa.

Mempertahankan STDIN

Katakanlah Anda memiliki yang berikut ini:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

Dan Anda ingin menjalankannya secara langsung. Berikut ini gagal total. Bash sudah menggunakan STDIN untuk membaca skrip, jadi input lain tidak mungkin.

curl -o - http://example.com/script.sh | bash 

Tetapi cara ini bekerja dengan sempurna.

bash <(curl -o - http://example.com/script.sh)

Substitusi proses keluar

Perhatikan juga bahwa proses substitusi juga berfungsi sebaliknya. Jadi Anda dapat melakukan sesuatu seperti ini:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

Itu sedikit contoh yang berbelit-belit, tetapi mengirimkan stdout ke /dev/null, sementara memipatkan stderr ke skrip sed untuk mengekstrak nama file yang menampilkan galat "Izin ditolak" ditampilkan, dan kemudian mengirim MEREKA hasil ke file.

Perhatikan bahwa perintah pertama dan pengalihan stdout ada dalam tanda kurung ( subkulit ) sehingga hanya hasil dari perintah ITU yang dikirim /dev/nulldan tidak mengacaukan dengan sisa baris.

tylerl
sumber
Ini perlu dicatat bahwa dalam diffcontoh Anda mungkin ingin peduli tentang kasus di mana cdmungkin gagal: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk
"while piping stderr": bukankah intinya ini bukan pemipaan, tetapi melalui file fifo?
Gauthier
@Gauthier no; perintah diganti bukan dengan fifo tetapi dengan referensi ke deskriptor file. Jadi "echo <(echo)" harus menghasilkan sesuatu seperti "/ dev / fd / 63", yang merupakan perangkat karakter khusus yang membaca atau menulis dari nomor FD 63.
tylerl
10

Jika perintah mengambil daftar file sebagai argumen dan memproses file-file tersebut sebagai input (atau output, tetapi tidak umum), masing-masing file tersebut dapat berupa pipa bernama atau / dev / fd file semu yang disediakan secara transparan oleh proses subsitusi:

$ sort -m <(command1) <(command2) <(command3)

Ini akan "mem-pipe" output dari tiga perintah untuk disortir, karena sort dapat mengambil daftar file input pada baris perintah.

camh
sumber
1
IIRC sintaks <(perintah) adalah fitur bash-only.
Philomath
@ Philomath: Ada di ZSH juga.
Caleb
Nah, ZSH memiliki segalanya ... (atau setidaknya mencoba).
Philomath
@ Philomath: Bagaimana proses substitusi diterapkan di shell lain?
camh
4
@ Philomath <(), seperti banyak fitur shell canggih, pada awalnya merupakan fitur ksh dan diadopsi oleh bash dan zsh. psubsecara khusus fitur ikan, tidak ada hubungannya dengan POSIX.
Gilles
3

Perlu dicatat bahwa proses substitusi tidak terbatas pada formulir <(command), yang menggunakan output commandsebagai file. Itu bisa dalam bentuk >(command)yang mengumpankan file sebagai input commandjuga. Ini juga disebutkan dalam kutipan manual bash dalam jawaban @ enzotib.

Untuk date | catcontoh di atas, perintah yang menggunakan proses substitusi formulir >(command)untuk mencapai efek yang sama adalah,

date > >(cat)

Perhatikan bahwa >sebelumnya >(cat)diperlukan. Sekali lagi ini dapat digambarkan dengan jelas echoseperti pada jawaban @ Caleb.

$ echo >(cat)
/dev/fd/63

Jadi, tanpa tambahan >, date >(cat)akan sama dengan date /dev/fd/63yang akan mencetak pesan ke stderr.

Misalkan Anda memiliki program yang hanya menggunakan nama file sebagai parameter dan tidak memproses stdinatau stdout. Saya akan menggunakan skrip yang terlalu disederhanakan psub.shuntuk menggambarkan hal ini. Isi psub.shis

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

Pada dasarnya, ini menguji bahwa kedua argumennya adalah file (tidak harus file biasa) dan jika ini masalahnya, tulislah bidang pertama dari setiap baris "$1"untuk "$2"menggunakan awk. Kemudian, perintah yang menggabungkan semua yang disebutkan sejauh ini adalah,

./psub.sh <(printf "a a\nc c\nb b") >(sort)

Ini akan dicetak

a
b
c

dan setara dengan

printf "a a\nc c\nb b" | awk '{print $1}' | sort

tetapi yang berikut tidak akan berfungsi, dan kami harus menggunakan proses substitusi di sini,

printf "a a\nc c\nb b" | ./psub.sh | sort

atau bentuknya yang setara

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Jika ./psub.shjuga membaca stdinselain apa yang disebutkan di atas, maka bentuk setara seperti itu tidak ada, dan dalam hal itu tidak ada yang bisa kita gunakan sebagai pengganti proses substitusi (tentu saja Anda juga dapat menggunakan pipa atau file temp bernama, tapi itu lain cerita).

Weijun Zhou
sumber