Bash'te kullanıcıdan veya bir dosyadan girdi almak için read yerleşik komutu kullanılır. read -r ad standart girdiden bir satır okur ve ad değişkenine atar; -p bir soru metni gösterir, -s yazılanı ekranda göstermez, -t zaman aşımı koyar, -a satırı kelimelere bölüp bir diziye yerleştirir. Hemen her durumda -r seçeneğini eklemeniz önerilir; aksi halde ters eğik çizgiler silinir.

Bu yazıda read komutunun satırı nasıl böldüğünü, IFS değişkeninin bu işi nasıl yönettiğini, seçeneklerin tamamını ve döngülerde sık karşılaşılan iki tuzağı (son satırın kaybolması ve boru hattında değişkenin kaybolması) örneklerle anlatıyoruz. Örneklerde girdiyi klavyeden yazmak yerine <<< ile veriyoruz; bu biçim here string yazısında anlatılıyor. Serinin diğer konuları için Bash öğrenme rehberi sayfasına bakın.

read satırı nasıl böler?

read satırı nasıl böler?

Kılavuza göre read bir satır okur, onu kabuğun kelime bölme kurallarıyla kelimelere ayırır ve ilk kelimeyi ilk değişkene, ikinci kelimeyi ikinci değişkene atar. Kelime sayısı değişken sayısından fazlaysa geri kalan kelimeler, aralarındaki ayırıcılarla birlikte son değişkene gider. Kelime sayısı azsa fazladan değişkenler boş değer alır:

bash
read -r ad soyad <<< "Ayşe Yılmaz Demir"
echo "ad=[$ad] soyad=[$soyad]"
# çıktı: ad=[Ayşe] soyad=[Yılmaz Demir]
read -r a b c <<< "tek"
echo "a=[$a] b=[$b] c=[$c]"
# çıktı: a=[tek] b=[] c=[]

Hiç değişken adı vermezseniz okunan satır REPLY değişkenine atanır. Kılavuz burada önemli bir ayrıntı verir: REPLY, satırı sondaki ayırıcı hariç hiç değiştirmeden alır. Bir değişken adı verdiğinizde ise satır kelime bölmeden geçtiği için baştaki ve sondaki boşluklar kırpılır. Satırı olduğu gibi bir değişkene almak istiyorsanız IFS değişkenini yalnızca o komut için boşaltırsınız:

bash
read <<< "  boşluklu  "
echo "[$REPLY]"
# çıktı: [  boşluklu  ]
read -r s <<< "  boşluklu  "
echo "[$s]"
# çıktı: [boşluklu]
IFS= read -r s <<< "  boşluklu  "
echo "[$s]"
# çıktı: [  boşluklu  ]

IFS= read -r satir kalıbı, bir dosyayı satır satır ve içeriğini bozmadan okumanın standart yoludur. IFS= ataması komutun önüne yazıldığı için yalnızca o read çağrısı sırasında geçerlidir; kabuğun kendi IFS değeri değişmez.

-r seçeneği neden hep önerilir?

Kılavuza göre -r verilmediğinde ters eğik çizgi, okunan bir sonraki karakterin özel anlamını kaldırır ve satır devamı için kullanılır. Yani girdideki ters eğik çizgiler silinir ve satır sonunda bir ters eğik çizgi varsa bir sonraki satır aynı satırın devamı sayılır. Windows yolları, düzenli ifadeler veya kaçış dizisi içeren metinler okurken bu davranış veriyi sessizce değiştirir:

bash
read x <<< 'C:\yeni\klasor'
echo "$x"
# çıktı: C:yeniklasor
read -r y <<< 'C:\yeni\klasor'
echo "$y"
# çıktı: C:\yeni\klasor
printf 'bir \\\niki\n' | { read z; echo "[$z]"; }
# çıktı: [bir iki]

Son satırda girdi iki satırdan oluşuyordu ve ilk satır ters eğik çizgiyle bitiyordu; -r olmadığı için read iki satırı birleştirdi. Satır devamını bilerek kullanmak istemediğiniz sürece -r yazın.

IFS ile alanlara bölmek

IFS (Internal Field Separator), kılavuza göre kabuğun genişletmeler sırasında kelimeleri bölerken ve read komutunun satırları kelimelere ayırırken kullandığı ayırıcı karakterlerin listesidir. Varsayılan değeri boşluk, sekme ve yeni satırdır. Bunu tek bir read çağrısı için değiştirerek iki nokta, virgül veya sekmeyle ayrılmış verileri okuyabilirsiniz:

bash
IFS=: read -r kullanici _ uid gid geri <<< "ayse:x:1001:1001:Ayşe Y:/home/ayse:/bin/bash"
echo "$kullanici $uid"
# çıktı: ayse 1001
echo "kalan: $geri"
# çıktı: kalan: Ayşe Y:/home/ayse:/bin/bash

İkinci alanı _ adlı bir değişkene okumak, o alanla ilgilenmediğinizi göstermenin yaygın bir yoludur. Son değişken, kalan bütün alanları ayırıcılarıyla birlikte aldı. Kelime bölme kurallarında bir fark daha vardır: boşluk, sekme ve yeni satır gibi IFS boşlukları art arda geldiğinde tek bir ayırıcı sayılır; iki nokta veya virgül gibi boşluk olmayan ayırıcılar ise yan yana geldiğinde aralarında boş bir alan oluşturur.

bash
IFS=, read -ra alan <<< "elma,armut,,kiraz"
echo "${#alan[@]} alan"
# çıktı: 4 alan
declare -p alan
# çıktı: declare -a alan=([0]="elma" [1]="armut" [2]="" [3]="kiraz")
read -ra kelime <<< "  bir    iki   "
echo "${#kelime[@]} kelime"
# çıktı: 2 kelime

-a dizi seçeneği kelimeleri sırayla dizinin 0'dan başlayan indekslerine atar; kılavuza göre atamadan önce dizideki tüm elemanlar silinir ve başka değişken adları yok sayılır. Dizilerle çalışmanın geri kalanı dizi yazısında, bir dosyanın bütün satırlarını tek seferde diziye okumak ise mapfile yazısında anlatılıyor.

Satır sonu yerine başka bir ayırıcı: -d, -n, -N

Satır sonu yerine başka bir ayırıcı: -d, -n, -N

-d ayırıcı seçeneği, satırın yeni satır yerine verilen karakterin ilk harfiyle bitmesini sağlar; ayırıcı boş dizge verilirse read NUL karakterinde durur. Bu ikinci kullanım, adlarında yeni satır bile bulunabilen dosya listelerini güvenle okumanın yoludur. -n sayı, en fazla o kadar karakter okuyup döner ama daha önce ayırıcıya rastlarsa orada durur. -N sayı ise ayırıcıları yok sayar ve tam olarak o kadar karakter okur; sonuç IFS ile bölünmez:

bash
read -r -d ';' ilk <<< "bir;iki"
echo "$ilk"
# çıktı: bir
read -r -n 3 x <<< "abcdef"
echo "$x"
# çıktı: abc
read -r -N 4 y <<< $'ab\ncd'
printf '%q\n' "$y"
# çıktı: $'ab\nc'
printf 'a.txt\0b c.txt\0' | while IFS= read -r -d '' dosya; do echo "[$dosya]"; done
# çıktı:
#   [a.txt]
#   [b c.txt]

-N 4 örneğinde yeni satır da bir karakter olarak sayıldı ve sonuca girdi. -n 1 kalıbı, kullanıcının Enter tuşuna basmasını beklemeden tek tuşla cevap vermesini istediğiniz menülerde kullanılır.

Etkileşimli seçenekler: -p, -s, -t, -e, -i

-p metin, girdi okunmadan önce sonunda yeni satır olmayan bir soru metni gösterir. Kılavuz bunun yalnızca girdi bir terminalden geliyorsa yapıldığını belirtir; girdi bir dosyadan veya borudan geliyorsa soru metni yazılmaz. Bu, betiğinizi otomatik çalıştırdığınızda günlüklerin soru metinleriyle dolmamasını sağlar:

bash
read -r -p "Adınız: " ad <<< "Ayşe"
echo "Merhaba $ad"
# çıktı: Merhaba Ayşe

-s sessiz kiptir: girdi terminalden geliyorsa yazılan karakterler ekranda gösterilmez; parola gibi değerler için kullanılır. Kullanıcı Enter'a bastığında yeni satır da ekrana basılmadığı için ardından bir echo eklemek imleci alt satıra taşır. Aşağıdaki oturumu terminalde kendiniz deneyebilirsiniz; ilk satırda parolayı yazdığınızda ekranda bir şey görünmez:

bash
$ read -r -s -p "Parola: " parola; echo
Parola:
$ echo "${#parola} karakter girildi"
8 karakter girildi

-t saniye, verilen sürede tam bir satır gelmezse read komutunun başarısız olmasını sağlar; süre ondalıklı olabilir. Kılavuza göre zaman aşımında okunan kısmi girdi değişkene yazılır ve çıkış durumu 128'den büyük olur. Bu seçenek terminal, boru veya başka özel dosyalardan okurken çalışır; normal dosyalardan okurken etkisi yoktur. -t 0 hiç okuma yapmadan hemen döner ve okunacak girdi olup olmadığını çıkış durumuyla bildirir. TMOUT değişkeni sıfırdan büyük bir değere sahipse read onu varsayılan zaman aşımı olarak kullanır.

bash
{ sleep 2; echo geç; } | {
  read -r -t 0.5 x
  durum=$?
  (( durum > 128 )) && echo "zaman aşımı, x=[$x]"
}
# çıktı: zaman aşımı, x=[]

-e girdi terminalden geliyorsa satırı Readline ile okur; böylece kullanıcı ok tuşlarıyla düzenleme yapabilir ve dosya adlarını Tab ile tamamlayabilir. -i metin ise Readline kullanılıyorsa düzenleme alanına başlangıç metni koyar; read -e -i "/var/www" -p "Dizin: " dizin kullanıcıya düzenlenebilir bir varsayılan değer sunar. Readline kısayolları Readline yazısında anlatılıyor.

Çıkış durumu ve son satır tuzağı

read, dosya sonuna gelindiğinde, zaman aşımında (128'den büyük değerle), bir atama hatasında veya -u ile geçersiz bir dosya tanımlayıcısı verildiğinde sıfırdan farklı bir durumla döner. while read döngüleri bu sayede dosya bitince kendiliğinden durur. Ancak bir ayrıntı vardır: dosyanın son satırı yeni satırla bitmiyorsa read o satırı değişkene yazar ama dosya sonuna ulaştığı için başarısızlık döndürür. Döngü bu satırı işlemeden biter:

bash
read -r y < /dev/null
# $? = 1
printf 'a\nb' | while read -r s; do echo "satır: $s"; done
# çıktı: satır: a
printf 'a\nb' | while read -r s || [[ -n $s ]]; do echo "satır: $s"; done
# çıktı:
#   satır: a
#   satır: b

Düzeltme, koşula || [[ -n $s ]] eklemektir: okuma başarısız olsa bile değişkende veri varsa döngü bir tur daha döner. Dosyaları satır satır okuyan döngülerin ayrıntıları while ve until yazısında anlatılıyor.

Boru hattına read: değişken neden boş?

Boru hattına read: değişken neden boş?

echo x | read v yazdığınızda v değişkeni sonraki satırda boş görünür. Bunun nedeni kılavuzun çalışma ortamı bölümünde açıklanır: bir boru hattının parçası olarak çalışan yerleşik komutlar bir alt kabukta çalışır; lastpipe seçeneğine bağlı olarak son eleman bu kuralın dışında kalabilir. read değişkeni alt kabukta doldurur ve alt kabuk bitince değer kaybolur:

bash
echo "değer" | read -r v
echo "[${v-}]"
# çıktı: []
read -r v <<< "değer"
echo "[$v]"
# çıktı: [değer]
read -r w < <(echo "süreç ikamesi")
echo "[$w]"
# çıktı: [süreç ikamesi]

Çözüm, girdiyi boru hattı yerine here string, dosya yönlendirmesi veya süreç ikamesiyle (<( )) vermektir. Süreç ikamesi process substitution yazısında, alt kabuk kavramı subshell yazısında, lastpipe ise pipe ve pipefail yazısında anlatılıyor.

Uygulama: basit bir ayar dosyasını okumak

Aşağıdaki örnek, anahtar=değer satırlarından oluşan bir ayar dosyasını okur. Yorum satırlarını ve boş satırları atlar, anahtardaki boşlukları temizler ve değerin içindeki ikinci bir eşittir işaretini korur. Bu sonuncusu, son değişkenin kalan her şeyi alması kuralı sayesinde kendiliğinden çalışır:

bash
printf '%s\n' '# ayarlar' 'port=8080' 'ad = site' '' 'yol=/var/www=eski' > ayar.conf
while IFS='=' read -r anahtar deger; do
  [[ -z $anahtar || $anahtar == \#* ]] && continue
  anahtar=${anahtar// /}
  deger=${deger# }
  printf '%-5s : %s\n' "$anahtar" "$deger"
done < ayar.conf
# çıktı:
#   port  : 8080
#   ad    : site
#   yol   : /var/www=eski

Girdi done < ayar.conf ile döngünün tamamına verildiği için döngü mevcut kabukta çalışır ve döngü içinde atadığınız değişkenler döngüden sonra da kullanılabilir. Gerçek bir uygulamada okunan anahtarları bir ilişkisel dizide toplamak işinizi kolaylaştırır; bu yöntem ilişkisel dizi yazısında anlatılıyor.

Uygulama: onay sorusu

Silme veya üzerine yazma gibi geri alınamayan işlemlerden önce kullanıcıdan onay almak yaygın bir ihtiyaçtır. Aşağıdaki fonksiyon tek tuşluk bir cevap okur, cevabı case ile değerlendirir ve sonucu çıkış durumu olarak döndürür. Örnekte girdiyi here string ile veriyoruz; terminalde çalıştırdığınızda soru metni görünür ve tek tuş beklenir:

bash
onay() {
  local cevap
  read -r -n 1 -p "$1 [e/h] " cevap
  [[ -t 0 ]] && echo
  case $cevap in
    [eE]) return 0 ;;
    *) return 1 ;;
  esac
}
onay "Devam edilsin mi?" <<< "e" && echo "devam ediliyor"
# çıktı: devam ediliyor
onay "Silinsin mi?" <<< "h" || echo "vazgeçildi"
# çıktı: vazgeçildi

[[ -t 0 ]] && echo satırı, standart girdi bir terminalse imleci kullanıcının bastığı tuştan sonra alt satıra indirir; girdi bir terminal olmadığında soru metni de gösterilmediği için bu satıra gerek kalmaz ve çıktı temiz kalır. case desenleri case esac yazısında, return ile durum döndürmek ise fonksiyon yazısında anlatılıyor.

Dosyadan okurken kullanıcıya soru sormak: -u

Bir dosyayı while read döngüsüyle okurken döngünün içinde kullanıcıdan bir cevap almak istediğinizde sorun çıkar: döngünün standart girdisi dosyaya bağlı olduğu için içerideki read de dosyadan okur ve bir sonraki satırı yutar. Kılavuzdaki -u fd seçeneği bu sorunu çözer: read standart girdi yerine verilen dosya tanımlayıcısından okur. Dosyayı 3 numaralı tanımlayıcıdan verip döngüyü read -u 3 ile kurarsanız standart girdi kullanıcıya kalır. Örnekte kullanıcının cevaplarını here string ile veriyoruz:

bash
printf '%s\n' yedek.tar eski.log > liste.txt
while IFS= read -r -u 3 dosya; do
  read -r -p "$dosya silinsin mi? " cevap
  echo "$dosya: $cevap"
done 3< liste.txt <<< $'evet\nhayır'
# çıktı:
#   yedek.tar: evet
#   eski.log: hayır

Döngü dosya satırlarını 3 numaralı tanımlayıcıdan, cevapları ise standart girdiden aldı; iki akış birbirine karışmadı. Gerçek kullanımda cevaplar klavyeden gelir ve -p metni ekranda görünür. Dosya tanımlayıcılarını açmanın ve kapatmanın diğer yolları dosya tanımlayıcıları ve exec yazısında anlatılıyor.

Seçenek özeti

Seçenekİşlevi
-rTers eğik çizgiyi kaçış karakteri saymaz
-p metinTerminalden okurken soru metni gösterir
-sTerminalde yazılanı göstermez
-t saniyeZaman aşımı; aşılırsa durum 128 üstü
-n sayıEn fazla sayı kadar karakter okur, ayırıcıda durur
-N sayıTam sayı kadar karakter okur, ayırıcıyı yok sayar
-d ayırıcıSatırı ayırıcının ilk karakterinde bitirir; boşsa NUL
-a diziKelimeleri dizinin 0, 1, 2... indekslerine yazar
-u fdStandart girdi yerine verilen tanımlayıcıdan okur
-e, -i metinReadline ile okur, başlangıç metni verir

-u fd seçeneği, aynı döngüde hem bir dosyadan hem de klavyeden okumanız gerektiğinde işe yarar; dosya tanımlayıcılarını açmak ve kullanmak dosya tanımlayıcıları yazısında anlatılıyor.

Sık Sorulan Sorular

read komutunda -r neden kullanılır?

-r olmadan girdideki ters eğik çizgiler kaçış karakteri sayılır ve silinir; satır sonundaki ters eğik çizgi iki satırı birleştirir. -r girdiyi olduğu gibi okur. Ters eğik çizginin özel davranışını bilerek istemiyorsanız her zaman -r yazın.

read ile okunan satırın baştaki boşlukları neden kayboluyor?

Bir değişken adı verdiğinizde satır IFS ile kelime bölmeden geçer ve baştaki, sondaki IFS boşlukları kırpılır. Satırı olduğu gibi almak için IFS= read -r satir yazın veya değişken adı vermeden REPLY değişkenini kullanın.

read ile bir satırı diziye nasıl bölerim?

read -ra dizi <<< "$satir" satırı IFS karakterlerine göre böler ve parçaları dizi değişkeninin 0'dan başlayan indekslerine yazar. Virgülle ayrılmış bir satır için komutun önüne IFS=, eklemeniz yeterlidir. Kılavuza göre ayırıcı boş dizge olarak verilmediği sürece read girdideki NUL karakterlerini yok sayar; ikili veri okumak için uygun bir araç değildir.

Betiğim cron ile çalışırken read neden beklemiyor?

Zamanlanmış görevlerde standart girdi bir terminale bağlı değildir; read çoğu zaman hemen dosya sonuyla karşılaşır ve başarısız döner. -p metni de gösterilmez. Etkileşim gerektiren betikleri otomatik çalıştırırken değerleri argüman veya ortam değişkeniyle verin.

Bir komutun çıktısını read ile nasıl okurum?

Boru hattı yerine süreç ikamesi kullanın: read -r ilk < <(komut). Boru hattında read bir alt kabukta çalışır ve atadığı değişken komut bittikten sonra kaybolur.

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 · while döngüsü · Here string · mapfile · echo ve printf