Dosya tanımlayıcılarını taşımak için pratik kullanım


16

Bash man sayfasına göre:

Yönlendirme operatörü

   [n]<&digit-

dosya tanımlayıcıyı digitdosya tanımlayıcıya nveya belirtilmemişse standart girdiye (dosya tanımlayıcı 0) taşır n. digitkopyalandıktan sonra kapatılır n.

Bir dosya tanımlayıcıyı diğerine "taşımak" ne demektir? Bu tür uygulamaların tipik durumları nelerdir?

Yanıtlar:


14

3>&4-bash tarafından da desteklenen bir ksh93 uzantısıdır ve bu kısadır 3>&4 4>&-, yani 3 şimdi 4'ün kullanıldığı ve 4'ün kapalı olduğu noktayı gösterir, bu yüzden 4 ile işaretlenen şimdi 3'e taşınmıştır.

Tipik kullanım, aşağıdaki gibi çoğalttığınız stdinveya stdoutbir kopyasını kaydettiğiniz ve geri yüklemek istediğiniz durumlarda olabilir:

Bir değişkenin stdout'unu yalnız bırakırken bir komutun stderr'ını (ve yalnızca stderr) yakalamak istediğinizi varsayalım.

Komut ikamesi var=$(cmd), bir boru oluşturur. Borunun yazma ucu cmd'stdout (dosya tanımlayıcı 1) olur ve diğer uç değişkeni doldurmak için kabuk tarafından okunur.

İstersen Şimdi stderrdeğişkene gitmek, bunu yapabilirsiniz: var=$(cmd 2>&1). Şimdi hem fd 1 (stdout) hem de 2 (stderr) boruya (ve sonunda değişkene) gider, bu da istediğimiz şeyin sadece yarısıdır.

Eğer yaparsak var=$(cmd 2>&1-)(kısa var=$(cmd 2>&1 >&-), şimdi sadece cmdstderr boruya gider, ama fd 1 kapalıdır. Herhangi cmdbir çıktı yazmaya çalışırsa, bu bir EBADFhata ile döner , eğer bir dosyayı açarsa, ilk serbest fd'yi alır ve stdoutkomut buna karşı koruma sağlamadığı sürece açık dosyaya atanır ! İstediğimiz de değil.

Stdout'un cmdyalnız bırakılmasını istiyorsak, komut ikamesinin dışında işaret ettiği kaynağa işaret etmek istiyorsak, o kaynağı komut ikamesinin içine getirmek için bir şekilde ihtiyacımız var. Bunun için komut değiştirme yerine stdout dışında bir kopyasını içine alabiliriz.

{
  var=$(cmd)
} 3>&1

Hangi yazmak için temiz bir yoldur:

exec 3>&1
var=$(cmd)
exec 3>&-

(aynı zamanda sonunda kapatmak yerine fd 3'ü geri yükleme avantajına sahiptir).

Daha sonra {(veya exec 3>&1) üzerine ve }her ikisi de fd 1 ve 3 başlangıçta işaret ettiği aynı fd 1 kaynağına işaret eder. fd 3 ayrıca komut ikamesi içindeki kaynağı işaret edecektir (komut ikamesi sadece fd 1, stdout'u yönlendirir). Yukarıda, cmd1, 2, 3 numaralı diskler için var:

  1. boru var
  2. dokunulmamış
  3. Komut yerine koymanın dışında 1 puanla aynı

Bunu şu şekilde değiştirirsek:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

Sonra olur:

  1. Komut yerine koymanın dışında 1 puanla aynı
  2. boru var
  3. Komut yerine koymanın dışında 1 puanla aynı

Şimdi, istediğimizi aldık: stderr boruya gidiyor ve stdout dokunulmadan bırakılıyor. Ancak, bu fd 3'ü sızdırıyoruz cmd.

Komutlar (kural gereği) 0 - 2 arasındaki fd'lerin açık olduğunu ve standart girdi, çıktı ve hata olduğunu varsayarken, diğer fd'lerden hiçbir şey kabul etmezler. Büyük olasılıkla bu fd 3'ü el değmeden bırakacaklar. Başka bir dosya tanımlayıcıya ihtiyaç duyarlarsa, yalnızca open()/dup()/socket()...ilk kullanılabilir dosya tanımlayıcısını döndürecek bir dosya yaparlar . Eğer (bunu yapan bir kabuk betiği gibi exec 3>&1) fdözel olarak kullanmaları gerekiyorsa , önce bir şeye atayacaklar (ve bu süreçte, fd 3'ümüz tarafından tutulan kaynak bu süreç tarafından serbest bırakılacak).

Fd 3'ü kapatmak iyi bir uygulamadır, çünkü cmdonu kullanmaz, ancak aramadan önce onu atanmış olarak bırakmak önemli değildir cmd. Sorunlar şunlar olabilir: cmd(ve potansiyel olarak ortaya çıkardığı diğer süreçler) bir tane daha az kullanılabilir. Potansiyel olarak daha ciddi bir problem, fd'nin işaret ettiği kaynağın, cmdarka planda ortaya çıkan bir süreç tarafından tutulabilmesidir . Bu kaynağın bir boru veya başka bir süreçler arası iletişim kanalı (betiğinizin çalıştırıldığı gibi script_output=$(your-script)) olması endişe verici olabilir , çünkü diğer uçtan okuma işlemi dosya sonuna kadar asla dosya sonu görmeyecektir arkaplan işlemi sona erer.

Yani burada yazmak daha iyi:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

Hangi ile bashile kısaltılabilir:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

Nadiren kullanılma nedenlerini özetlemek için:

  1. standart olmayan ve sadece sözdizimsel şekerdir. Birkaç tuş vuruşunu kaydetmeyi, betiğinizi bu sıra dışı özelliğe alışkın olmayan kişiler için daha az taşınabilir ve daha az belirgin hale getirerek dengelemeniz gerekir.
  2. Orijinal fd'yi çoğalttıktan sonra kapatma ihtiyacı genellikle göz ardı edilir, çünkü çoğu zaman sonuçtan muzdarip değiliz, bu yüzden veya >&3yerine yaparız .>&3->&3 3>&-

Nadiren kullanıldığının kanıtı, bildiğiniz gibi bash'da sahte olduğu . Bash compound-command 3>&4-veya any-builtin 3>&4-yapraklar fd 4 sonra bile kapalı compound-commandveya any-builtindöndü. Sorunu çözmek için bir düzeltme eki (2013-02-19) kullanıma sunulmuştur.


Teşekkürler, şimdi bir fd'yi hareket ettirmenin ne olduğunu biliyorum. 2. snippet (ve genel olarak fds) ile ilgili 4 temel sorum var: 1) cmd1'de, 3'ün (stderr) 3'ün bir kopyası olmasını sağlarsınız, eğer komut bu 3 fd'yi dahili olarak kullanırsa? 2) Neden 3> & 1 ve 4> & 1 çalışıyor? Çoğaltma 3 ve 4 sadece bu iki cmds'de etkili olur, mevcut kabuk da etkilenir mi? 3) Neden cmd1'de 4 ve cmd2'de 3'ü kapatıyorsunuz? Bu komutlar belirtilen diskleri kullanmaz, değil mi? 4) Son snippet'te, bir fd varolmayan birine kopyalanırsa (cmd1 ve cmd2 cinsinden), sırasıyla 3 ve 4 anlamına gelir?
Quentin

@Quentin, daha basit bir örnek kullandım ve şimdi yanıtladığından daha az soru getirdiğini umarak biraz daha açıkladım. Hala doğrudan hareketli sözdizimi ile ilgili olmayan sorularınız varsa, ayrı bir soru sormanızı öneririz
Stéphane Chazelas

{ var=$(cmd 2>&1 >&3) ; } 3>&1-Kapanış 1'deki bir yazım hatası değil mi?
Quentin

@Quentin, Ben bunu kapsaması amaçlanmıştır sanmıyorum bir yazım hatası ancak parantez kullanımlar içeride hiçbir şey beri hiç fark etmez, bu 1 fd (o 3 fd'ye bunu çoğaltarak bütün mesele vardı: 1 Orijinal çünkü aksi takdirde içeride erişilebilir olmaz ). $(...)
Stéphane Chazelas

1
@Quentin Girdikten sonra {...}, fd 3, fd 1'in işaret ettiği şeyi ve fd 1'in kapatıldığını gösterir, daha sonra girildikten sonra $(...)fd 1, beslenen boruya $var, daha sonra cmd2'ye de ve daha sonra da 1'e 3 noktasına ayarlanır. daha sonra 1'in kapalı kalması bash'da bir hata, bunu rapor edeceğim. Bu özelliğin geldiği ksh93'te bu hata yoktur.
Stéphane Chazelas

4

Diğer dosya tanımlayıcı ile aynı yeri göstermesi anlamına gelir. Sen ayrı standart hata tanımlayıcısı bariz ayrı ele gelen, çok nadiren yapmanız gerekir ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2). Bazı karmaşık durumlarda kullanışlı olabilir.

Gelişmiş Bash Komut Dosyası kılavuzunda daha uzun günlük düzeyi örneği ve bu snippet bulunur:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

Source Mage's Sorcery'da örneğin, aynı kod bloğundan farklı çıktıları ayırt etmek için kullanıyoruz:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

Günlüğe kaydetme nedenleriyle ek işlem ikamesi eklenir (VOYEUR verilerin ekranda gösterilip gösterilmeyeceğine veya yalnızca günlükte tutulmasına karar verir), ancak bazı iletilerin her zaman sunulması gerekir . Bunu başarmak için, onları dosya tanımlayıcı 3'e yazdırır ve daha sonra özel olarak ele alırız.


0

Unix'te dosyalar dosya tanımlayıcıları tarafından işlenir (örneğin küçük girdi tamsayıları, örneğin standart girdi 0, standart çıktı 1, standart hata 2'dir; diğer dosyaları açtıkça normalde kullanılmayan en küçük tanımlayıcı atanırlar). Bu nedenle, programın içsel özelliklerini biliyorsanız ve dosya tanımlayıcı 5'e giden çıktıyı standart çıktıya göndermek istiyorsanız, tanımlayıcı 5'i 1'e taşırsınız. Bu, geldikleri yer 2> errorsve yapıların 2>&1hataları çoğaltmak ister çıktı akışı.

Yani, neredeyse hiç kullanılmadı (25 yılı aşkın neredeyse özel Unix kullanımımda öfkeyle bir veya iki kez kullandığımı belli belirsiz hatırlıyorum), ancak gerektiğinde kesinlikle gerekli.


Ama neden dosya tanımlayıcı 1'i şu şekilde çoğaltmıyorsunuz: 5> & 1? Çekirdek FD'yi hareket ettirmenin ne işe yaradığını anlamıyorum çünkü çekirdek hemen sonra onu kapatmak üzeredir ...
Quentin

Bu 5'i 1'e kopyalamaz, 5'in 1'e gittiği yere gönderir. Ve sormadan önce; Evet, çeşitli öncü kabuklardan alınan birkaç farklı gösterim var.
vonbrand

Hala anlamadım. Eğer 5>&11 nereye gittiğini için 5 gönderir, sonra da tam olarak ne yok 1>&5-5 kapanış yanında do?
Quentin
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.