Bash'te dosya tanımlayıcısı (file descriptor), kabuğun açık bir dosyaya veya akışa verdiği numaradır: 0 standart girdi, 1 standart çıktı, 2 standart hatadır, 3 ve sonrası sizin açacağınız akışlar içindir. exec 3> dosya yazarak 3 numarayı bir dosyaya yazmak için açar, echo ... >&3 ile oraya yazar, exec 3>&- ile kapatırsınız. exec komutu tek başına yönlendirmeyle kullanıldığında yönlendirme bir komutla sınırlı kalmaz, betiğin geri kalanına uygulanır. {fd}> biçimi ise boş bir numarayı kabuğun kendisinin seçmesini sağlar.

Bu yazı GNU Bash kılavuzunun Redirections bölümündeki tanımlayıcı kopyalama, taşıma ve okuma-yazma için açma başlıklarını ve exec yerleşik komutunu çalıştırılmış örneklerle anlatıyor. Betiğin tüm çıktısını bir günlüğe yönlendirip sonra ekrana geri dönmek, iki dosyayı aynı döngüde satır satır okumak ve standart çıktı ile standart hatanın yerini değiştirmek gibi pratik kalıpları da gösteriyoruz. Temel yönlendirme operatörleri (>, >>, 2>&1) için önce yönlendirme yazısını okumanızı öneririz.

exec ile kalıcı yönlendirme

exec ile kalıcı yönlendirme

Kılavuza göre exec yerleşik komutu iki farklı iş yapar. Ona bir komut verirseniz kabuk yeni bir süreç oluşturmadan o komutla yer değiştirir; exec satırından sonraki satırlar hiç çalışmaz. Komut vermeden yalnızca yönlendirme yazarsanız yönlendirmeler mevcut kabuğun ortamına uygulanır ve sonraki tüm komutlar bundan etkilenir. Yönlendirme hatasız yapılırsa dönüş durumu sıfırdır.

bash
exec echo "son komut"
echo "bu satır hiç çalışmaz"
# çıktı: son komut

Bu yazıdaki örneklerin çoğu ikinci kullanımla ilgili. Birinci kullanım, bir betiğin son adımında başka bir programı başlatırken kabuk sürecini gereksiz yere açık tutmamak için tercih edilir. Kılavuz, komut çalıştırılamazsa etkileşimli olmayan bir kabuğun çıktığını, execfail seçeneği açıksa ise sıfırdan farklı bir durumla devam ettiğini de belirtir.

Yeni bir tanımlayıcı açmak ve kapatmak

exec 3> dosya 3 numaralı tanımlayıcıyı dosyaya yazmak için açar; tıpkı > gibi dosya varsa sıfırlanır. Sonrasında >&3 ile yazdığınız her satır aynı açık dosyaya, kaldığı yerden eklenir. Dosya her yazmada yeniden açılmadığı için çok sayıda satır yazan döngülerde bu yöntem >> kullanmaktan daha verimlidir. İşiniz bitince exec 3>&- ile tanımlayıcıyı kapatırsınız. Kılavuz, kopyalama operatörlerinde hedef sözcük - olduğunda tanımlayıcının kapatıldığını söyler.

bash
exec 3> gunluk.txt
echo "ilk kayıt" >&3
echo "ikinci kayıt" >&3
exec 3>&-
cat gunluk.txt
# çıktı:
#   ilk kayıt
#   ikinci kayıt

Okuma için de aynısı geçerlidir: exec 4< dosya 4 numarayı okumak için açar. read komutu varsayılan olarak standart girdiden okur; başka bir tanımlayıcıdan okumak için ya read ... <&4 biçiminde yönlendirme yaparsınız ya da read komutunun -u seçeneğini kullanırsınız. Kılavuza göre -u fd, okumayı standart girdi yerine verilen tanımlayıcıdan yapar. İki yöntem de aynı açık dosyadan okuduğu için her read bir sonraki satıra geçer.

bash
printf '%s\n' "ilk kayıt" "ikinci kayıt" > gunluk.txt
exec 4< gunluk.txt
read -r s1 <&4
read -r -u 4 s2
exec 4<&-
echo "$s1 / $s2"
# çıktı: ilk kayıt / ikinci kayıt

read seçeneklerinin tamamı read ile kullanıcı girdisi yazısında anlatılıyor. Burada önemli olan, açık bir tanımlayıcının bir okuma konumu tutmasıdır: dosyayı kapatıp yeniden açmadığınız sürece okuma kaldığı yerden devam eder.

Kopyalama: n>&m ve n<&m

Kılavuzdaki [n]>&sözcük operatörü çıktı tanımlayıcılarını, [n]<&sözcük girdi tanımlayıcılarını kopyalar. Sözcük bir sayıya genişlerse n o tanımlayıcının kopyası yapılır. n yazılmazsa çıktı için 1, girdi için 0 varsayılır. Kopyalamanın anlamı, iki numaranın aynı açık dosyayı göstermesidir; birine yazılan diğerinin konumunu da ilerletir. Kılavuz, sayının çıktı için açık olmayan bir tanımlayıcıyı göstermesinin yönlendirme hatası olduğunu belirtir. Açık olmayan bir tanımlayıcıya yazmaya çalıştığınızda aldığınız hata şudur:

bash
echo "deneme" >&7
echo "durum: $?"
# çıktı:
#   ornek.sh: line 1: 7: Bad file descriptor
#   durum: 1

Kopyalamanın en bilinen örneği 2>&1'dir, ama aynı mekanizma kendi açtığınız tanımlayıcılarla da çalışır. Aşağıda betik boyunca açık kalan bir hata dosyası 3 numaraya bağlanıyor ve tek bir komutun hata akışı 2>&3 ile oraya yönlendiriliyor. Standart çıktı etkilenmediği için ekranda kalıyor.

bash
f() { echo "normal çıktı"; echo "hata mesajı" >&2; }
exec 3> sessiz.txt
f 2>&3
exec 3>&-
echo "dosyada: $(cat sessiz.txt)"
# çıktı:
#   normal çıktı
#   dosyada: hata mesajı

Otomatik numara: {değişken}>

Otomatik numara: {değişken}>

Sabit numaralarla çalışmanın sorunu, bir kütüphane fonksiyonunun veya başka bir betiğin aynı numarayı kullanabilmesidir. Kılavuz bunun için {değişken} biçimini tanımlar: numara yerine süslü parantez içinde bir değişken adı yazarsanız kabuk 10 veya daha büyük, boş bir tanımlayıcı seçer ve numarasını o değişkene atar. Kapatma operatörleriyle (>&- ve <&-) kullanıldığında ise değişkenin değeri kapatılacak tanımlayıcıyı belirtir.

bash
exec {kayit}> otomatik.txt
echo "fd 10 veya üstü mü: $(( kayit >= 10 ))"
echo "veri" >&"$kayit"
exec {kayit}>&-
cat otomatik.txt
# çıktı:
#   fd 10 veya üstü mü: 1
#   veri

Bu biçimin bir özelliği daha vardır: kılavuza göre {değişken} verildiğinde yönlendirme, exec kullanılmasa bile komutun kapsamının ötesinde kalıcı olur. Böylece tanımlayıcının ömrünü kendiniz yönetirsiniz. Aşağıda echo satırı bir dosya açıyor; tanımlayıcı satır bittikten sonra da açık kaldığı için ikinci echo aynı dosyaya devam ediyor. shopt -s varredir_close açıldığında ise kabuk bu tanımlayıcıları komut bitince kendiliğinden kapatır.

bash
echo "ilk" {fd}> kalici.txt
echo "ikinci" >&"$fd"
exec {fd}>&-
cat kalici.txt
# çıktı:
#   ilk
#   ikinci

Betiğin tüm çıktısını günlüğe almak ve geri dönmek

Uzun bir bakım betiğinde, belirli bir bölümün tüm çıktısının ve hatalarının bir günlük dosyasına gitmesini, sonra ekrana geri dönülmesini isteyebilirsiniz. Bunun için önce mevcut standart çıktı ve standart hata yedek numaralara kopyalanır, ardından exec > dosya 2>&1 ile ikisi de dosyaya yönlendirilir. Bölüm bitince yedekler geri taşınır.

bash
exec 5>&1 6>&2
exec > betik.log 2>&1
echo "bu satır dosyaya gider"
echo "bu hata da dosyaya gider" >&2
exec 1>&5- 2>&6-
echo "ekrana döndük"
cat betik.log
# çıktı:
#   ekrana döndük
#   bu satır dosyaya gider
#   bu hata da dosyaya gider

Geri dönüş satırındaki 1>&5- kılavuzdaki taşıma operatörüdür: [n]>&rakam- önce rakam tanımlayıcısını n numarasına kopyalar, sonra rakam numarasını kapatır. Yani exec 1>&5 5>&- ile aynı işi tek adımda yapar. Girdi tarafındaki karşılığı [n]<&rakam- biçimidir. Yedek tanımlayıcıyı kapatmayı unutursanız, betiğin başlattığı her program bu fazladan açık tanımlayıcıyı devralır; kapatmak düzenli bir alışkanlıktır.

Standart çıktı ile standart hatanın yerini değiştirmek

Bazen bir komutun hata akışını boruya vermek, normal çıktısını ise ekranda bırakmak istersiniz. Bunun için üçüncü bir tanımlayıcı geçici bir kap olarak kullanılır: 3>&1 1>&2 2>&3 3>&-. Sırayla okunursa: 3 numara, standart çıktının o anki hedefini saklar; 1 numara, standart hatanın hedefine yönlendirilir; 2 numara, 3'te saklanan eski standart çıktıya bağlanır; son olarak 3 kapatılır. Yönlendirmeler soldan sağa işlendiği için bu sıra önemlidir.

bash
f() { echo "normal çıktı"; echo "hata mesajı" >&2; }
{ f 3>&1 1>&2 2>&3 3>&- ; } 2> /dev/null
# çıktı: hata mesajı

Örnekte dıştaki 2> /dev/null önce uygulandığı için takas sonrasında normal çıktı /dev/null dosyasına gidiyor, hata mesajı ise standart çıktıya geçip ekrana basılıyor. Yalnızca hata akışını boruya vermek için daha kısa bir yol da vardır: komut 2>&1 > /dev/null | diğer. Bu kalıbın açıklaması pipe yazısında var.

Okuma ve yazma için açmak: <>

[n]<>sözcük operatörü dosyayı hem okumak hem yazmak için açar; n verilmezse 0 numara kullanılır. Kılavuza göre dosya yoksa oluşturulur. > operatöründen farkı, dosyanın sıfırlanmamasıdır: yazma, dosyanın başından başlar ve var olan baytların üzerine yazar, geri kalanına dokunmaz. Aşağıdaki örnekte ilk üç harf değişiyor, satırın geri kalanı yerinde duruyor.

bash
printf 'merhaba dünya\n' > rw.txt
exec 3<> rw.txt
printf 'MER' >&3
exec 3>&-
cat rw.txt
# çıktı: MERhaba dünya

Bu davranış, sabit uzunluklu bir kaydı yerinde güncellemek gibi özel durumlar dışında pek gerekmez; çoğu betikte dosyayı okumak için <, yazmak için > veya >> kullanmak daha anlaşılırdır. <> operatörünün bir kullanım alanı da okuma ve yazmanın aynı tanımlayıcıdan yapılması gereken özel dosyalardır.

İki dosyayı aynı döngüde okumak

Standart girdi tek olduğu için iki dosyayı aynı anda satır satır okumak boruyla mümkün değildir. Döngünün sonuna iki ayrı girdi yönlendirmesi yazıp her read komutunu farklı bir tanımlayıcıdan okutursanız iki dosya paralel ilerler. Döngü, dosyalardan biri bittiğinde && sayesinde sona erer.

bash
printf '%s\n' a b c > harf.txt
printf '%s\n' 1 2 3 > sayi.txt
while read -r h <&3 && read -r s <&4; do
  echo "$h$s"
done 3< harf.txt 4< sayi.txt
# çıktı:
#   a1
#   b2
#   c3

Bu kalıbın bir yararı daha vardır: döngünün standart girdisi serbest kaldığı için döngü içindeki bir komut kullanıcıdan girdi bekliyorsa veya kendi standart girdisini okuyorsa, okunacak dosyanın satırlarını yanlışlıkla tüketmez. Dosyalar yerine komut çıktılarını okumak için 3< <(komut) biçiminde süreç ikamesi kullanılabilir.

Tanımlayıcılar alt süreçlere geçer

Kılavuz, kabuğun çalışma ortamının bir parçası olarak exec ile değiştirilmiş açık dosyaları sayar ve harici bir komut çalıştırıldığında kabuğun açık dosyalarının, komuta özgü yönlendirmelerle birlikte o komuta geçtiğini belirtir. Alt kabuklar da aynı tanımlayıcıları devralır. Aşağıda parantezle açılan alt kabuk, dışarıda açılan 3 numaraya yazabiliyor.

bash
( echo "alt kabuktan" >&3 ) 3> alt.txt
cat alt.txt
# çıktı: alt kabuktan

Bunun pratik bir sonucu vardır: bir betikte açık bıraktığınız tanımlayıcılar, betiğin başlattığı uzun ömürlü arka plan programlarına da geçer. Kapatılması gereken bir kilit dosyası veya günlük dosyası, betik bittiği halde o program çalıştığı sürece açık kalabilir. Bu nedenle işi biten tanımlayıcıyı kapatmak iyi bir alışkanlıktır. Alt kabuk ve çalışma ortamı kuralları subshell yazısında toplu olarak anlatılıyor.

Operatör özeti

OperatörAnlamı
exec 3> dosya3 numarayı yazmak için aç (dosya sıfırlanır)
exec 3>> dosya3 numarayı sona eklemek için aç
exec 4< dosya4 numarayı okumak için aç
exec 5<> dosya5 numarayı okuma ve yazma için aç, sıfırlama yok
komut >&3komutun standart çıktısını 3 numaraya yaz
read -u 4 x4 numaradan bir satır oku
exec 3>&-3 numarayı kapat
exec 1>&5-5 numarayı 1 numaraya taşı ve 5 numarayı kapat
exec {fd}> dosyaboş bir numara seç, fd değişkenine yaz
exec {fd}>&-fd değişkenindeki numarayı kapat

Sık yapılan hatalar

Sık yapılan hatalar: exec 3> dosya satırını bir döngünün içine koymak. Her turda dosya yeniden..., İşi biten tanımlayıcıyı kapatmamak. Açık kalan numara, betiğin sonradan..., >&3...
  • exec 3> dosya satırını bir döngünün içine koymak. Her turda dosya yeniden açılır ve sıfırlanır; açma işlemini döngünün dışına alın.
  • İşi biten tanımlayıcıyı kapatmamak. Açık kalan numara, betiğin sonradan başlattığı her programa geçer; exec 3>&- satırını unutmayın.
  • >&3 yerine > &3 yazmak. Boşluk yüzünden & işareti ayrı bir kontrol operatörü olarak okunur ve Bash "syntax error near unexpected token" hatası verir.
  • Değişkendeki numarayı dolar işareti olmadan yazmak. >&"$fd" doğru; >&fd ise kılavuzdaki kurala göre standart çıktıyı ve standart hatayı fd adlı bir dosyaya yönlendirir.
  • Sabit numara seçip bir kütüphanenin de aynı numarayı kullanması. Paylaşılan kodda {değişken} biçimini tercih edin.

Sık Sorulan Sorular

Kaç tanımlayıcı açabilirim?

Sınır Bash'ten değil işletim sisteminden gelir; kılavuz bir sayı vermez. Elle numara seçerken 3 ile 9 arasında kalmanız, daha fazlası için kabuğa numara seçtirmeniz önerilir.

exec ile yaptığım yönlendirme fonksiyonun içinde de geçerli mi?

Evet. exec mevcut kabuğun ortamını değiştirir ve fonksiyonlar aynı kabukta çalışır. Etkiyi bir bölümle sınırlamak istiyorsanız yönlendirmeyi süslü parantezle gruplanmış bir bloğa veya fonksiyon çağrısının kendisine yazın: { ...; } > dosya ya da fonksiyon > dosya.

/dev/fd/3 yazmak ile >&3 aynı şey mi?

Kılavuz /dev/fd/N dosya adının yönlendirmelerde N numaralı tanımlayıcıyı kopyaladığını, sistem bu dosyaları sağlamıyorsa Bash'in aynı davranışı kendi içinde taklit ettiğini söyler. Bash içinde iki yazım aynı sonucu hedefler; >&3 daha kısa ve sistemden bağımsızdır.

set -x çıktısını ayrı bir tanımlayıcıya yönlendirebilir miyim?

Evet. Kılavuzdaki BASH_XTRACEFD değişkenine açık bir tanımlayıcının numarasını atarsanız iz çıktısı standart hata yerine oraya yazılır. Ayrıntılar hata ayıklama ve set -x yazısında.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Bash öğrenme rehberi · Yönlendirme: stdout ve stderr · Here document ve here string · Process substitution · read ile kullanıcı girdisi · Hata ayıklama ve set -x