İşlem ikame çıktısı bozuk


16

echo one; echo two > >(cat); echo three; 

komutu beklenmedik çıktılar veriyor.

Bunu okudum: İşlem ikamesi bash'da nasıl uygulanır? ve internette süreç ikamesi ile ilgili diğer birçok makale, ancak neden bu şekilde davrandığını anlamıyorum.

Beklenen çıktı:

one
two
three

Gerçek çıktı:

prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two

Ayrıca, bu iki komut benim açımdan eşdeğer olmalıdır, ancak bunlar:

##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5

Neden aynı olmaları gerektiğini düşünüyorum? Çünkü, her ikisi de seqçıkışı catanonim kanal üzerinden girişe bağlar - Wikipedia, İşlem ikamesi .

Soru: Neden bu şekilde davranıyor? Benim hatam nerede? Kapsamlı cevap istenir ( bashkaputun altında nasıl yapıldığının açıklaması ile ).



2
Aslında, bu sorunun daha önemli olduğu için bu sorunun bunun bir kopyası olarak işaretlenmesi daha iyi olurdu. Bu yüzden cevabımı oraya kopyaladım.
Stéphane Chazelas

Yanıtlar:


21

Evet, olduğu bashgibi ksh(özelliğin geldiği yer), işlem ikamesi içindeki süreçler için beklenmez (koddaki bir sonraki komutu çalıştırmadan önce).

Bir için <(...)olduğu gibi ince, genellikle var biri:

cmd1 <(cmd2)

kabuk , yerine geçen borudaki dosya sonuna kadar okunması ve genellikle dosya sonu öldüğünde bu dosya sonu gerçekleşmesi için bekleyecek cmd1ve cmd1tipik olarak bekleyecektir . Yani birkaç kabukları (değil aynı neden ) bekleyen zahmet etmeyin de .cmd2cmd2bashcmd2cmd2 | cmd1

İçin cmd1 >(cmd2)daha var olduğu, ancak, o, genellikle böyle değil cmd2bekler o tipik cmd1olacak genellikle çıkış sonrası bu yüzden orada.

Bu zsh, cmd2orada bekleyen sabittir (ancak yerleşik olarak yazdıysanız cmd1 > >(cmd2)ve cmd1yerleşik değilse, belgelenmiş{cmd1} > >(cmd2) olarak kullanın ).

kshvarsayılan olarak beklemez, ancak waityerleşik ile beklemenizi sağlar (ayrıca pid'i kullanılabilir hale getirir $!, ancak bu yardımcı olmaz cmd1 >(cmd2) >(cmd3))

rc( cmd1 >{cmd2}sözdizimi ile), kshdışında tüm arka plan işlemlerinin pids'lerini alabilirsiniz $apids.

es(bu da cmd1 >{cmd2}bekler için) cmd2gibi zsh, hem de bekler cmd2içinde <{cmd2}işlem yönlendirmeleri.

bashpid'in cmd2(ya da daha tam olarak alt kabuğun o alt kabuğun cmd2bir alt işleminde çalıştığı gibi tam olarak alt kabuğun ) kullanılabilir olmasını sağlar $!, ancak beklemenize izin vermez.

Kullanmak zorunda yoksa bash, sen her iki komutlarını bekler bir komutu kullanarak sorunu çalışabilirsiniz:

{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1

Bu her ikisini de yapar cmd1ve cmd2fd 3'lerini bir boruya açar. catdosya sonunu diğer uçta bekleyecek, bu nedenle genellikle yalnızca her ikisi de cmd1ve cmd2ölü olduğunda çıkacaktır . Ve kabuk bu catkomutu bekleyecek . Tüm arka plan işlemlerinin sonlandırılmasını yakalamak için bir net olarak görebilirsiniz (bunu arka planla başlayan diğer şeyler için kullanabilirsiniz &, coprocs veya arka planın kendileri gibi arka planın kendileri gibi tüm dosya tanımlayıcılarını kapatmazlarsa kullanabilirsiniz ).

Yukarıda bahsedilen boşa giden alt kabuk işlemi sayesinde cmd2, fd 3'ü kapatsa bile çalıştığını unutmayın (komutlar genellikle bunu yapmaz, ancak bazıları beğenir sudoveya sshyapar). Gelecekteki sürümleri bashsonunda diğer kabuklarda olduğu gibi orada optimizasyonu yapabilir. O zaman şöyle bir şeye ihtiyacınız olacak:

{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1

Hala bu fd 3 açık bu sudokomutu bekleyen ekstra bir kabuk işlemi olduğundan emin olmak için .

Not cat(süreçler kendi fd 3 yazmayın beri) bir şey okumazlar. Sadece senkronizasyon için var. Sonunda read()hiçbir şey olmadan geri dönecek sadece bir sistem çağrısı yapacak.

catBoru senkronizasyonunu yapmak için bir komut ikamesi kullanarak çalışmayı gerçekten önleyebilirsiniz :

{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1

Bu sefer, kabuk yerine var catolan diğer ucu arasında fd 3 açıktır borusundan okuyor cmd1ve cmd2. Çıkış durumu yüzden değişken atama kullanıyorsanız cmd1mevcuttur $?.

Ya da süreç ikamesini elle yapabilirsiniz ve daha sonra sisteminizi shstandart kabuk sözdizimi haline gelecek şekilde bile kullanabilirsiniz :

{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1

Ancak daha önce de belirtildiği gibi, tüm shuygulamaların bittikten cmd1sonra beklemeyeceğine dikkat edin cmd2(bu, diğer taraftan daha iyi olsa da). O zaman, $?çıkış durumunu içerir cmd2; gerçi bashve zshmake cmd1mevcuttur 'ın çıkış durumu ${PIPESTATUS[0]}ve $pipestatus[1]sırasıyla (ayrıca bkz pipefailyani birkaç kabuklarda seçeneği $?geçen dışındaki boru devrelerinin hasar bildirebilirsiniz)

yashİşlem yeniden yönlendirme özelliğiyle benzer sorunları olduğunu unutmayın . orada cmd1 >(cmd2)yazılır cmd1 /dev/fd/3 3>(cmd2). Ama beklemiyor ve onu beklemek için cmd2kullanamazsınız waitve pid $!değişkeni de kullanıma sunmaz. Etrafındaki işi aynı şekilde kullanırdınız bash.


Önce denedim echo one; { { echo two > >(cat); } 3>&1 >&4 4>&- | cat; } 4>&1; echo three;, sonra basitleştirdim echo one; echo two > >(cat) | cat; echo three;ve değerleri de doğru sırayla çıktı. Tüm bu tanımlayıcı manipülasyonlar 3>&1 >&4 4>&-gerekli mi? Ayrıca, bunu anlamıyorum >&4 4>&- stdoutdördüncü fd'ye yönlendiriyoruz , sonra dördüncü fd'yi kapatıyoruz, sonra tekrar kullanıyoruz 4>&1. Neden gerekli ve nasıl çalışıyor? Belki bu konuda yeni bir soru oluşturmalıyım?
MiniMax

1
@MiniMax, ama orada, sen stdout'unu etkileyen ediyoruz cmd1ve cmd2, dosya tanımlayıcı ile küçük dans ile nokta orijinal olanları geri ve sadece ekstra boru kullanarak etmektir bekleyen yerine de komutların çıktısını kanalize.
Stéphane Chazelas

@MiniMax Anlamak biraz zaman aldı, boruları daha önce bu kadar düşük bir seviyeye getirmedim. En sağdaki 4>&1dış parantez komut listesi için bir dosya tanımlayıcı (fd) 4 oluşturur ve dış parantezlerin stdout'una eşit yapar. İç parantezler, dış parantezlere bağlanmak için otomatik olarak stdin / stdout / stderr kurulumuna sahiptir. Ancak, 3>&1fd 3'ün dış parantezlerin stdinine bağlanmasını sağlar. >&4iç parantezlerin stdout'unu fd 4 (daha önce yarattığımız) dış parantezlere bağlar. 4>&-fd 4'ü iç parantezlerden kapatır (İç parantezlerin stdoutu zaten parantezlerin fd 4'üne bağlı olduğundan).
Nicholas Pipitone

@MiniMax Kafa karıştırıcı kısım sağdan sola kısımdı 4>&1, önce diğer yönlendirmelerden önce yürütülür, böylece "tekrar kullanmazsınız 4>&1". Genel olarak, iç diş telleri verilmiş olan her şeyle üzerine yazılan stdout'una veri gönderiyor. İç parantezlerin verildiği fd4, dış parantezlerin orijinal stdoutuna eşit olan dış parantezlerin fd 4'üdür.
Nicholas Pipitone

Bash, 4>5"4, 5'e gider" anlamına gelir, ancak gerçekten "fd 4, fd 5'in üzerine yazılır" gibi hissettirir . Ve yürütmeden önce, fd 0/1/2 otomatik olarak bağlanır (dış kabuğun herhangi bir fd'si ile birlikte) ve istediğiniz gibi üzerine yazabilirsiniz. Bu en azından bash belgelerini yorumlamam. Eğer başka bir şey out anlasalardı bu , Lmk.
Nicholas Pipitone

4

İkinci komutu cat, giriş borusu kapanana kadar bekleyecek olan bir başkasına bağlayabilirsiniz. Ör:

prompt$ echo one; echo two > >(cat) | cat; echo three;
one
two
three
prompt$

Kısa ve basit.

==========

Göründüğü kadar basit, perde arkasında çok şey oluyor. Bunun nasıl çalıştığıyla ilgilenmiyorsanız, cevabın geri kalanını göz ardı edebilirsiniz.

Eğer varsa echo two > >(cat); echo three, >(cat)interaktif kabuk tarafından çatallanabilen ve bağımsız çalışır edilir echo two. Böylece, echo twobitirir ve sonra bitirilir, echo threeancak bitirmeden önce >(cat). Ne zaman beklemiyordu zaman bashveri alır >(cat)(birkaç milisaniye sonra), size terminaline geri almak için yeni satır vurmak zorunda istemi benzeri bir durum verir (başka bir kullanıcı mesgseni edindiği gibi).

Bununla birlikte, echo two > >(cat) | cat; echo threeiki alt kabuk ortaya çıkar ( |sembolün belgelerine göre ).

A adlı echo two > >(cat)bir alt kabuk içindir ve B adlı bir alt kabuk içindir cat. A otomatik olarak B'ye bağlanır (A'nın stdout'u B'nin stdinidir). Ardından, echo twove >(cat)yürütme başlar. >(cat)'nin stdout'u A'nın stdout'una ayarlanır, bu da B'nin stdin'ine eşittir. Bitirdikten sonra echo twoA, stdout'unu kapatarak çıkar. Bununla birlikte, >(cat)hala B'nin stdinine atıfta bulunuyor. İkincisi cat, B'nin stdin'ini tutuyor ve bu catbir EOF görene kadar çıkmayacak. EOF yalnızca artık dosya yazma modunda hiç kimse açık olmadığında verilir, bu nedenle >(cat)stdout ikincisini engeller cat. B o saniye beklemeye devam ediyor cat. echo twoÇıkıldığından beri , >(cat)sonunda bir EOF alır,>(cat)arabelleğini temizler ve çıkar. Artık hiç kimse B'nin / saniyenin catstdini tutmuyor, bu yüzden ikincisi catbir EOF okuyor (B stdini hiç okumuyor, umursamıyor). Bu EOF, ikincisinin catarabelleğini temizlemesine, stdout'unu kapatmasına ve çıkmasına neden olur ve daha sonra B çıkar catve B bekliyordu cat.

Bunun bir uyarısı, bash'ın bir subshell ortaya çıkarmasıdır >(cat)! Bu nedenle, göreceksin ki

echo two > >(sleep 5) | cat; echo three

B'nin stdinine sahip echo threeolmasa bile, yürütmeden önce 5 saniye bekleyecek sleep 5. Bunun nedeni, C'nin ortaya çıktığı gizli bir alt kabukun >(sleep 5)beklemesidir sleepve C, B'nin stdin'ini tutmaktadır. Nasıl olduğunu görebilirsiniz

echo two > >(exec sleep 5) | cat; echo three

Ancak beklemeyeceğim, çünkü sleepB'nin stdin'ini tutmuyor ve B'nin stdin'ini tutan hayalet alt kabuk C yok (exec, C'yi beklemeye alıp yapmak yerine uykuyu C'nin yerini almaya zorlayacak sleep). Bu uyarıdan bağımsız olarak,

echo two > >(exec cat) | cat; echo three

işlevleri daha önce açıklandığı gibi sırayla düzgün şekilde yürütür.


Cevabımdaki yorumlarda @MiniMax ile dönüşümde belirtildiği gibi, ancak komutun stdoutunu etkilemenin dezavantajı vardır ve çıktının ekstra bir zaman okunması ve yazılması gerektiği anlamına gelir.
Stéphane Chazelas

Açıklama doğru değil. ortaya çıkmayı Abeklemiyor . Cevabımda belirttiğim gibi, 5 saniye sonra çıktıların nedeni , atıkların mevcut sürümlerinin beklemekte olan fazladan bir kabuk işlemi olması ve bu sürecin hala ikincinin sona ermesini engelleyen stdout'a sahip olmasıdır . Bu ekstra işlemi ortadan kaldırmak için ile değiştirirseniz, hemen geri döndüğünü göreceksiniz. cat>(cat)echo two > >(sleep 5 &>/dev/null) | cat; echo threethreebash>(sleep 5)sleeppipecatecho two > >(exec sleep 5 &>/dev/null) | cat; echo three
Stéphane Chazelas

Yuvalanmış bir alt kabuk mu yapar? Bunu anlamak için bash uygulamasına bakmaya çalışıyorum echo two > >(sleep 5 &>/dev/null), en azından kendi alt kabuğunu aldığından eminim . sleep 5Kendi alt kabuğuna da neden olan belgelenmemiş bir uygulama detayı mı? Belgelenirse, daha az karakterle bitirmenin meşru bir yolu olurdu (Sıkı bir döngü yoksa, herkes bir alt kabuk veya kedi ile performans sorunlarını fark edeceğini sanmıyorum) ``. Belgelenmemişse, rip, güzel kesmek olsa da, gelecekteki sürümlerde çalışmaz.
Nicholas Pipitone

$(...), <(...)bir alt kabuk içermelidir, ancak ksh93 veya zsh aynı alt kabuktaki son komutu aynı işlemde çalıştırır, bashbu yüzden boruyu açık sleeptutmayan bir boruyu açık tutarken başka bir işlem var . Gelecek sürümleri de bashbenzer bir optimizasyon uygulayabilir.
Stéphane Chazelas

1
@ StéphaneChazelas Cevabımı güncelledim ve kısa versiyonun mevcut açıklamasının doğru olduğunu düşünüyorum, ancak doğrulayabilmeniz için kabukların uygulama ayrıntılarını biliyor gibisiniz. Bence bu çözüm, dosya tanımlayıcı dansın aksine kullanılmalı, ancak altında bile execbeklendiği gibi çalışıyor.
Nicholas Pipitone
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.