Bir bash betiğinde / dev / stdout hedef konumu nasıl kaydedilir?


12

Ben /dev/stdout1 dosya tanımlayıcı diğer konum ile değiştirmeden önce orijinal konumunu korumak isteyen belirli bir bash komut dosyası var .

Doğal olarak şöyle bir şey yazdım

old_stdout=$(readlink -f /dev/stdout)

Ve işe yaramadı. Çok çabuk sorunun ne olduğunu anlıyorum:

test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18

Obvioulsly, $()ana kabuğa borulanan bir alt kabukta çalışır.

Yani soru şudur: /dev/stdoutkonumu bir bash betiğinde dize olarak kaydetmek için güvenilir (Linux dağıtımları arasında taşınabilirlik kapsamına alınmış) bir yol var mı?


Bu biraz XY problemine benziyor . Altta yatan sorun nedir?
Kusalananda

Temel sorun, iki modda çalışan belirli bir kurulum betiğidir - sessiz, tüm çıktıyı dosya ve ayrıntılı olarak günlüğe kaydeder, yalnızca dosyaya oturum açmakla kalmaz, aynı zamanda her şeyi terminale yazdırır. Ancak her iki modda da komut dosyası kullanıcıyla etkileşim kurmak ister, yani terminale yazdırma ve kullanıcı yanıtını okuma. Bu nedenle tasarrufun /dev/stdoutsessiz modda mesaj yazdırmayla ilgili sorunu çözeceğini düşündüm . Alternatif, çıktı üreten diğer tüm eylemleri yönlendirir ve oldukça fazla sayıda eylem vardır. Kullanıcı etkileşim mesajlarından yaklaşık 100 kat daha fazla.
alexey.e.egorov

Kullanıcı ile etkileşimin standart yolu yazdırmaktır stderr. Örneğin, istemler stderrvarsayılan olarak neden budur .
Kusalananda

Ne yazık ki, stderrkomut dosyası bir dizi harici programı çağırdığından ve olası tüm hata iletileri toplanıp günlüğe kaydedileceğinden, yeniden yönlendirilmeli ve kaydedilmelidir.
alexey.e.egorov

Yanıtlar:


14

Bir dosya tanımlayıcısını kaydetmek için, dosyayı başka bir fd'de çoğaltırsınız. İlgili dosyaya bir yol kaydetmek yeterli değildir, açılış modunu, açılış bayraklarını, dosyadaki geçerli konumu vb. Kaydetmeniz gerekir. Ve elbette, anonim borular veya soketler için, bunların yolu olmadığı için işe yaramaz. Kaydetmek istediğiniz şey, fd'nin başvurduğu açık dosya açıklamasıdır ve bir fd'yi çoğaltmak aslında yeni bir fd'yi aynı açık dosya tanımına döndürür .

Bourne benzeri bir kabukla bir dosya tanımlayıcısını diğerine çoğaltmak için sözdizimi şöyledir:

exec 3>&1

Yukarıda fd 1, fd 3 üzerine kopyalanmıştır.

Fd 3 daha önce açık olan her neyse kapatılacaktı, ancak 3 ila 9 arasındaki disklerin (genellikle daha fazla, 99'a kadar yash) bu amaç için ayrıldığını (ve 0, 1 veya 2'nin aksine özel bir anlamı olmadığını) unutmayın. shell bunları kendi iç işleri için kullanmamayı bilir. Fd 3'ün önceden açık olmasının tek nedeni, komut dosyasında 1 yapmış olmanız ya da arayan tarafından sızdırılmış olmasıdır.

Ardından stdout'u başka bir şeye değiştirebilirsiniz:

exec > /dev/null

Ve sonra, stdout'u geri yüklemek için:

exec >&3 3>&-

( 3>&-artık ihtiyacımız olmayan dosya tanımlayıcısını kapatmak).

Şimdi, bununla ilgili sorun, ksh dışında, bundan sonra çalıştırdığınız her komutun exec 3>&1fd 3'ü miras alacağıdır. Genellikle çok önemli değil, ama bu soruna neden olabilir.

kshbu fds üzerinde yürütme yakın bayrağını ayarlar (2'den büyük fds için), ancak diğer mermilerin ve diğer mermilerin bu bayrağı manuel olarak ayarlamak için herhangi bir yolu yoktur.

Diğer kabuk için çözüm, her komut için fd 3'ü kapatmaktır, örneğin:

exec 3>&-

exec > file.log

ls 3>&-
uname 3>&-

exec >&3 3>&-

Hantal. Burada, en iyi yol hiç kullanmamak exec, ancak komut gruplarını yönlendirmek olacaktır:

{
  ls
  uname
} > file.log

Orada, stdout'u kaydetmeye ve daha sonra geri yüklemeye özen gösteren kabuktur (ve exec-close-off bayrak seti yashile bir fd'de (9'un üzerinde, 99'un üzerinde) çoğaltarak dahili olarak yapar ).

Not 1

Şimdi, bu fds 3 ila 9'un yönetimi, yoğun veya işlevlerde, özellikle de komut dosyanız bu fds'yi kullanabilen bazı üçüncü taraf kodları kullanıyorsa, hantal ve sorunlu olabilir.

Bazı kabuklar ( zsh, bash, ksh93, bütün özelliği (katma Oliver dalyan önerdiğizsh bu durumda yardımcı olur yerine 10'un üzerinde ilk serbest fd atamak için alternatif bir sözdizimi var onların geliştiriciler arasında tartışıldı sonra 2005 yılında aynı zamanda etrafında)):

myfunction() {
  local fd
  exec {fd}>&1
  # stdout was duplicated onto a new fd above 10, whose actual value
  # is stored in the fd variable
  ...
  # it should even be safe to re-enter the function here
  ...
  exec >&"$fd" {fd}>&-
}

Ayrıca, kod bir rc.localhizmetten çalıştırıldığında olduğu gibi fd 3'ün zaten alınmış olabileceği bir anlamda yanlıştır , örn exec {FD}>&1. Ancak bu sadece gerçekten üzücü olan bash 4'te desteklenir. Yani bu gerçekten taşınabilir değil.
alexey.e.egorov

@ alexey.e.egorov, bkz. düzenleme.
Stéphane Chazelas

Bash 3. * bu özelliği desteklemez ve bu sürüm hala desteklenmekte ve kullanılmakta olan Centos 5'de kullanılmaktadır. Ve ücretsiz tanımlayıcı bulmak ve sonra eval "exec $i>&1"hantallık nedeniyle kaçınmak istediğim bir şey. Gerçekten o zaman 9'un üzerindeki fds'nin özgür olacağına güvenebilir miyim ?
alexey.e.egorov

@ alexey.e.egorov, hayır, geriye bakıyorsun. 3'ten 9'a kadar olan disklerin kullanımı ücretsizdir (ve bunları istediğiniz gibi yönetmek size bağlıdır) ve bu amaç için tasarlanmıştır. 9'un üzerindeki fds kabuk tarafından dahili olarak kullanılabilir ve kapatılması kötü sonuçlara yol açabilir. Çoğu mermi onları kullanmanıza izin vermez. bashkendini ayağından vurmana izin verecek.
Stéphane Chazelas

2
@ alexey.e.egorov, eğer başlangıçta senaryonuzda (3..9) açık bazı fds varsa, bunun nedeni aracınızın onları kapatmayı veya yürütme yakın bayrağını ayarlamayı unutmasıdır. Ben buna kaçak diyorum. Şimdi, belki arayan bu fds'leri size iletmeyi amaçladı, böylece onlardan / onlara veri okuyabilir ve / veya yazabilirsiniz, ancak sonra bunu bilirsiniz. Onları bilmiyorsanız, o zaman umursamazsınız, o zaman onları serbestçe kapatabilirsiniz (aracınızın komut dosyasını değil, sadece komut dosyanızın işlemini kapattığını unutmayın).
Stéphane Chazelas

3

Gördüğünüz gibi, bash komut dosyaları, dosya tanımlayıcıları atayabileceğiniz normal bir programlama dili gibi değildir.

En basit çözüm, yeniden yönlendirilmesini istediğiniz şeyi çalıştırmak için bir alt kabuk kullanmaktır, böylece işleme, standart G / Ç bozulmamış üst kabuğa geri döndürülebilir.

Alternatif bir çözüm tty, TTY cihazını tanımlamak ve kodunuzdaki G / Ç'yi kontrol etmek için kullanmak olacaktır . Örneğin:

dev=$(tty)

ve sonra ..

echo message > $dev

> Alternatif bir çözüm, TTY cihazını tanımlamak ve betiğinizdeki G / Ç'yi kontrol etmek için tty kullanmak olacaktır. Bunu nasıl yaparsınız?
alexey.e.egorov

1
Cevabıma bir örnek ekledim.
Julie Pelletier

1

$$ etkileşimli kabuk veya komut dosyası durumunda, ilgili kabuk PID'sini geçerli işlem PID'si alırsınız.

Böylece şunları kullanabilirsiniz:

readlink -f /proc/$$/fd/1

Misal:

% readlink -f /proc/$$/fd/1
/dev/pts/33

% var=$(readlink -f /proc/$$/fd/1)

% echo $var                       
/dev/pts/33

1
İşlevsel olmakla birlikte, belirli bir /procyapıya güvenmek /dev/stdout, soruda belirtildiği gibi kullanmak gibi taşınabilirlik sorunlarına neden olur .
Julie Pelletier

1
@JuliePelletier Belirli bir /procyapıya mı güveniyorsunuz ? Herhangi bir Linux üzerinde çalışacak procfs..
heemayl

1
Doğru, bu yüzden Linux procfsiçin neredeyse her zaman olduğu gibi genelleme yapabiliriz , ancak genellikle taşınabilirlik soruları görüyoruz ve iyi bir geliştirme yöntemi diğer sistemlere taşınabilirliği dikkate almayı içerir. bashçok sayıda işletim sisteminde çalışabilir.
Julie Pelletier
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.