Here document, bir komuta verilecek çok satırlı girdiyi ayrı bir dosya açmadan doğrudan betiğin içine yazmanızı sağlar: komut <<EOF satırından sonra gelen satırlar, yalnızca EOF yazan bir satıra kadar komutun standart girdisi olur. Sınırlayıcı sözcüğü tırnaksız yazarsanız metindeki $degisken, $(komut) ve $(( )) genişletilir; <<'EOF' gibi tırnaklı yazarsanız metin olduğu gibi aktarılır. <<- biçimi satır başındaki sekmeleri siler, <<< "metin" biçimindeki here string ise tek bir metni sonuna yeni satır ekleyerek girdi yapar.

Bu yazı GNU Bash kılavuzunun Here Documents ve Here Strings başlıklarını temel alıyor. Genişletmenin hangi durumda yapılıp hangi durumda yapılmadığını, sınırlayıcı satırın neden tam olarak tek başına durması gerektiğini, girintili betiklerde <<- kullanımını, here document'ı değişkene ve dosyaya aktarmayı ve here string'in borudan farkını çalıştırılmış örneklerle anlatıyoruz. Genel yönlendirme kuralları için yönlendirme yazısına bakabilirsiniz.

Here document sözdizimi

Here document sözdizimi

Kılavuzdaki genel biçim [n]<<[-]sözcük şeklindedir. Ardından metnin satırları gelir ve metin, yalnızca sınırlayıcıyı içeren bir satırla biter. Sınırlayıcı satırda başka hiçbir şey olmamalıdır; sondaki bir boşluk bile o satırı sıradan bir metin satırına çevirir. Başta yazılan n isteğe bağlıdır ve metnin hangi dosya tanımlayıcısına verileceğini seçer; yazılmazsa standart girdi kullanılır. Geleneksel olarak EOF ya da SON gibi sözcükler seçilir, ama metnin içinde tek başına bir satır olarak geçmeyecek her sözcük iş görür.

Kılavuz, başlangıçtaki sözcük üzerinde değişken genişletmesi, komut ikamesi, aritmetik genişletme veya dosya adı genişletmesi yapılmadığını belirtir. Yani <<$SINIR yazıp sınırlayıcıyı bir değişkenden almaya çalışmak işe yaramaz; sözcük harfi harfine kullanılır. Sınırlayıcıyı metnin bitişinde de aynı şekilde yazmanız gerekir.

Tırnaksız sınırlayıcı: metin genişletilir

Sınırlayıcının hiçbir parçası tırnak içinde değilse metin, kılavuzun deyişiyle çift tırnaklı bir dizgeye benzer şekilde işlenir. Parametre genişletmesi, komut ikamesi ve aritmetik genişletme yapılır. Ters eğik çizgi yalnızca \, $ ve ters tırnak karakterlerinin önünde özel anlam taşır; dolar işaretini metinde aynen görmek isterseniz \$ yazarsınız. Çift tırnak karakterlerinin ise burada özel bir anlamı yoktur, metinde göründükleri gibi kalırlar. Bu, çift tırnaklı bir dizgeden önemli bir farktır.

bash
ad="Ayşe"
cat <<EOF
Merhaba $ad
Bugün $((3 + 4)) görev var.
Tırnak "olduğu gibi" kalır.
Dolar işareti: \$HOME
EOF
# çıktı:
#   Merhaba Ayşe
#   Bugün 7 görev var.
#   Tırnak "olduğu gibi" kalır.
#   Dolar işareti: $HOME

Bu davranış, şablon metinleri doldurmak için çok kullanışlıdır: e-posta gövdesi, yapılandırma dosyası ya da kullanım mesajı gibi metinleri değişkenlerle birleştirirken her satır için ayrı echo yazmak zorunda kalmazsınız. Joker karakterler (*, ?) ise genişletilmez; metindeki *.txt bir dosya listesine dönüşmez.

Tırnaklı sınırlayıcı: metin olduğu gibi kalır

Sınırlayıcının herhangi bir parçasını tırnaklarsanız iki şey olur. Birincisi, gerçek sınırlayıcı tırnakları kaldırılmış hali olur: <<'EOF' için bitiş satırına yine EOF yazarsınız. İkincisi, metnin satırlarında hiçbir genişletme yapılmaz. Bu, içinde dolar işareti, ters eğik çizgi veya başka bir betiğin kodu bulunan metinleri yazdırırken tam olarak istediğiniz şeydir.

bash
ad="Ayşe"
cat <<'EOF'
Merhaba $ad
Bugün $((3 + 4)) görev var.
Komut: $(date)
EOF
# çıktı:
#   Merhaba $ad
#   Bugün $((3 + 4)) görev var.
#   Komut: $(date)

Tırnaklamanın biçimi önemli değildir. Tek tırnak, çift tırnak, ters eğik çizgi ya da sözcüğün yalnızca bir kısmını tırnaklamak aynı sonucu verir. Aşağıdaki üç örnekte de sınırlayıcı SON olur ve $ad genişletilmez.

bash
ad="Ayşe"
cat <<"SON"
1: $ad
SON
cat <<\SON
2: $ad
SON
cat <<S"ON"
3: $ad
SON
# çıktı:
#   1: $ad
#   2: $ad
#   3: $ad

Satır devamı ve ters eğik çizgi

Tırnaksız sınırlayıcıyla yazılan metinde satır sonundaki ters eğik çizgi, satırı bir sonrakine bağlar: ters eğik çizgi ve yeni satır karakteri silinir, iki satır birleşir. Kılavuz bu birleştirmenin metin okunurken, bitiş satırı aranmadan önce yapıldığını da not eder. Sınırlayıcı tırnaklıysa ters eğik çizgi metnin sıradan bir karakteridir ve satırlar ayrı kalır.

bash
cat <<EOF
bir \
iki
EOF
cat <<'EOF'
bir \
iki
EOF
# çıktı:
#   bir iki
#   bir \
#   iki

Girintili betikler için <<-

Girintili betikler için <<-

Bir fonksiyonun veya if bloğunun içinde here document yazdığınızda metin satırları ve bitiş satırı sola yaslanmak zorunda kalır, bu da okunurluğu bozar. Operatörü <<- olarak yazarsanız kabuk metin satırlarının ve sınırlayıcı satırın başındaki sekme (tab) karakterlerini siler. Böylece metni kodun geri kalanıyla aynı hizada girintileyebilirsiniz. Aşağıdaki örnekte girintiler sekme karakteridir.

bash
if true; then
	cat <<-SON
		Girintili birinci satır
		Girintili ikinci satır
	SON
fi
# çıktı:
#   Girintili birinci satır
#   Girintili ikinci satır

Bitiş satırı bulunamazsa ne olur

Sınırlayıcı satırın sonunda görünmez bir boşluk kaldığında ya da bitiş satırını tamamen unuttuğunuzda Bash metni dosyanın sonuna kadar okur. Betik çalışmaya devam eder ama bir uyarı basar ve dosyanın geri kalanı metne dahil olur. Aşağıdaki betikte son satır EOF biçiminde, sonunda bir boşlukla yazılmıştır:

bash
cat <<EOF
merhaba
EOF 

Çıktısı şöyledir; uyarı standart hataya, metin standart çıktıya yazılır:

bash
ornek.sh: line 3: warning: here-document at line 1 delimited by end-of-file (wanted `EOF')
merhaba
EOF 

Bu uyarıyı gördüğünüzde ilk bakmanız gereken yer, sınırlayıcı satırın sonundaki boşluklar ve satır sonu karakteridir. Windows'ta düzenlenmiş dosyalarda her satırın sonunda görünmez bir satırbaşı karakteri bulunabilir; o durumda da bitiş satırı EOF değil EOF artı bir karakter olur. Bu ve benzeri satır sonu sorunlarının belirtileri sık hata mesajları yazısında anlatılıyor.

Here document'ı dosyaya, döngüye ve değişkene aktarmak

Here document bir yönlendirmedir, dolayısıyla diğer yönlendirmelerle birleştirilebilir. En yaygın kullanım, küçük bir yapılandırma dosyasını betik içinden üretmektir: cat > dosya <<EOF satırında cat metni standart girdiden okur ve standart çıktıya yazar, standart çıktı da dosyaya yönlendirilmiştir. Yönlendirmelerin sırası burada sonucu değiştirmez, cat <<EOF > dosya da aynı işi yapar.

bash
sunucu="ornek.local"
port=8080
cat > ayar.conf <<EOF
host=$sunucu
port=$port
EOF
cat ayar.conf
# çıktı:
#   host=ornek.local
#   port=8080

Here document bir döngünün girdisi de olabilir. while read döngüsünün done sözcüğünden sonra yazılan yönlendirme bütün döngüye uygulanır ve her read çağrısı bir satır okur. Satırı alanlara bölme kuralları ve -r seçeneğinin neden önemli olduğu read ile kullanıcı girdisi yazısında, döngünün kendisi ise while döngüsü yazısında anlatılıyor.

bash
while read -r ad rol; do
  echo "$ad: $rol"
done <<EOF
ayse yonetici
mehmet editor
EOF
# çıktı:
#   ayse: yonetici
#   mehmet: editor

Metni bir değişkene almak için iki yaygın yol vardır. Birincisi here document'ı komut ikamesinin içine koymaktır; sondaki yeni satırlar komut ikamesi tarafından silinir. İkincisi read -r -d '' kullanmaktır: boş sınırlayıcı NUL karakterine kadar okumak anlamına gelir, metinde NUL olmadığı için read dosya sonuna kadar okur. Kılavuza göre read dosya sonuyla karşılaştığında sıfırdan farklı bir durum döndürür; değişken dolar ama çıkış durumu 1 olur. Aşağıdaki çıktıda bunu görüyorsunuz.

bash
metin=$(cat <<'EOF'
satır bir
satır iki
EOF
)
echo "$metin"
# çıktı:
#   satır bir
#   satır iki
read -r -d '' sablon <<'EOF'
Sayın $ad,
EOF
echo "read durumu: $?"
echo "$sablon"
# çıktı:
#   read durumu: 1
#   Sayın $ad,

Kullanım mesajı yazmak

Here document'ın belki de en yaygın kullanımı, betiğin kullanım (yardım) mesajıdır. Mesajı bir fonksiyonun içine koyarsanız hem seçenek ayrıştırma kodunda hem hata durumlarında aynı metni tek satırla basabilirsiniz. Tırnaksız sınırlayıcı sayesinde $0 betiğin çağrıldığı adla doldurulur; betiği yeniden adlandırsanız bile mesaj doğru kalır. Metnin içindeki girintiler boşluk olduğu için olduğu gibi korunur. Seçenekleri ayrıştırma kısmı getopts yazısında, $0 ve diğer özel parametreler ise script argümanları yazısında anlatılıyor.

bash
kullanim() {
  cat <<EOF
Kullanım: $0 [-v] dosya
  -v  ayrıntılı çıktı
EOF
}
kullanim
# çıktı:
#   Kullanım: ornek.sh [-v] dosya
#     -v  ayrıntılı çıktı

Kullanım mesajını hata durumunda basıyorsanız, kullanim >&2 biçiminde standart hataya yönlendirip ardından sıfırdan farklı bir kodla çıkmak yerleşik bir alışkanlıktır. Kullanıcı yardımı açıkça istediğinde ise mesaj standart çıktıya gider ve betik 0 ile biter.

Here string: <<<

Here string, here document'ın tek satırlık kardeşidir: komut <<< sözcük. Kılavuza göre sözcük üzerinde tilde genişletmesi, parametre ve değişken genişletmesi, komut ikamesi, aritmetik genişletme ve tırnak kaldırma yapılır; dosya adı genişletmesi ve sözcük bölme yapılmaz. Sonuç tek bir dizge olarak, sonuna bir yeni satır eklenerek komutun standart girdisine verilir.

bash
read -r ilk ikinci <<< "elma armut kiraz"
echo "ilk=$ilk ikinci=$ikinci"
# çıktı: ilk=elma ikinci=armut kiraz
metin="bir iki üç"
wc -w <<< "$metin"
# çıktı: 3
wc -c <<< "abc"
# çıktı: 4

Son satırdaki 4 sonucu, eklenen yeni satırın kanıtıdır: abc üç karakterdir, wc -c dördüncü karakter olarak yeni satırı sayar. Bu ayrıntı, girdinin bayt sayısının önemli olduğu durumlarda (örneğin bir özet değeri hesaplarken) farklı sonuç almanıza yol açabilir; o durumda printf '%s' ile boru kullanmak daha doğrudur. Türkçe harfler UTF-8'de birden fazla bayt tuttuğu için wc -c karakter değil bayt saydığını da unutmayın.

Kılavuzdaki "dosya adı genişletmesi ve sözcük bölme yapılmaz" kuralı, here string'de değişkeni tırnaksız yazmanın normalde olacağı kadar tehlikeli olmadığı anlamına gelir. Aşağıda *.txt değeri bir dosya listesine dönüşmeden aynen geçiyor. Yine de tırnak kullanmak alışkanlık olarak doğrudur. İkinci kısımdaki IFS=, read -r -a kalıbı, virgülle ayrılmış bir metni diziye bölmenin kısa bir yoludur; diziler için dizi kullanımı yazısına bakabilirsiniz.

bash
desen="*.txt"
cat <<< $desen
# çıktı: *.txt
IFS=, read -r -a parca <<< "a,b,c"
echo "${#parca[@]} parça: ${parca[1]}"
# çıktı: 3 parça: b

Here string ile boru arasındaki fark

echo "x y" | read a b satırı ilk bakışta read a b <<< "x y" ile aynı görünür, ama sonuç farklıdır. Kılavuz, birden fazla komut içeren bir borudaki her komutun kendi alt kabuğunda çalıştığını söyler. read alt kabukta değişkenleri doldurur, alt kabuk biter ve değerler kaybolur. Here string ise boru açmaz; read mevcut kabukta çalışır ve değişkenler kalır.

bash
echo "x y" | read -r a b
echo "boru sonrası: [$a]"
# çıktı: boru sonrası: []
read -r a b <<< "x y"
echo "here string sonrası: [$a]"
# çıktı: here string sonrası: [x]

Aynı sorunun çok satırlı veya bir komuttan gelen versiyonu için process substitution kullanılır; borudaki alt kabuk davranışının ayrıntıları ve lastpipe seçeneği ise pipe yazısında anlatılıyor.

Hangi durumda hangisi

Hangi durumda hangisi: <<EOF, <<'EOF', <<-EOF, <<< "$metin"
BiçimGenişletmeNe zaman
<<EOFdeğişken, komut ikamesi, aritmetikdeğişkenlerle doldurulan şablon metin
<<'EOF'yok, metin aynenkod, dolar işaretli metin, başka betik üretmek
<<-EOFsınırlayıcı tırnaksızsa vargirintili blok içinde, sekmeyle girintileme
<<< "$metin"değişken, komut ikamesi; bölme ve glob yoktek satırlık girdi, read ile ayrıştırma
echo metin | komutecho argümanlarına bağlıkomut alt kabukta çalışabilir, değişken kaybı olur

Sık yapılan hatalar

  • Bitiş satırını girintilemek veya sonuna boşluk bırakmak. Bitiş satırı yalnızca sınırlayıcıyı içermeli; <<- kullanıyorsanız başında yalnızca sekme olabilir.
  • Şablon metnin içindeki dolar işaretlerini unutmak. Tırnaksız sınırlayıcıda $5 gibi bir fiyat ifadesi beşinci konum parametresine genişler ve büyük ihtimalle boş kalır. Ya \$5 yazın ya da sınırlayıcıyı tırnaklayın.
  • Çift tırnakları kaçırmaya çalışmak. Here document içinde çift tırnak özel değildir; \" yazarsanız ters eğik çizgi metinde kalır.
  • Sınırlayıcıyı değişkenden almaya çalışmak. Başlangıç sözcüğü genişletilmez.
  • read -d '' sonucunun çıkış durumunu set -e altında gözden kaçırmak.

Sık Sorulan Sorular

EOF zorunlu mu?

Hayır. Sınırlayıcı herhangi bir sözcük olabilir; EOF yalnızca alışılmış bir seçimdir. Önemli olan, aynı sözcüğün metnin içinde tek başına bir satır olarak geçmemesi ve bitiş satırında birebir aynı yazılmasıdır.

Here document'ı sudo ile bir dosyaya nasıl yazarım?

sudo cat > /etc/dosya <<EOF çalışmaz, çünkü yönlendirmeyi yetkisiz kabuğunuz yapar. Bu durumda metin, yetkili çalışan bir programa (sık kullanılanı tee) standart girdi olarak verilir. Sistem dosyalarına yazmak bu serinin kapsamı dışında; sunucu yönetimi için Linux sunucu yönetimi temelleri yazısına bakabilirsiniz.

Here document ile here string arasında performans farkı var mı?

Kılavuz bu konuda bir ölçüm vermiyor, biz de vermeyeceğiz. Seçimi okunurluğa göre yapın: tek satır için here string, çok satır için here document.

Here document içine yorum yazabilir miyim?

Hayır, en azından kabuğun yorumu olarak değil. Here document'ın satırları kabuk kodu değil, komuta verilecek veridir; # ile başlayan bir satır da olduğu gibi komuta gider. Metni açıklamak istiyorsanız yorumu here document'ın başladığı satırdan önceye yazın. Kabuğun yorum kuralları için script yazma ve çalıştırma yazısına bakabilirsiniz.

Metni başka bir dosya tanımlayıcısına verebilir miyim?

Evet, kılavuzdaki [n] öneki bunu sağlar. 3<<EOF yazarsanız metin 3 numaralı tanımlayıcıdan okunabilir hale gelir ve döngü içinde read -u 3 ile okunur. Bu sayede döngünün standart girdisi başka bir iş için serbest kalır. Ayrıntılar dosya tanımlayıcıları 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 · read ile kullanıcı girdisi · Tek tırnak ve çift tırnak farkı · Komut ikamesi · Process substitution