Linux sunuculara SSH bağlantısı üzerinden gönderilen komutlar genellikle sorunsuz çalışır. Ancak bazen aynı komut aynı kimlik doğrulamasıyla farklı sonuçlar üretiyor. Bu durumun ardındaki gizemli nedenlerden biri, sunucunun varsayılan olarak csh (C Shell) veya tcsh kullanması ve bu durumun komut çalıştırma alışkanlıklarınızla uyumsuzluk yaratmasıdır.
Bu yazıda, SSH komutlarının csh ortamında neden başarısız olduğuna, bu sorunun nasıl tespit edildiğine ve kod tabanınızda nasıl kalıcı olarak çözüldüğüne odaklanacağız. Ayrıca, benzer hataların gelecekte tekrarlanmasını engellemek için uyguladığımız otomatik test stratejisini de paylaşacağız.
Hatanın kaynağı: csh’nin komut yorumlama farklılıkları
Bir kullanıcı, yeni bir site ekleme arayüzündeki "SSH profilini otomatik olarak algıla" seçeneğinin sürekli olarak "yol bulunamadı" hatası verdiğini bildirdi. Oysa aynı SSH bağlantısı üzerinden çalışan WP-CLI yol test butonu düzgün çalışıyordu. Aynı kimlik bilgileri, aynı sunucu — sadece bir durum çalışırken diğeri başarısız oluyordu.
Hatanın izini sürerken karşılaşılan sunucu tarafındaki hata günlüğü şu şekildeydi:
find: 2: unknown primary or operatorStandart POSIX komutu olan find aslında 2>/dev/null gibi standart bir hata yönlendirmesini kabul etmesi gerekirken, csh bunu literal bir argüman olarak yorumluyordu. Çünkü csh, 2>/dev/null gibi yönlendirmeleri tanımayan bir kabuktur. Bu durum, komutun tamamen farklı bir şekilde yorumlanmasına neden oluyordu.
Not: 2>/dev/null hata çıktısını bastırmak için Bourne Shell (sh) ve bash’ta kullanılan standart bir yöntemdir. csh/tcsh ise farklı bir sözdizimi kullanır ve bu tür yönlendirmeleri desteklemez.Japonya’daki bir bulut sağlayıcısının varsayılan kabuğu: csh
Daha önce yayınlanan bir araştırmada da belirtildiği üzere, Japonya merkezli Sakura Internet gibi bazı bulut sağlayıcıları, kullanıcı hesaplarının varsayılan kabuğunu csh veya tcsh olarak ayarlıyor. Bu durum, birçok geliştiricinin alışık olduğu bash ortamından önemli ölçüde farklıdır.
Python’un popüler SSH kütüphanesi olan Paramiko’nun exec_command yöntemi, komutları kullanıcının oturum açma kabuğu üzerinden çalıştırır. Sakura Internet gibi csh kullanan bir sunucuya find ... 2>/dev/null gönderildiğinde, csh bunu yorumlamaya çalışır ve hata alırsınız. İşte bu, asıl sorunun kaynağıdır.
csh’nin bash’la uyumsuzluk yaratan bazı örnekler:
2>/dev/null— hata yönlendirmesi[ -f yol ]— test sözdizimifor X in ...; do ... done— döngü yapısıkomut1 && komut2— kısa devre değerlendirmesi\( ... \)— alt kabuk işlemleri
Bu komutlardan herhangi biri csh’de çalıştırıldığında "unknown primary or operator" veya "Missing }" gibi hatalar üretebilir.
Bir yeri düzeltmek, tümünü düzeltmez: Kopyala-yapıştır hataları
Bu sorunla ilk kez karşılaşmamıştık. Geçmişte, test_ssh_profile adlı SSH bağlantı test API’sinin csh kullanan sunucularda başarısız olduğunu fark etmiş ve sorunu /bin/sh -c ile sarmalayarak çözmüştük:
sonuc = c.run('/bin/sh -c ' + shlex.quote('echo ok'), hide=True, warn=True)Bu basit çözüm, komutun POSIX uyumlu /bin/sh üzerinden çalışmasını sağlıyordu. Ne var ki, bu düzeltme yalnızca tek bir API uç noktasına uygulanmıştı. Kod tabanında aynı desenin başka nerelerde kullanıldığını araştırmadığımız için, diğer SSH komut gönderme noktaları csh kullanan sunucularda hâlâ çalışmıyordu.
Tespit edilen ve henüz düzeltilemeyen konumlar:
/api/discover_server_paths— WordPress kurulum yollarını otomatik algılama- WP-CLI otomatik algılama (profil kaydederken)
test_wpcliuç noktası — WP-CLI yol test butonu- Eklenti listesi getirme (birden fazla çağrı noktası)
Bu durum, kullanıcılara tuhaf bir şekilde yansıyordu: "Test butonu çalışıyor, otomatik algılama başarısız oluyor."
Tüm SSH komutlarını güvenli bir şekilde çalıştırma: _safe_run yardımcı fonksiyonu
Her SSH komutunu elle /bin/sh -c ile sarmalamak, gelecekte unutulabilecek bir yaklaşımdır. Bunun yerine, tüm SSH komutlarını tek bir yardımcı fonksiyona yönlendirdik:
def _safe_run(c, komut, **opsiyonlar):
"""
Komutu /bin/sh -c ile sarmala, böylece oturum açma kabuğundan bağımsız çalışır.
Tüm SSH ilişkili API’ler tarafından kullanılan varsayılan yol.
"""
sarmalanmis = '/bin/sh -c ' + shlex.quote(komut)
return c.run(sarmalanmis, **opsiyonlar)Artık kod tabanında c.run(komut) gibi ham çağrılar kalmadı. Yeni SSH komutları doğal olarak _safe_run üzerinden yönlendiriliyor ve böylece csh uyumsuzlukları ortadan kalkıyor.
Tekrar etmeyi engellemek: Statik analiz testi
Yalnızca bir düzeltme uygulamak yerine, sorunun tekrar ortaya çıkmasını engellemek için otomatik bir test katmanı ekledik. Bu test, csh uyumsuzluğu yaratan komut desenlerinin yeniden kod tabanına girmesini engelliyor.
# tests/test_csh_safe_run_wrapping.py (örnek)
def test_no_raw_c_run_with_sh_syntax():
"""
csh uyumlu olmayan syntax içeren ham c.run() çağrılarının kalmadığını statik olarak doğrula.
"""
for cagri in find_c_run_calls('site_manager_web.py'):
arg = cagri.argument_text
if contains_sh_syntax(arg):
assert arg.lstrip("'").lstrip('"').startswith('/bin/sh -c'), \
f"csh uyumlu olmayan syntax {cagri.lineno} satırında bulundu"Bu test yalnızca c.run( içindeki argüman ifadelerine odaklandığından, dokümantasyon veya yorumlarda geçen 2>/dev/null gibi ifadeler yanlış alarm vermez. CI hattına entegre edilen bu test sayesinde, gelecekteki bir PR’de /bin/sh -c kullanılmadan c.run('find ... 2>/dev/null') yazılması derhal yakalanır.
Sonuç: İki katmanlı koruma ile kopyala-yapıştır hatalarını engelleme
Bu sorundan çıkarılması gereken iki önemli ders var:
- İlk düzeltmeyi yaptıktan sonra tüm örnekleri araştırın. Kopyala-yapıştır yoluyla yayılan hatalar insan hafızasından kaçabilir. İlk düzeltme anında aynı desenin başka yerlerde kullanılıp kullanılmadığını araştırmak, ilk savunma hattını oluşturur.
- Yasak desenleri yakalayan regresyon testleri ekleyin. İnsan disiplininin yetersiz kaldığı durumlarda bile çalışan otomatik testler, ikinci savunma hattını oluşturur. CI sistemi, insan faktörünü ortadan kaldırır.
Bu hikâye bize şunu öğretti: test_ssh_profile düzeltildiğinde csh’nin çözüldüğünü varsaydık ve bu varsayım beş başka hatanın gizli kalmasına neden oldu. Umarız bu deneyim, kabuk uyumsuzluklarıyla başa çıkmaya çalışan diğer geliştiricilere faydalı olur.
Unutmayın: SSH komutlarınızın hangi kabukta çalıştırıldığını her zaman kontrol edin ve POSIX uyumlu /bin/sh üzerinden çalıştırıldığından emin olun. Gelecekteki kabuk çatışmalarıyla karşılaşmamak için bu basit adımı şimdi uygulayın.
Yapay zeka özeti
SSH üzerinden gönderilen komutlar neden bazen csh kullanan sunucularda çalışmaz? Bu yazıda csh/bash farklarını, SSH komutlarındaki gizli hataları ve kalıcı çözümleri keşfedin.