Kabuk hangi sırayla komutları yürütür ve yeniden yönlendirmeyi yürütür?


32

Her iki yönlendirmek için çalışıyordu stdoutve stderrbir dosyaya bugün ve bu rastladım:

<command> > file.txt 2>&1

Bu görünüşte yönlendirmeleri stderriçin stdoutilk ve daha sonra sonuçta elde stdoutyönlendirilir file.txt.

Ancak, neden sipariş değil <command> 2>&1 > file.txt? Kişi bunu doğal olarak (soldan sağa doğru yürütmeyi varsayarsak) önce yürütülen komutu, oraya stderryönlendirilmekte stdoutve daha sonra sonuçta stdoutyazıldığını okurdu file.txt. Ancak yukarıdakiler yalnızca stderrekrana yönlendirir .

Kabuk her iki komutu da nasıl yorumlar?


7
TLCL, "Önce standart çıktıyı dosyaya yeniden yönlendiririz, sonra da dosya tanımlayıcı 2'yi (standart hata) bir dosya tanımlayıcıya bir (standart çıktı) yönlendiririz" ve "Yönlendirmelerin sırasının önemli olduğuna dikkat edin. Standart hatanın yeniden yönlendirilmesi standart çıktıyı yönlendirdikten sonra her zaman gerçekleşmelidir veya çalışmaz "
Zanna

@Zanna: Evet, sorum tam olarak bunu TLCL'de okumaktan geldi! :) Neden işe yaramayacağını bilmekle ilgileniyorum, yani kabuğun genel olarak komutları nasıl yorumladığını.
Tren Heartnet

Peki, sorunuzda bunun tam tersini söylüyorsunuz, "Bu görünüşte stderr'i stdout'a ilk yönlendirir ..." - TLCL'den anladığım şey, kabuğun dosyaya stdout'u ve ardından stdout'a (yani dosyaya) göndermesidir. Benim yorumum stdout'a stderr gönderirseniz, terminalde görüntülenecek ve stdout'un daha sonra yeniden yönlendirilmesi stderr içermeyecektir (yani stderr'ün ekrana yönlendirilmesi, stdout'un yeniden yönlendirilmesi gerçekleşmeden önce tamamlanır mı?)
Zanna

7
Bunun söylenecek eski moda bir şey olduğunu biliyorum, ancak kabuğunuz bu şeyleri açıklayan bir el kitabıyla gelir - örneğin el kitabında yönlendirmebash . Bu arada, yönlendirmeler komut değildir.
Reinier Post

Komut olamaz yönlendirmeler kadar belirlenmeden önce yürütülecek: Ne zaman execv-ailesi syscall başlayabilmesi aslında komuta kapalı alt işlemi el çağrılır, kabuk döngü (dışarı o zaman artık bu süreçte yürütülmesi kodu vardır ve bu noktadan sonra olanları kontrol etmenin mümkün bir yolu yoktur; yönlendirmeler böylece tüm gereken yürütme başlamadan önce kabuk o süreçte çalışan kendisinin bir kopyasını sahipken, gerçekleştirilebilir fork(). komutunuzla çalıştırmak ed
Charles Duffy

Yanıtlar:


41

<command> 2>&1 > file.txtStderr komutunu çalıştırdığınızda , 2>&1stdout'un şu anda gittiği yere yönlendirilirsiniz , terminaliniz. Ondan sonra stdout dosya tarafından yönlendirilir >, ancak stderr onunla yönlendirilmez, böylece terminal çıkışı olarak kalır.

İle <command> > file.txt 2>&1stdout'u ilk dosyaya yönlendirilir >, daha sonra 2>&1yönlendirmeleri stdout'u dosyası olan nereye kadar stderr.

Başlamak sezgisel görünebilir gibi görünebilir, ancak bu şekilde yönlendirmeleri düşündüğünüzde ve soldan sağa doğru işlendiklerini unutmayın.


Dosya tanımlayıcıları ve "dup / fdreopen" çağrıları açısından soldan sağa sırayla yapıldığını düşünürseniz mantıklı
Mark K Cowan,

20

Eğer izini sürersen mantıklı gelebilir.

Başlangıçta, stderr ve stdout aynı şeye gider (genellikle burada çağırdığım terminal pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Buradaki dosya tanımlayıcı numaralarından stdin, stdout ve stderr'den bahsediyorum : sırasıyla dosya tanımlayıcıları 0, 1 ve 2.

Şimdi ilk yönlendirmelerde > file.txtve var 2>&1.

Yani:

  1. > file.txt: fd/1şimdi gider file.txt. İle >, 1bu yüzden hiçbir şey belirtilmemişse zımni dosya tanımlayıcı vardır 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    
  2. 2>&1: fd/2Şimdi her yerde gider fd/1 anda gider:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt
    

Öte yandan, 2>&1 > file.txtsiparişin tersine çevrilmesiyle:

  1. 2>&1: fd/2şu fd/1anda nereye giderse gitsin, yani hiçbir şey değişmez:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
    
  2. > file.txt: fd/1şimdi file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    

Önemli olan nokta, yeniden yönlendirmenin, yeniden yönlendirilen dosya tanımlayıcısının, hedef dosya tanımlayıcısındaki tüm gelecekteki değişiklikleri izleyeceği anlamına gelmez; sadece mevcut durumu ele alacaktır .


Teşekkürler, bu daha doğal bir açıklama gibi görünüyor! :) 2.İkinci kısımda olsa hafif bir yazım hatası yaptınız ; fd/1 -> file.txtve değil fd/2 -> file.txt.
Tren Heartnet

11

Sanırım kabuğun solda ilk yönlendirmeyi ayarlayacağını düşünecek ve bir sonraki yönlendirmeyi ayarlamadan önce onu tamamlayacağımı düşünüyorum.

William Shotts'un Linux Komut Satırı diyor

Önce standart çıktıyı dosyaya yönlendiririz, sonra da dosya tanımlayıcı 2'yi (standart hata) dosya tanımlayıcı bire (standart çıktı) yönlendiririz.

Bu mantıklı, ama sonra

Yönlendirme sırasının önemli olduğuna dikkat edin. Standart hatanın yeniden yönlendirilmesi, standart çıktının yönlendirilmesinden sonra her zaman gerçekleşmelidir veya çalışmaz.

ama aslında, stderr'yi aynı etkiye sahip bir dosyaya yönlendirdikten sonra stdout'u stderr'e yönlendirebiliriz.

$ uname -r 2>/dev/null 1>&2
$ 

Böylece, command > file 2>&1kabuk, stdout'u bir dosyaya, ardından stderr'i stdout'a (bir dosyaya gönderiliyor) gönderir. Oysa, command 2>&1 > filekabukta ilk önce stderr'i stdout'a yönlendirir (yani stdout'un normalde gittiği terminalde görüntüler) ve daha sonra stdout'u dosyaya yönlendirir. TLCL, önce stdout'u yönlendirmemiz gerektiğini söylerken yanıltıcıdır: stderr'i önce bir dosyaya yönlendirebilir, sonra stdout'u gönderebiliriz. Yapamayacağımız, stdout'u bir dosyaya yönlendirmeden önce stderr'ye veya tersi yönde yönlendirmektir. Başka bir örnek

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

Bunun stdout'u stderr ile aynı yere attığını düşünebiliriz, ancak değil, stdout'u ilk önce stderr'e (ekran) yönlendirir, sonra stderr'i yeniden yönlendirir, tam tersi şekilde denediğimizde ...

Umarım bu biraz ışık getirir ...


Çok daha anlamlı!
Arronical

Ah, şimdi anlıyorum! Çok teşekkürler, @Zanna ve @Arronical! Komut satırı yolculuğuma başladım. :)
Tren Heartnet

@TrainHeartnet bir zevk! Umarım sen de benim kadar zevk alıyorsun: D
Zanna

@Zanna: Gerçekten de öyleyim! : D
Tren Heartnet

2
@TrainHeartnet endişelenmeyin, hayal kırıklığı ve neşe dolu bir dünya sizi bekliyor!
Arronical

10

Şimdiden çok iyi cevapların var. Anlayışını büyük ölçüde yardımcı olan, burada yer alan iki farklı kavram olduğunu vurgulamama izin verin:

Arka plan: Dosya tanımlayıcısı vs. dosya tablosu

Dosya tanımlayıcınız, işleminizdeki dosya tanımlayıcı tablosundaki dizin olan sadece 0 ... n'dir. Kurallara göre, STDIN = 0, STDOUT = 1, STDERR = 2 (buradaki terimlerin STDINvb. Sadece bazı programlama dillerinde ve kılavuz sayfalarında kongre tarafından kullanılan semboller / makrolar olduğuna dikkat edin, STDIN adında gerçek bir "nesne" yoktur; Bu tartışmanın amacı, STDIN ise 0, vs.).

Bu dosya tanımlayıcı tablosu kendi içinde asıl dosyanın ne olduğu hakkında herhangi bir bilgi içermez. Bunun yerine, farklı bir dosya tablosuna bir işaretçi içerir; Sonuncusu, gerçek bir fiziksel dosya (veya engelleme aygıtı veya borusu veya Linux'un dosya mekanizması aracılığıyla ele alabileceği başka herhangi bir şey) ve daha fazla bilgi (yani okumak veya yazmak için) hakkında bilgi içerir.

Bu nedenle, kullandığınızda >veya <kabuğunuzda, ilgili dosya tanımlayıcısının işaretçisini başka bir şeye işaret etmek için değiştirirsiniz. Sözdizimi, 2>&1tanımlayıcı 2'yi, 1 puan olan yere işaret eder. > file.txtsadece file.txtyazmak için açılır ve STDOUT'un (dosya decsriptor 1) işaret etmesini sağlar.

Başka hedefler de var, örneğin 2>(xxx) (yani: çalışan yeni bir işlem xxxoluşturun, bir boru oluşturun, yeni işlemin dosya tanımlayıcısını 0 borunun okuma ucuna bağlayın ve orijinal işlemin dosya tanımlayıcısını 2 bağlantının yazma ucuna bağlayın boru).

Bu, kabuğunuzdan başka bir yazılımdaki "dosya tanıtımı sihri" nin de temelidir. Örneğin, Perl betiğinizde, dupSTDOUT dosya tanımlayıcısını başka (geçici) bir tanesine yerleştirebilir ve ardından STDOUT dosyasını yeni oluşturulan geçici bir dosyaya yeniden açabilirsiniz. Bu noktadan itibaren, kendi Perl betiğinizden tüm STDOUT çıktısı vesystem() bu betiğin tüm çağrıları bu geçici dosyada sona erecektir. Tamamlandığında, dupSTDOUT'unuzu kaydettiğiniz geçici tanımlayıcıya yeniden yazabilir ve önceden olduğu gibi hazırlayabilirsiniz. Bu arada, bu geçici tanımlayıcıya bile yazabilirsiniz, bu yüzden gerçek STDOUT çıktınız geçici dosyaya giderken, gerçekte gerçek STDOUT (genellikle kullanıcı) öğelerini çıktı alabilirsiniz .

Cevap

Sorunuza yukarıda verilen temel bilgileri uygulamak için:

Kabuk hangi sırayla komutları yürütür ve yeniden yönlendirmeyi yürütür?

Soldan sağa.

<command> > file.txt 2>&1

  1. fork yeni bir işlem.
  2. file.txtİşaretçisini açıp dosya tanımlayıcı 1'de (STDOUT) saklayın.
  3. STDERR'yi (dosya tanımlayıcısı 2) şu anda fd 1'in işaret ettiği yere (ki bu daha önce file.txtelbette zaten açık olan) işaretleyin.
  4. exec <command>

Bu, görünüşe göre stderr'yi önce stdout'a yönlendirir, ardından ortaya çıkan stdout file.txt dosyasına yönlendirilir.

Sadece bir masa olsaydı bu mantıklı olurdu , ama yukarıda açıklandığı gibi iki tane var. Dosya tanımlayıcıları tekrarlı olarak birbirlerine işaret etmiyor, "STDERR'yi STDOUT'a yönlendir" düşünmenin bir anlamı yok. Doğru düşünce "STDERR'nin STDOUT'un işaret ettiği yere gitmesi" dir. STDOUT'u daha sonra değiştirirseniz, STDERR olduğu yerde kalır, STDOUT'da yapılan diğer değişikliklerle birlikte sihirli bir şekilde ilerlemez.


"Dosya tanıtıcı sihir" bit için uyarlama - soruyu doğrudan yanıtlamamasına rağmen, bugün yeni bir şey öğrendim ...
Floris

3

Sıra sağa bırakıldı. Bash'in el kitabı ne istediğinizi çoktan ele aldı REDIRECTIONKılavuzun bölümünden alıntı :

   Redirections  are  processed  in  the
   order they appear, from left to right.

ve birkaç satır sonra:

   Note that the order of redirections is signifi
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

Herhangi bir komut çalıştırılmadan önce yönlendirmenin ilk önce çözüldüğünü not etmek önemlidir! Bkz. Https://askubuntu.com/a/728386/295286


3

Her zaman sağa kalır ... ne zaman hariç

Tıpkı Math'da olduğu gibi, çarpma ve bölme, toplama ve çıkarma işleminden önce, parantez içindeki (+ -) işlemlerin çarpma ve bölmeden önce yapılması haricinde sağdan sola döneriz.

Buradaki Bash yeni başlayanlar için kılavuza göre ( Bash Yeni Başlayanlar Rehberi ), önce gelenlerin 8 hiyerarşisi vardır (soldan sağa önce):

  1. Brace genişletme "{}"
  2. Tilde genişlemesi "~"
  3. Shell parametresi ve değişken ifadesi "$"
  4. Komut değiştirme "$ (command)"
  5. Aritmetik ifadesi "$ ((EXPRESSION))"
  6. İşlem Değişikliği Burada neyi kastediyoruz "<(LIST)" veya "> (LIST)"
  7. Kelime "" <boşluk> <sekme> <yeni satır> '"
  8. Dosya Adı Genişletme "*", "?", Vb.

Demek ki her zaman sağa gidiyor ...


1
Açık olmak gerekirse: işlem değiştirme ve yeniden yönlendirme iki bağımsız işlemdir. Birini diğeri olmadan yapabilirsiniz. Süreç ikamesine sahip olmak,
bash'ın
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.