Linux'ta yönlendirme: >, 2>&1 ve /dev/null
Crontab'a yapıştırdığınız >/dev/null 2>&1 neyi atıyor? Üç akışı, 2>&1 sırasını, cron'un /bin/sh kullanmasını ve &> satırını sh altında deneyerek öğrenin.
Kısa cevap: >/dev/null 2>&1 neyi atar?
Linux yönlendirme satırı >/dev/null 2>&1, bir komutun hem normal çıktısını hem de hata mesajlarını /dev/null dosyasına gönderir. Yani ikisini birden atar. Komut çalışır, ama ne yazdığını hiç görmezsiniz. Bir cron işi bozulduğunda sebebini de göremezsiniz, çünkü sebebi söyleyen hata satırı da aynı yere gitmiştir.
Bu satırı çoğumuz bir kurulum betiğinden ya da bir forum cevabından kopyalayıp crontab'a yapıştırdık. Bu rehberde satırı parça parça açıyoruz. Önce üç akışı tanıyacağız. Sonra her operatörü, hata yazacağını bildiğimiz bir komutla deneyeceğiz. En önemli konu sıradır: 2>&1 satırın neresinde durduğuna göre farklı iş yapar. Son bölümlerde sunucu tarafına geçiyoruz: cron, noclobber, exec ve sudo ile yönlendirme.
Bu yazıda hiçbir hata metnini önceden yazmıyorum. Komutları kendi sunucunuzda çalıştırın ve ekrana bakın. Önemli olan metnin ne dediği değil, hangi akıştan çıktığıdır.
Üç akış: 0, 1 ve 2
Linux'ta her program üç açık akışla başlar. stdin(3) kılavuz sayfası bunu şöyle anlatır: biri girdi için, biri çıktı için, biri de tanılama ve hata mesajları için. Bu akışların numaraları programın başlangıcında 0, 1 ve 2'dir. Bu numaralara dosya tanımlayıcısı (file descriptor, kısaca fd) denir.
- 0: standart girdi (stdin). Program buradan okur.
- 1: standart çıktı (stdout). Normal sonuçlar buraya yazılır.
- 2: standart hata (stderr). Hata ve uyarı mesajları buraya yazılır.
Terminalde çalışırken 1 ve 2 aynı yere, ekrana gider. Bu yüzden ikisini gözle ayıramazsınız. Yönlendirme yaptığınız anda fark ortaya çıkar.
Hata mesajlarının dili yerel ayara (locale) bağlıdır. locale(7) sayfasına göre LC_MESSAGES kategorisi, iletilerin hangi dilde gösterileceğini belirler. Sunucunuz Türkçe bir yerel ayarla çalışıyorsa bazı hatalar Türkçe görünebilir. Bu, akış numarasını değiştirmez. Hata metni hangi dilde olursa olsun 2 numaralı akıştan çıkar. Kendi ayarınızı locale komutuyla görebilirsiniz.
Deneme ortamı ve /dev/null
Denemeler için boş bir dizin açın:
mkdir -p ~/yonlendirme-deneme
cd ~/yonlendirme-denemeDeneme komutumuz şu:
ls /etc/passwd /yok-boyle-bir-dizin/etc/passwd her Linux sisteminde bulunur, bu yüzden ls onun adını standart çıktıya yazar. /yok-boyle-bir-dizin yoktur, bu yüzden ls bir hata satırını standart hataya yazar. Komutu önce yalın hâliyle çalıştırın. Ekranda iki satır görmelisiniz: biri dosya adı, biri hata. Hangisinin hangi akıştan geldiğini birazdan yönlendirme ile kanıtlayacağız.
/dev/null özel bir dosyadır. null(4) kılavuz sayfasına göre bu dosyaya yazılan veri atılır. Bu dosyadan okuma ise her zaman dosya sonu (EOF, end of file) döndürür. Kendiniz deneyin:
echo merhaba > /dev/null
cat /dev/nullcat hiçbir şey yazdırmamalı. merhaba hiçbir yerde saklanmadı. /dev/null dosyasına giden veri geri alınamaz.
> ve >>: standart çıktıyı dosyaya yazmak
> işaretinin önüne numara yazmazsanız kabuk onu 1 numaralı akışa, yani standart çıktıya uygular. Hedef dosya yoksa oluşturulur. Varsa içi boşaltılır ve baştan yazılır.
ls /etc/passwd /yok-boyle-bir-dizin > cikti.txt
cat cikti.txtBirinci komuttan sonra ekranda bir satır kalır. O satır hata satırıdır, çünkü > yalnızca 1 numarayı dosyaya bağladı. 2 numara hâlâ terminale gidiyor. cikti.txt içinde yalnızca standart çıktı vardır.
>> dosyanın içini boşaltmaz, sona ekler. Dosya yoksa oluşturur:
echo bir > sayilar.txt
echo iki >> sayilar.txt
cat sayilar.txtGünlük (log) dosyalarında >> kullanın. Yanlışlıkla > yazarsanız her çalıştırmada eski kayıtları silersiniz.
2> ve 2>>: hataları ayrı dosyaya almak
Operatörün önüne 2 yazınca aynı işlem 2 numaralı akışa uygulanır.
ls /etc/passwd /yok-boyle-bir-dizin 2> hatalar.txt
cat hatalar.txtBu kez ekranda dosya adı kalır. Hata satırı hatalar.txt içine gider. 2>> aynı işi ekleme kipinde yapar. Bir betiğin hatalarını ayrı bir dosyada biriktirmek için kullanışlıdır:
ls /etc/passwd /yok-boyle-bir-dizin 2>> hatalar.txt
cat hatalar.txtHataları atmak isterseniz hedef olarak /dev/null verin: 2>/dev/null. Bunu yalnızca hangi hatayı attığınızı bildiğinizde yapın.
2>&1 ne demek?
n>&m biçimi, m numaralı çıktı tanımlayıcısını n numaraya kopyalar. 2>&1 şunu söyler: 2 numara, 1 numaranın şu anda gittiği yere gitsin. Önemli kelime "şu anda" kelimesidir. Kopya, kabuk o yönlendirmeyi işlediği anda alınır. Sonradan 1 numarayı başka yere çevirirseniz 2 numara onu takip etmez.
Aynı aileden bir biçim daha vardır: n>&-, n numaralı tanımlayıcıyı kapatır. Günlük işlerde nadiren gerekir, ama bir betikte görürseniz ne olduğunu bilin.
Yönlendirme sırası neden önemli?
Bu yazının merkezi bu bölüm. Bash kılavuzu açıkça söyler: yönlendirmeler yazıldıkları sırayla, soldan sağa işlenir. POSIX standardı da aynısını ister: bir komutta birden fazla yönlendirme varsa baştan sona doğru değerlendirilir. Şimdi iki satırı arka arkaya çalıştırın:
ls /etc/passwd /yok-boyle-bir-dizin > ikisi.txt 2>&1
ls /etc/passwd /yok-boyle-bir-dizin 2>&1 > yalniz-cikti.txt
cat ikisi.txt
cat yalniz-cikti.txtİlk satır ekrana hiçbir şey yazmamalı. İkinci satırda hata satırı yine ekranda görünmeli, ama bu kez standart hatadan değil, standart çıktının eski hedefinden gelir. Dosyalara bakınca nedenini görürsünüz: ikisi.txt iki satır içerir, yalniz-cikti.txt yalnızca dosya adını. Kabuğun her satırda ne yaptığını adım adım yazalım.
> ikisi.txt 2>&1 için:
> ikisi.txt: 1 numara artıkikisi.txtdosyasına gidiyor.2>&1: 2 numara, 1 numaranın şu anki hedefini kopyalıyor. O hedefikisi.txtdosyası.
2>&1 > yalniz-cikti.txt için:
2>&1: 2 numara, 1 numaranın şu anki hedefini kopyalıyor. O hedef hâlâ terminal.> yalniz-cikti.txt: 1 numara dosyaya çevriliyor. 2 numara, birinci adımda kopyaladığı hedefe, yani 1 numaranın eski yerine yazmaya devam ediyor.
Bash kılavuzu ikinci durumu aynı mantıkla açıklar. Standart hata, standart çıktı dosyaya çevrilmeden önce onun kopyası yapıldı. Bu yüzden dosyaya yalnızca standart çıktı gider.
Bu fark terminalde de görünür: birinci satır ekrana hiçbir şey yazmaz, ikinci satırda ise hata satırı dosyaya girmez ve ekranda kalır. Ekrana bakarak ayıramadığınız tek şey, hata satırının 1 numaranın eski hedefinden geldiğidir, çünkü o hedef de terminaldir. Bunu görmek için komutun 1 numarasını dışarıdan bir boruya bağlayın. Hata satırı artık 1 numaranın eski hedefine, yani boruya yazılır:
ls /etc/passwd /yok-boyle-bir-dizin 2>&1 > yalniz-cikti.txt | wc -lwc yalnızca boruya giren satırları sayar. Dosya adı yalniz-cikti.txt içine gitti, bu yüzden sayılan satır hata satırıdır. Kural kısadır: önce hedefi yazın, sonra 2>&1 yazın.
Aynı mantık boru (pipe) için de geçerlidir. Bash kılavuzuna göre boru, birinci komutun standart çıktısını ikinci komutun standart girdisine bağlar. Hata akışı boruya kendiliğinden girmez. Hataları da boruya göndermek için 2>&1 yazmanız gerekir. Bash'te |& bunun kısaltmasıdır, yani 2>&1 | ile aynıdır.
ls /etc/passwd /yok-boyle-bir-dizin 2>&1 | wc -l
ls /etc/passwd /yok-boyle-bir-dizin | wc -lİki sayıyı karşılaştırın. İkinci satırda hata mesajı ekranda görünür, çünkü boruya girmedi ve wc onu saymadı. Komut yerine koymada da durum aynıdır. Bash kılavuzuna göre $(...) ifadesinin yerine komutun standart çıktısı konur. Bir hata mesajını değişkene almak için bu yüzden $(komut 2>&1) yazarsınız. Ayrıntılar Bash'te komut yerine koyma (command substitution) rehberinde.
&> ve &>>: Bash'in kısayolu
Bash, iki akışı tek dosyaya göndermek için kısa bir biçim sunar. &>dosya, >dosya 2>&1 ile aynı işi yapar. &>>dosya ise >>dosya 2>&1 gibi sona ekler. Bash kılavuzu >&dosya biçimini de tanır, ama iki biçimden &> olanı tercih eder.
ls /etc/passwd /yok-boyle-bir-dizin &> kisa.txt
cat kisa.txtkisa.txt, ikisi.txt ile aynı içeriği taşımalı. Bu biçim kısa ve okunaklıdır. Ama POSIX standardının yönlendirme listesinde yoktur. Bunun sunucuda neden önemli olduğunu bir sonraki bölümde göreceğiz.
Cron neden /bin/sh kullanır?
Cron, işlerinizi sizin giriş kabuğunuzla çalıştırmaz. Ubuntu 24.04'ün crontab(5) sayfası şöyle der: SHELL değişkeni /bin/sh olarak ayarlanır. Ubuntu 26.04'ün sayfasında da aynı kural vardır: SHELL varsayılan olarak /bin/sh olur. Yani terminalde Bash kullanıyor olsanız bile crontab satırınızı /bin/sh okur.
/bin/sh her dağıtımda aynı program değildir. Debian, Squeeze sürümünden beri /bin/sh bağlantısını dash'e (Debian Almquist shell) yönlendirir. Ubuntu'daki dash kılavuz sayfası da dash'i sistemin standart komut yorumlayıcısı ve POSIX uyumlu bir /bin/sh olarak tanıtır. Kendi sunucunuzda emin olmak için bağlantının nereye gittiğine bakın:
readlink -f /bin/shÇıktı dash ile bitiyorsa crontab satırlarınızı dash okuyor demektir.
&>/dev/null satırını sh ve bash altında deneyin
dash'in kılavuz sayfası desteklediği yönlendirmeleri tek tek sayar: >, >|, >>, <, <&, >&, <&-, >&-, <> ve here-doc. &> bu listede yoktur. Aynı satırı iki kabukta çalıştırın ve sonucu kendiniz görün:
sh -c 'ls /yok-boyle-bir-dizin &>/dev/null; echo bitti'
bash -c 'ls /yok-boyle-bir-dizin &>/dev/null; echo bitti'Bash satırı yalnızca bitti yazmalı, çünkü Bash &> biçimini tanır ve iki akışı da /dev/null dosyasına gönderir. sh satırında bitti ile birlikte hata satırını da görüyorsanız sebebi POSIX dilbilgisidir. POSIX'te &> diye bir operatör yoktur. & ise bir komut listesini bitiren ayırıcıdır. Standarda göre & ile biten bir liste alt kabukta, arka planda çalışır ve kabuk onu beklemeden bir sonraki komuta geçer.
Bu yüzden POSIX bir kabuk bu satırı iki ayrı komut olarak okur. Birincisi ls /yok-boyle-bir-dizin & komutudur: arka planda, hiçbir yönlendirme olmadan çalışan bir ls. İkincisi >/dev/null komutudur: komut adı olmayan, yalnızca bir yönlendirme taşıyan boş bir komut. ls hatasını hiç yönlendirilmemiş 2 numaralı akışa, yani terminale yazar. Arka planda çalıştığı için hata satırı bitti satırından önce de görünebilir, sonra da.
Testte sh satırı hatayı gösterdiyse, crontab'daki &>/dev/null de beklediğiniz işi yapmıyor demektir. İki doğru seçenek var:
- Taşınabilir biçimi yazın:
>/dev/null 2>&1. Bu biçim POSIX'tir. Hem dash hem Bash onu aynı şekilde okur. - Crontab'ın başına
SHELL=/bin/bashsatırını ekleyin.crontab(5)sayfasına göre crontab SHELL değişkenini değiştirebilir.
Birinci seçenek daha güvenlidir, çünkü satırı başka bir sunucuya taşıdığınızda da aynı şekilde çalışır.
Cron çıktısı nereye gider ve neden susturulur?
Cron, bir iş çıktı üretirse onu e-postayla gönderir. Ubuntu 24.04'ün crontab(5) sayfasına göre MAILTO tanımlı değilse posta crontab'ın sahibine gider. MAILTO boş tanımlıysa (MAILTO="") hiç posta gönderilmez. Postanın gerçekten bir kutuya ulaşıp ulaşmadığı ise sunucunun posta ayarlarına bağlıdır. İnsanların satır sonuna >/dev/null 2>&1 eklemesinin sebebi budur: her çalıştırmada gelen postayı durdurmak.
Sorun şu: bu satır hata mesajlarını da atar. İş bozulduğunda geriye hiçbir iz kalmaz. Daha iyi bir yol, normal çıktıyı atıp hataları bir dosyada tutmaktır:
0 3 * * * /usr/local/bin/yedek.sh >/dev/null 2>>/home/deniz/yedek-hata.logYa da her şeyi tek bir günlüğe ekleyin:
0 3 * * * /usr/local/bin/yedek.sh >>/home/deniz/yedek.log 2>&1Sıraya dikkat edin: >>dosya 2>&1, tersi değil. Komut satırına date +%F gibi bir şey eklerseniz başka bir tuzak çıkar. crontab(5) sayfasına göre komut alanındaki kaçırılmamış % karakteri satır sonuna dönüşür ve ilk % işaretinden sonraki her şey komuta standart girdi olarak verilir. Crontab içinde % işaretini \% olarak yazın. İşiniz hiç çalışmıyorsa ve günlük dosyası bile oluşmuyorsa sorun yönlendirmede değildir. Diğer olası sebepler için cron işinin neden çalışmadığını bulmak rehberine bakın.
noclobber ve >|: dosyanın üzerine yanlışlıkla yazmamak
> uyarmadan dosyanın içini boşaltır. Bir yapılandırma dosyasının adını yanlış yere yazmak, içeriğini silmeye yeter. noclobber seçeneği bunu engeller. Bash'te set -o noclobber ya da set -C ile açılır. dash'te de -C seçeneği vardır ve kılavuz sayfası onu var olan dosyaların üzerine > ile yazmamak olarak tanımlar.
set -o noclobber
echo uc > sayilar.txt
echo "cikis kodu: $?"
echo dort >| sayilar.txt
cat sayilar.txt
set +o noclobberBash kılavuzuna göre noclobber açıkken >, hedef var olan normal bir dosyaysa başarısız olur. Bu yüzden ikinci satır bir hata yazmalı ve üçüncü satır sıfırdan farklı bir çıkış kodu göstermeli. >| bu korumayı bilerek aşar, bu yüzden dördüncü satır dosyayı yine de yazar. Kural yalnızca normal dosyalar içindir. /dev/null normal bir dosya değildir, bu yüzden >/dev/null seçenek açıkken de çalışır.
exec >log 2>&1: bütün betiği tek satırla yönlendirmek
Her komutun sonuna >>log 2>&1 yazmak yorucudur ve birini unutmak kolaydır. exec komut adı olmadan, yalnızca yönlendirmeyle çağrılırsa geçerli kabuğun yönlendirmelerini değiştirir. Bash kılavuzu bunu böyle anlatır. dash kılavuzu da aynı şeyi söyler: exec üzerindeki yönlendirmeler kalıcı olarak işaretlenir. Bu satırdan sonra betikteki her komut aynı yere yazar:
#!/bin/sh
exec >>"$HOME/yedek.log" 2>&1
echo "Başladı: $(date)"
tar czf "$HOME/etc-yedek.tgz" /etc
echo "Bitti, tar çıkış kodu: $?"Bu içeriği yedek.sh adıyla kaydedin, çalıştırılabilir yapın ve çalıştırın:
chmod +x yedek.sh
./yedek.sh
cat ~/yedek.logBetik çalışırken ekranda hiçbir şey görünmemeli. Her şey ~/yedek.log içinde olmalı. tar bazı dosyaları normal kullanıcı olarak okuyamazsa, o hata satırları da günlüğe düşer ve son satırdaki çıkış kodu sıfırdan farklı olur. Sıra kuralı burada da geçerlidir: exec 2>&1 >>log yazarsanız hatalar yine terminale gider.
Bir uyarı: bu betiği çalıştırmak yerine source ya da . ile okursanız, exec sizin açık terminal oturumunuzun akışlarını değiştirir. Ondan sonra yazdığınız her komutun çıktısı günlüğe gider ve ekranınız boş kalır. Bu farkı bir Bash betiğini source etmek ile çalıştırmak arasındaki fark rehberinde anlattık.
sudo echo x > /root/dosya neden çalışmaz?
Bu satır başarısız olur ve çoğu kişi şaşırır, çünkü başında sudo vardır. Sebep yine sıradır. Yönlendirmeyi sudo yapmaz, sizin kabuğunuz yapar. Kabuk /root/dosya dosyasını, komutu çalıştırmadan önce açmaya çalışır. Kabuk ise root olarak değil, sizin kullanıcınız olarak çalışıyor. Bu yüzden dosya açma işlemi sudo daha başlamadan başarısız olur.
Çözüm, dosyayı root olarak çalışan bir programa açtırmaktır:
echo x | sudo tee /root/dosya > /dev/null
sudo cat /root/dosyaBurada dosyayı açan tee programıdır ve tee root olarak çalışır. tee aldığı veriyi ekrana da yazar. Sondaki > /dev/null o kopyayı atar. Bu tekniğin ayrıntılı anlatımı tee ile çıktıyı iki yere göndermek ve sudo ile korumalı dosyaya yazmak rehberinde.
Hızlı başvuru listesi
>dosya: standart çıktıyı dosyaya yazar, dosyayı önce boşaltır.>>dosya: standart çıktıyı dosyanın sonuna ekler.2>dosyave2>>dosya: aynı işlemler, standart hata için.>dosya 2>&1: iki akış da dosyaya gider. POSIX biçimidir, cron'da güvenlidir.2>&1 >dosya: yalnızca standart çıktı dosyaya gider, hatalar 1 numaranın eski hedefine (çoğunlukla ekrana) gider.&>dosyave&>>dosya: Bash kısayolu./bin/shaltında güvenmeyin.>|dosya:noclobberaçıkken bile dosyanın üzerine yazar.exec >>log 2>&1: betiğin geri kalanının bütün çıktısını günlüğe gönderir.
FAQ
>/dev/null 2>&1 ile 2>&1 >/dev/null aynı şey mi?
Hayır. Yönlendirmeler soldan sağa işlenir. İlkinde önce 1 numara /dev/null dosyasına gider, sonra 2 numara onun kopyası olur, yani iki akış da atılır. İkincisinde 2 numara önce 1 numaranın o anki hedefini, yani terminali kopyalar. Sonra 1 numara /dev/null dosyasına çevrilir. Sonuçta normal çıktı atılır, hata mesajları 1 numaranın eski hedefine, yani terminale ya da dıştaki boruya gider.
Crontab'da &>/dev/null kullanabilir miyim?
Cron, crontab içinde SHELL değiştirilmedikçe işleri /bin/sh ile çalıştırır. dash'in kılavuz sayfası &> operatörünü listelemez ve POSIX dilbilgisinde &, önündeki komutu arka planda çalıştıran bir ayırıcıdır. Kendi sunucunuzda sh -c 'ls /yok-boyle-bir-dizin &>/dev/null; echo bitti' satırını çalıştırıp sonucu görün. Güvenli seçim, >/dev/null 2>&1 yazmak ya da crontab'ın başına SHELL=/bin/bash eklemektir.
/dev/null'a gönderdiğim çıktıyı sonradan görebilir miyim?
Hayır. null(4) kılavuz sayfasına göre /dev/null dosyasına yazılan veri atılır ve hiçbir yerde saklanmaz. Çıktıyı sonra okumak istiyorsanız onu baştan bir dosyaya yönlendirin, örneğin >>/home/deniz/is.log 2>&1 ile. Yalnızca hataları saklamak için >/dev/null 2>>/home/deniz/hata.log yazın.
Hata mesajlarım Türkçe çıkıyor. 2 numarası yine geçerli mi?
Evet. Mesajların dili LC_MESSAGES yerel ayar kategorisiyle belirlenir, akış numaralarıyla değil. stdin(3) kılavuz sayfasına göre stdin, stdout ve stderr program başladığında 0, 1 ve 2 numaralı tanımlayıcılardır. Bu yüzden 2>, 2>> ve 2>&1 mesaj Türkçe de olsa İngilizce de olsa aynı şekilde çalışır. Dil yalnızca metni değiştirir.