4 puan yazan GN⁺ 2023-09-10 | 1 yorum | WhatsApp'ta paylaş
  • GNU coreutils 8.3 kaynak yapısını yardımcı program bazında baştan sona okuyan uzun soluklu bir açıklama projesi; komut satırı aracı tasarımını öğrenen acemi programcılara yönelik
  • Bir kullanım belgesi değil, kaynak kod okuma yardımcı materyali; her komutun namespace’ini, yürütme akışını, tasarım kararlarını, algoritmalarını, UNIX ve erken Coreutils soy ağacını birlikte ele alıyor
  • coreutils küçük ve tek amaçlı çok sayıda C programından oluştuğu için, global değişkenler, makrolar, goto, uzun fonksiyonlar gibi modern bakışla kaba görünen kod kalıplarını da tarihsel bağlam içinde okumak gerekiyor
  • Ortak akış; başlatma, seçenek ayrıştırma, girdi işleme, sistem çağrısı yürütme ve hata işleme şeklinde ilerliyor; getopt_long(), fts ve gnulib yardımcıları tekrar tekrar karşımıza çıkıyor
  • Katkı veya düzeltme yapmadan önce, mevcut araçlarla yeniden üretilebilir mi, geriye dönük uyumluluğu bozuyor mu, POSIX’ten ne kadar sapıyor diye önce kontrol etmek gerekiyor

GNU coreutils açıklama projesinin kapsamı

  • GNU coreutils 8.3 içindeki tüm yardımcı programları açıklayan uzun soluklu bir proje
  • Hedef okur kitlesi, komut satırı yardımcı programlarının tasarımını inceleyen acemi programcılar
  • İlgilendiğiniz yardımcı programın source code dosyalarını okurken arka plan açıklaması olarak birlikte kullanmaya en uygun kaynak
  • Bu materyal kullanıcı kılavuzu değildir; gerçek kullanım için man pages başvurulmalıdır
  • Proje birkaç aşamadan oluşuyor
    • Phase 1: Her yardımcı program için namespace ve yürütme özeti sayfalarının tamamlanması
    • Phase 2: Başlıca tasarım kararları ve algoritmalar, UNIX ve erken Coreutils soy ağacı, daha yararlı kaynak kod walkthrough’ları, kaynak kod evrimi görselleştirmelerinin tamamlanması
    • Phase indefinite: Her yardımcı programın satır satır kod walkthrough’u uzun vadeli hedef olarak kalıyor
    • Satır satır notların toplanması için bir GitHub repo sunuluyor

Yardımcı program bazında açıklama sayfaları

  • Her komut adı, ilgili yardımcı programın çözümlendiği ayrıntılı sayfaya bağlanıyor
  • Her sayfada tartışma, kaynak kod ve walkthrough yer alıyor
  • Kalın gösterilen yardımcı programlar Phase 2’de genişletilmiş öğeler
  • Listede arch, base64, cat, chmod, cp, date, dd, df, ls, rm, sort, tail, tr, wc, yes gibi GNU coreutils komutları yer alıyor

Kodu okumadan önce bilinmesi gereken varsayımlar

  • coreutils yardımcı programlarının çoğu neredeyse 30 yıla yaklaşan bir geçmişe sahip; uzun süre boyunca birçok kişi tarafından değiştirilmiş kodlar
  • Çoğu küçük ve tek bir amaca odaklanmış küçük programlar
    • Genellikle tek kaynak dosyalı programlar
    • Uzun süre yaşayacak ya da rolünün ötesinde genişleyecek şekilde tasarlanmış kodlar değiller
    • Global değişkenler, makrolar, goto, iç içe switch/döngüler içeren uzun fonksiyonlar gibi kalıplar görülüyor
  • POSIX bilgisi kodu anlamak için önemli
    • Utility Syntax Guidelines ile başlamak iyi olur
    • POSIX girdi ve çıktının birlikte çalışabilirliğini tanımlar, ancak gerçek çalışma biçimini uygulamaya bırakır
    • GNU coreutils POSIX’i katı biçimde izlemeyebilir; ancak izin bitleri, uid/gid, ortam değişkenleri, çıkış durumu gibi kavramlar derinlemesine yerleşmiştir
  • Taşınabilirlik sorunları nedeniyle coreutils büyük ölçüde gnulib üzerine dayanır
    • Neredeyse tüm yardımcı programlar, farklı sistemlerde ortak sorunları ele almak için tasarlanmış gnulib fonksiyonlarını içerir
  • coreutils, bash, zsh, ksh gibi shell desteği varsayımıyla çalışır
    • Shell; fork/clone ile yardımcı programı çalıştırır, argümanları iletir, ortamı ayarlar, pipe ile girdi/çıktıyı yönlendirir ve çıkış değerini korur
  • GNU coreutils başlangıçta shell, metin ve dosya yardımcı programları olarak üç paketten doğdu; aynı türden yardımcı programlar birçok tasarım kalıbını paylaşır

Temel tasarım kalıpları

  • Çoğu CLI yardımcı programında ortak olarak üç aşama bulunur
    • Bayrakları, seçenekleri, yerelleştirmeyi vb. hazırlayan kurulum aşaması
    • Girdiyi okuyup yürütme parametrelerini belirleyen argüman ayrıştırma aşaması
    • Girdiyi hazırlayıp bir veya daha fazla sistem çağrısına aktaran işleme·yürütme aşaması
  • Yürütme sırasında çeşitli noktalarda kısıtlar kontrol edilir ve işlem başarısız olabilir
    • Farklı çıkış durumları, sorunun yerini ima edebilir
    • EXIT_FAILURE genel başarısızlık durumu olarak sık kullanılır
  • Başarısızlıktan sonra kullanıcıya geri bildirim sağlanır
  • Yardımcı program varyasyonları üç gruba ayrılır
    • Trivial utilities: Birkaç satır makro tanımladıktan sonra başka bir yardımcı programın kaynağını include eder ve makrolarla belirli akış kontrolünü zorlar. Örnekler: arch, dir, vdir
    • Wrapper utilities: Kurulum ve seçenek ayrıştırmadan sonra komut satırı seçeneklerini doğrudan sistem çağrısı argümanlarına aktarır. Sistem çağrısının sonucu yardımcı programın sonucu olur; kendi işleme mantığı azdır. Örnekler: link, whoami, hostid, logname
    • Full utilities: Kurulum, seçenek·argüman ayrıştırma, girdi verisi işleme ve birden çok sistem çağrısı yürütmenin tamamını içerir; yardımcı programların çoğu bu gruptadır

Ortak başlatma ve seçenek ayrıştırma

  • Tüm yardımcı programlarda main() başında kısa bir başlatma prosedürü bulunur
    • initialize_main (&argc, &argv);
    • set_program_name (argv[0]);
    • setlocale (LC_ALL, "");
    • bindtextdomain (PACKAGE, LOCALEDIR);
    • textdomain (PACKAGE);
    • atexit (close_stdout);
  • Bu ön işleme kodu, uluslararasılaştırma ve çıkışta davranış kaydı gibi yönetim işlerini ele alır; tek tek yardımcı programların temel davranışını etkilemez
  • Komut satırı seçeneklerini ayrıştırmada Getopt araç ailesi merkezi rol oynar
    • Kısa seçenekler -, uzun seçenekler -- önekini kullanır
    • Kısa seçenekler string olarak tanımlanır, uzun seçenekler struct kullanır
    • Kısa seçenek string’inde argüman yoksa yalnızca karakter yazılır; zorunlu argüman için :, isteğe bağlı argüman için :: eklenir
    • kill komutunun kısa seçenek string’i Lln:s:t, L, l, t için argüman olmadığını; n, s için argüman gerektiğini ifade eder
    • Tüm yardımcı programlarda getopt_long() sıradaki seçeneği döndürmek için kullanılır
    • optind, argv[] dizisinde sıradaki argümanın konumunu gösterir
    • optarg, seçenek argümanı değerini gösteren karakter işaretçisidir

Dosya sistemi dolaşımı ve sistem çağrısı yardımcıları

  • Unix benzeri sistemlerde dosya sistemi dolaşımını kolay ele almak için çoğu zaman fts kütüphanesi desteklenir
    • fts_open() veya xfts_open() ile bir yoldan FTS yapısı oluşturulur
    • Ağaçtaki dosya veya dizin düğümü FTSENT yapısı ile temsil edilir
    • fts_read() çağrıldığında FTSENT üretilir; bu süreç ağaç dolaşımıdır
    • FTSENT->fts_info alanı öğe açıklamasında sık kullanılır ve öğenin nasıl işleneceğini belirlemekte kullanılır
  • coreutils, libc’nin sağladıklarının dışında da birçok sistem çağrısı sarmalayıcısı ve yardımcı kullanır
  • Yazma işlemlerinde write() sistem çağrısı sarmalayıcısı ve fwrite() gibi libc fonksiyonlarının yanında full_write() gibi standart dışı fonksiyonlar da görülür
    • full_write(), sert bir hata oluşmadığı sürece yazmayı yeniden denemeye devam eder
    • safe_write(), kesme oluşsa bile write() sistem çağrısını yeniden dener
    • dd içindeki iwrite(), split içindeki cwrite() gibi yalnızca tek bir yardımcı programda kullanılan yazma yardımcıları da vardır

Ortak fonksiyonlar ve kod deyimleri

  • Tüm yardımcı programlar en azından main(), usage(), _() olmak üzere üç fonksiyon kullanır
  • usage(), girdi parametrelerini, anlamlarını ve doğru söz dizimini içeren yardım metnini gösterir
  • _(), system.h içinde tanımlı bir makrodur ve düz string’leri GNU gettext.h Native Language Support özelliğine bağlar
    • Kullanıcıya gösterilen bir string ise genellikle bu makro ile sarılır
  • Önemsiz olmayan yardımcı programların çoğunda ortak kod satırları tekrar eder
    • #include "system.h" sistem bağımlı makroları, değişkenleri ve standart dışı yardımcı fonksiyonları tanımlar
    • PROGRAM_NAME yardımcı programın resmi adını tanımlar ve sürüm kontrolünde kullanılır
    • AUTHORS yardımcı program yazarlarını tanımlar ve sürüm kontrolünde kullanılır
    • emit_try_help() başarısız çıktıdan sonra yardım önerisini yazdırır
    • emit_ancillary_info (PROGRAM_NAME) komuta özgü çıktının ardından ortak ek yardım bilgilerini yazdırır
    • initialize_main(&argc, &argv) VMS’nin yerleşik joker karakter genişletmesini ele alır; diğer işletim sistemlerinin çoğunda kaldırılır
    • atexit(close_stdout) program çıkışında buffered stream’leri flush edip kapatan fonksiyonu kaydeder
  • Acemilere yabancı gelebilecek C deyimleri de karşımıza çıkar
    • !! çiftli tekli NOT işlemidir; bir değeri boolean’a zorla dönüştürmek için kullanılır
    • do { ... } while (0), çok ifadeli makroları sarıp önişlemci ikamesinden sonra token’laştırmayı güvenli kılan döngü olmayan bir kalıptır

Bakım modeli ve katkı öncesi kontroller

  • coreutils gibi aktif projeler genellikle üç bakım akışıyla evrilir
  • Proje geneli değişiklikler, tüm yardımcı programların yapısı ve bağımlılıklarını kapsayan büyük değişikliklerdir
    • 1995’te GNU gettext project ile Native Language Support eklendi ve metin çıktısı satırlarının çoğuna _() makrosu getirildi
    • 1996’da uluslararasılaştırma desteği genişletildi ve main() içine çeşitli başlatma kodları eklendi
    • 1995’te kullanım çıktısına yardımcı programın amacını anlatan kısa açıklama eklendi
    • 2003’te VMS joker karakter desteği eklendi; bu initialize_main() fonksiyonunda görülür
    • 2016’da başarısızlık yollarında derleyici uyarılarını önlemek için die() makrosu, exit() ve error() fonksiyonlarının çoğunun yerini aldı
  • Yardımcı program bazlı değişiklikler hata düzeltmeleri, yeni özellikler ve optimizasyonlar olarak ayrılır
    • join, sort, uniq, 2016’daki patch öncesinde overflow saldırılarına açıktı
    • df komutuna 2013’te --output seçeneği eklendi
    • yes, daha iyi buffering ile performans iyileştirmesi aldı
  • Yıllık bakım, en azından tüm yardımcı programların telif hakkı yıllarının güncellenmesini içerir
    • FSF adresinin güncellenmesi gibi idari değişiklikler de vardır; yürütmeye etkisi yoktur
  • Katkı vermek isteyenlerin önce GNU project page, contribution guidelines, rejected features, mailing list archives sayfalarını kontrol etmesi iyi olur
  • Kod yazmadan önce kontrol edilmesi gereken üç soru vardır
    • Aynı işlev mevcut araçlarla yeniden üretilebilir mi?
    • Katkı geriye dönük uyumluluğu bozar mı?
    • Önerilen davranış POSIX’ten büyük ölçüde sapıyor mu?
  • Emin değilseniz, mailing list üzerinden topluluğa danışma süreci önerilir

İlginç ayrıntılar

  • En kısa yardımcı program false olup 2 satırdır; arch, dir, vdir ile aynı uzunluktadır
  • En kısa bağımsız çalıştırılabilir yardımcı program true olup 80 satırdır; ilk sürümü neredeyse minimal bir C programına yakındır
  • En uzun yardımcı program ls olup 5308 satırdır
  • Birçok yardımcı programın kökleri 1970’lerin Research UNIX’ine kadar uzanır; bazıları Multics’e kadar gider
  • En eski düşünsel ata, yaklaşık 1963 tarihli CTSS LISTF komutudur; daha sonra ls olarak kısalmıştır
  • dd komutunun kendine özgü söz dizimi, 1960’ların başındaki OS/360 job control language yapısını andırır
  • sort, çoklu iş parçacığından yararlanan tek yardımcı programdır
  • fmt, fonksiyon maliyetini kullanarak satır ve paragraf optimizasyonunu gösterir
  • yes, yüksek performanslı çıktı için sayfa hizalı bellek tamponu kullanır
  • df aygıt metadata’sını kullanır, du ise tüm dosyaları kontrol eder; bu yüzden df daha hızlıdır
  • cksum içinde normal yürütme ve CRC-32 tablosu üretimi için iki giriş noktası vardır
  • echo için başarısızlık koşulu yoktur
  • test ve expr tasarımı, genel yardımcı program yapısından oldukça farklıdır
  • su başlangıçta coreutils/shellutils içinde bakımı yapılan bir komuttu

Bakmaya değer uygulamalar

Sponsorluk notu

  • Kişisel sponsorluk alacak bir ayar yapılmamış
  • Zaman veya para paylaşmak isterseniz Free Software Foundation’a katkıda bulunabilirsiniz

1 yorum

 
GN⁺ 2023-09-10
Hacker News yorumları
  • “Bu yardımcı programların çoğu neredeyse 30 yıllık ve bu süre içinde birçok kişi tarafından değiştirilmiş” denmesine rağmen “uzun ömürlü olacak ya da rolünün ötesine genişleyecek şekilde tasarlanmamış” deniyorsa, yazarın uzun ömür düşünülerek yazılmış program örneklerini görmek isterdim
    30 yıl ayakta kalmış bir program mı, yoksa önümüzdeki 30 yıl daha gideceğini düşündüğü bir program mı, merak ediyorum

    • Sorun, o rolün tam olarak ne olduğu. Bu araçlarla yapılabileceklerin bir sınırı var mı emin değilim
      Kendi yaptığım küçük programlama dilini test etmek için bash ve coreutils ile bir test framework'ü kurdum; başta “düzgün” bir dil kullanmadığım için içime sinmemişti ama pratikte çok iyi çalıştı ve paralel çalıştırma da vardı
      Test edemediğim tek şey programın argv[0] değeriydi; ne kadar kombinasyon denesem de istediğim davranışı tam olarak elde edemedim, bu yüzden coreutils'e env için bir özellik isteği ve yama gönderdim: https://lists.gnu.org/archive/html/coreutils/2023-08/msg0006...
      Kabul edilecek gibi görünüyor; bu da eski bir programa yeni bir özelliğin eklenmesi demek
    • Burada kastedilenin “uzun süre aktif olarak geliştirilecek şekilde yapılmış” olması gibi anlaşılması gerektiğini düşünüyorum
      Yazarın sıraladığı pratikler, büyük programlarda genellikle ölçek büyüdükçe bakım yapılabilirliğe zarar verdiği için eleştirilen şeyler
    • Muhtemelen “uzun geliştirme ömrü” demek istemiş. Yani neredeyse bir kez yazılıp uzun süre kullanılan yardımcı program anlamında; bu da “tek bir işi iyi yap” yaklaşımının avantajlarından biri
    • Garip şekilde “long life” ifadesini sürekli çalışan program olarak yorumlamıştım. Net girdisi, kısa çalışması ve net çıktısı olan araçların karşıtı gibi görmüştüm
    • Emacs?
  • Bakılabilecek kaynaklar:
    GNU coreutils test yaklaşımı: https://www.pixelbeat.org/docs/coreutils-testing.html
    coreutils'in her komutunu inceleme: https://ratfactor.com/slackware/pkgblog/coreutils
    GNU Coreutils ile komut satırında metin işleme: https://learnbyexample.github.io/cli_text_processing_coreuti... — 20'den fazla metin işleme aracını ele alan e-kitabım

  • İlgili yazılar:
    Decoded: GNU Coreutils (2018) - https://news.ycombinator.com/item?id=29871037 - Ocak 2022, 7 yorum
    Decoded: GNU coreutils (2019) - https://news.ycombinator.com/item?id=26411908 - Mart 2021, 38 yorum
    Decoded: GNU Coreutils - https://news.ycombinator.com/item?id=20328650 - Temmuz 2019, 55 yorum

  • İlginç bir bilgi: macOS'te Homebrew ile coreutils kurarsanız, macOS'te zaten od(1) bulunduğu için coreutils'in od komutu god(1) olarak kurulur

  • Yazar görüyorsa, en az bir hata var. shred sayfasının kısa açıklaması[0] aslında csplit açıklaması[1]; “dosya içeriğini gizlemek için üzerine yazar ve isteğe bağlı olarak siler” gibi bir şey olmalı
    [0]: https://maizure.org/projects/decoded-gnu-coreutils/shred.htm...
    [1]: https://maizure.org/projects/decoded-gnu-coreutils/csplit.ht...

  • Böyle bir şeyin olduğunu bilmiyordum, güzelmiş. yes gibi basit bir şey bile standart çıktıya yazan bir yardımcı programın temel kodunun nasıl yazıldığını görmek açısından oldukça ilginç
    https://maizure.org/projects/decoded-gnu-coreutils/yes.html

    • Demek istediğim, basit bir yardımcı programın bile iyi yazılmayı ve optimize edilmeyi hak ettiği
      Örneğin çıktıyı çok hızlı yazmanın nasıl yapılacağını gösteren minimal bir örnek olması güzel. Program zaten çok kısa olduğu için satır sayısı pek önemli değil; yazarı yöntemi biliyorsa, engel oluşturmaması için mümkün olduğunca hızlı yapmak daha iyi bence
    • Eğlence için yapılmış Go uygulaması şöyle:
      package main
      
      import "flag"
      
      func main() {  
      yes := flag.String("m", "y", "message")  
      flag.Parse()  
      for {  
      println(*yes)  
      }  
      }  
      
  • Temel yardımcı programlar listesi olarak oldukça eğlenceli. UNIX’i çok uzun süredir kullanıyorum ama shred, shuf, factor gibi şeyleri hiç duymamıştım
    Bir ara

    sudo find / -type f -exec shred {} \;  
    

    çalıştırıp kendini öldürmeden önce ne kadar ilerleyeceğini görmek istiyorum. Elbette bir sanal makinede ya da kolayca yeniden flash’lanabilecek bir cihazda

    • Benzer bir şeyi dd ile denedim; sonuna kadar tamamlandı ve epey heves kırıcıydı
      Çökmesini ya da en azından bağlantının kopmasını ummuştum; ama çekirdek ile sshd ve bash hâlâ bellekte olduğundan, hiçbir şeyi doğru düzgün yapamadığım bir prompt’a geri döndüm
    • Linux’ta muhtemelen sonuna kadar gitme olasılığı yüksek. O anda çalışmakta olan çalıştırılabilir dosyalara ve kütüphanelere yazılamadığı için (ETXTBUSY), shred kendisini ya da findı bozamaz
  • /bin/truenun aslında başarısız olup false döndürebilmesini seviyorum. Teknik olarak “Not /bin/false” çağrısı daha sağlam hale geliyor: https://github.com/coreutils/coreutils/blob/master/src/true....
    Elbette bunun neredeyse hiç gerçekleşmeyecek bir durum olduğunu biliyorum, ama yine de komik

    • true komutuyla ilgili bir başka ilginç nokta da gereğinden çok daha karmaşık hale gelmiş olması
      Önce bir alıştırma:
      touch mytrue  
      chmod u+x mytrue  
      ./mytrue  
      echo "error code for mytrue is $?"  
      
      true kelimenin tam anlamıyla böyle başladı. Epey Zen koanı gibi
      İlk değişiklik hukuk yüzündendi. Her kodda telif hakkı bildirimi olması gerekiyordu; boş dosyalarda bile. Böylece hiçbir şey içermeyen dosya, yalnızca telif hakkı bildirimi içeren bir dosyaya dönüştü ve “hiçliğe telif hakkı konabilir mi?” sorusu akla geldi. AT&T bunu denedi
      Sonra biri, programların iyi tanımlanmış olması ve Unix’in tesadüfi davranışlarına bel bağlamaması gerektiğini söyledi; o noktada bu oldukça makuldü. Böylece true nihayet koda kavuştu ve o kod exit 0 idi
      Bir başkası da sistem yardımcı programlarının shell yerine C ile yazılırsa daha hızlı olacağını söyledi; OpenBSD’de bunun hâlâ güzel bir örneği var: http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/usr....
      Bir noktada GNU bürokrasisi devreye girip tüm programların -h bayrağını desteklemesi gerektiğini söyledi; ardından tüm programların locale desteği olması gerektiği söylendi ve bugünkü GNU true şaşırtıcı biçimde 80 satırlık hale geldi: https://github.com/coreutils/coreutils/blob/master/src/true....
      Bunu sorun etmeyebilirsiniz; ama tanımı gereği “hiçbir şey yapmayıp başarılı olan” bir program için epey fazla kod
      http://trillian.mit.edu/~jc/humor/ATT_Copyright_true.html
    • Bu sadece seçeneklerle çalıştırıldığında olan bir şey değil mi? Sadece true çalıştırmak, yorum kafa karıştırmıyorsa, etkilenmiyor gibi görünüyor
  • Veri yapıları ve algoritmaların nasıl faydalı uygulanacağı konusunda sezgi geliştirmek isteyen acemi bir programcı için burada özellikle bakılması gereken bir şey var mı?

    • coreutils içindeki zekânın büyük kısmı veri yapıları ve algoritmalardan çok çekirdekle etkili biçimde etkileşime girme yollarında
      Örneğin veriyi kullanıcı alanına kopyalamamak için read()/write() yerine copy_file_range() kullanmak gibi. Bu, bilgisayar biliminden çok yazılım mühendisliğine yakın
    • Yanlış anlamış olabilirim ama veri yapıları ve algoritmaların komut satırı araçlarıyla ne ilgisi olduğunu bilmiyorum
  • Bu sitenin ana fikrini kaçırıyor olabilirim ama bunların her biri için zaten man sayfası ya da info sayfası yok mu?

    • Kullanım bilgisi değil; bu site programların içeride nasıl çalıştığını ayrıntılı biçimde ele alıyor