UNIX Dilinde Konuşma, Bölüm 6: Her şeyi otomatikleştirin!

0
butch
IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

UNIX Dilinde Konuşma, Bölüm 6: Her şeyi otomatikleştirin!

Kişisel ve sistemle ilgili zevksiz işleri kabuk komut dosyalarıyla mekanikleştirin

Düzey: Orta

Martin Streicher (martin.streicher@linux-mag.com), Şef Editör, Linux Magazine

03 Ocak 2007

Kabuk komut dosyalarının (shell scripts), kişisel ya da sistemle ilgili hemen her tür görevi nasıl mekanikleştirdiğini keşfedin. Komut dosyaları izleyebilir, arşivleyebilir, güncelleyebilir, raporlayabilir, karşıya ve karşıdan yükleyebilir. Gerçekten de bir komut dosyası için hiçbir iş çok küçük ya da çok büyük değildir. Aşağıda bu konuya bir giriş bulacaksınız.

Uzun süredir UNIX® kullanan bir kullanıcıyı çalışırken izlediğinizde, kullanıcının omzu üzerinden bakarsanız, komut satırında sıralanan garip sihirlerin etkisiyle büyülenebilirsiniz. UNIX Dilinde Konuşma dizisinin önceki makalelerinden herhangi birini okuduysanız (bkz. Kaynaklar), en azından tilde (~), dikey çubuk (|), değişkenler ve yeniden yöneltme (< ve >) gibi bazı işaretler size tanıdık gelecektir. Ayrıca, belirli UNIX komut adlarını ve birleşimlerini tanıyabilir ya da büyücünün kısayolu olarak bir diğer ad kullanıldığında, bunu fark edebilirsiniz.

Yine de diğer komut satırı sihirlerini anlayamayabilirsiniz; çünkü, sık yinelenen görevleri basitleştirmek ya da otomatikleştirmek için yüksek düzeyde özelleştirilmiş küçük sihirlerin bulunduğu kabuk komut dosyalarından oluşan bir cephaneliği yığmak, deneyimli bir UNIX kullanıcısı için olağan bir şeydir. Zevksiz bir işi tamamlamak için karmaşık komut dizilerini (olasılıkla) yeniden yeniden yazmak yerine, bir kabuk komut dosyası işi mekanikleştirir.

UNIX Dilinde Konuşma dizisinin 6. Bölümünde (bkz. Kaynaklar), kabuk komut dosyalarını nasıl yazacağınızı ve komut satırıyla ilgili daha birçok ipucunu öğreneceksiniz.

Yalnızca tek kelime: "otomasyon"

Bazı kabuk komut dosyaları, tam olarak aynı komutları çalıştırıp, aynı dosya kümesini sürekli olarak yeniden işler. Örneğin, ana dizininizin tüm içeriğini üç uzak bilgisayara dağıtmak için bir Z kabuk komut dosyasının yazılması Liste 1'deki kadar kolay olabilir.



Liste 1. Ana dizininizle birçok uzak makineyi eşzamanlamak üzere hazırlanmış basit bir kabuk komut dosyası
       
#! /bin/zsh

for each machine (groucho chico harpo)
    rsync -e ssh --times --perms --recursive --delete $HOME $machine:
end

Liste 1'i bir kabuk komut dosyası olarak kullanmak için yukarıdaki içeriği bir dosyaya kaydedin (örneğin, simpleprop.zsh) ve dosyayı yürütülebilir kılmak için chmod +x simpleprop.zsh komutunu çalıştırın. Komut dosyasını ./simpleprop.zsh yazarak çalıştırabilirsiniz.

Z kabuğunun her bir komutu nasıl genişlettiğini görmek isterseniz, komut dosyasının #! satırının (diyez-ünlem işareti çiftine genellikle shuh-bang denir) sonuna -x seçeneğini ekleyin:

#! /bin/zsh -x

groucho, chico ve harpo bilgisayarlarının her biri için komut dosyası, rsync komutunu çalıştırır, $HOME dizinini ana dizininizle (örneğin, /home/joe) ve $machine değişkenini bir bilgisayar adıyla değiştirir.

Liste 1'de gösterildiği gibi, değişkenler ve komut dosyası denetim yapıları (örneğin, döngüler), komut dosyalarının yazımını ve korunmasını kolaylaştırır. Örneğin, havuzunuza zeppo adlı dördüncü bir bilgisayarı dahil etmek isterseniz, yalnızca bu adı listeye eklemeniz yeterli olacaktır. rsync komutunu, diyelim ki, başka bir seçenek eklemek için değiştirmeniz gerekirse, düzenleme yapacağınız yalnızca bir örnek vardır. Geleneksel programlamada olduğu gibi, kabuk komut dosyalarında kesme ve yapıştırma işlemlerinden kaçınmanız gerekir.


İyi bir bağımsız değişken oluşturma

Diğer kabuk komut dosyaları, işlemek için bağımsız değişkenler ya da öğelerin (dosyalar, dizinler, bilgisayar adları gibi) dinamik bir listesini gerektirir. Örnek olarak verilen Liste 2, eşzamanlamak istediğiniz bilgisayarların adlarını yazdığınız komut satırını kullanmanıza olanak tanıyan, önceki örneğin değişik bir şeklidir.



Liste 2. İşlenecek bilgisayarların adını belirtmenize olanak tanıyan, Liste 1'in değişik bir şekli
        
#! /bin/zsh

for each machine
    rsync -e ssh --times --perms --recursive --delete $HOME $machine:
end

Liste 2'yi synch.zsh adlı bir dosyada sakladığınızı düşünürsek, ana dizininizi moe, larry ve curly adlı bilgisayarlara kopyalamak için komut dosyasını zsh synch.zsh moe larry curly şeklinde çağırmanız gerekir.

foreach yapısında listenin olmaması bir yazım hatası değil: Bir listeyi atlarsanız, foreach yapısı, komut satırından belirtilen bağımsız değişkenler listesini işler. Komut satırı bağımsız değişkenlerine de konum parametreleri denir. Bunun nedeni, komut satırındaki bağımsız değişkenin konumunun genellikle semantik olarak önemli olmasıdır.

Örnek olarak, hiçbir bağımsız değişken belirlemezseniz, yararlı bir kullanım iletisi sağlamak için Liste 2, konum parametrelerinin varlığından ya da yokluğundan yararlanabilir. Geliştirilmiş komut dosyası Liste 3'te gösterilmiştir.



Liste 3. Herhangi bir bağımsız değişken sağlanmazsa, birçok komut dosyası yararlı iletiler sağlar
 
#! /bin/zsh

if [[ -z $1 || $1 == "--help" ]] 
then
    echo "usage: $0 machine [machine ...]
fi

foreach machine
    rsync -e ssh --times --perms --recursive --delete $HOME $machine:
end

Çağrılan komut dosyasının adını da içeren komut satırındaki boşlukla ayrılmış her bir dize bir konum parametresi olur. Diğer yandan, synch.zsh komutunda yalnızca bir konum parametresi ($0) vardır. synch.zsh --help komutunda iki konum parametresi vardır: $0 ve $1 (burada $1, --help dizesidir).

Dolayısıyla, Liste 3 şunu söylemektedir: "İlk konum parametresi boşsa (-z işleci boş dize olup olmadığını sınar) ya da (|| ile belirtilir) ilk parametre '--help' değerine eşitse, bir kullanım iletisi yazdır." (Komut dosyaları yazmaya başlarsanız, ipucu olarak her birinde bir kullanım iletisi sağlamayı düşünebilirsiniz. Diğer kullanıcılara, hatta unutursanız size, komut dosyasının nasıl kullanılacağını hatırlatır.)

[[ -z $1 || $1 == "--help" ]] sözcük grubu, if deyiminin koşuludur, ancak aynı koşulu, bir komut olarak kullanabilir ve komut dosyanızdaki akışı denetlemek için diğer komutlarla birleştirebilirsiniz. Liste 4'e bir bakın. $PATH değişkeninizdeki tüm yürütülebilir komutlar sıralanır ve koşullar, uygun çalışmayı gerçekleştirmek üzere diğer komutlarla birlikte kullanılır.



Liste 4. $PATH değişkeninizdeki komutların listelenmesi
    
#! /bin/zsh

directories=(`echo $PATH | column -s ':' -t`) 

for directory in $directories
do
  [[ -d $directory ]] || continue
  
  pushd "$directory"
  
  for file in *
  do
      [[ -x $file && ! -d $file ]] || continue
      echo $file
  done
  
  popd
done | sort | uniq

Bu komut dosyasında birçok şey olup bitiyor. O zaman gelin komut dosyasını parçalara ayıralım:

  1. Komut dosyasının ilk gerçek satırı olan directories=(`echo $PATH | column -s ':' -t`), belirtilen dizinleri içeren bir dizi oluşturur. Bağımsız değişkenlerinizi parantez içine alarak directories=(...) satırında olduğu gibi zsh içinde bir dizi oluşturursunuz. Bu durumda, dizinin öğeleri, dizinlerin boşlukla birbirinden ayrılmış bir listesini oluşturmak üzere (column değerinin -t bağımsız değişkeni) her bir sütunda (column -s ':') $PATH değişkeninin bölünmesiyle oluşturulur.
  2. Listedeki her bir dizin için komut dosyası, dizindeki yürütülebilir dosyaları sıralamayı dener. Adım 3 ile 6 arasında işlem açıklanmıştır.
  3. [[ -d $directory ]] || continue satırı, bir short-circuiting (kısa devre) komutu örneğidir. Bir short-circuiting komutu, mantıksal koşulları tanımlayıcı bir sonuç "oluşturur oluşturmaz" sona erer.

    Örneğin, [[ -d $directory ]] || continue sözcük grubu, bir mantıksal OR (||) kullanıp ilk komutu yürütür ve yalnızca, ilk komut başarısız olursa ikinci komut yürütülür. Dolayısıyla, $directory değişkenindeki giriş varsa ve bir dizinse (-d işleci), sınama başarılı olur, değerlendirme sona erer ve geçerli öğenin işlenmesini atlayan continue komutu hiçbir zaman yürütülmez.

    Ancak, ilk sınama başarısız olursa, mantıksal OR'un bir sonraki koşulu continue komutunu yürütür. (continue her zaman başarılı olduğundan genellikle short-circuiting komutlarının sonunda yer alır.)

    Mantıksal AND (&&) işlecine dayalı short-circuiting, ilk komutu yürütür ve daha sonra, yalnızca ama yalnızca ilk komut başarılı olduysa ikinci komutu yürütür.

  4. pushd ve birlikte kullanılan popd, işlemeden önce yeni bir dizine geçmek ve işlemeden sonra önceki dizine dönmek için kullanılır. Dosya sistemindeki yerinizi korumak için dizin yığınını kullanmak iyi bir komut dosyası yöntemidir.
  5. İç for döngüsü, geçerli çalışma dizinindeki tüm dosyaları sıralar. Joker karakter olan * (yıldız) her şeyi eşleştirir ve daha sonra, her bir girişin dosya olup olmadığını sınar. [[ -x $file && ! -d $file ]] || continue satırı, "$file varsa ve yürütülebiliyorsa ve bir dizin değilse, bunu işle; tersi durumunda, devam et (continue)" der.
  6. Son olarak, önceki tüm koşullar karşılandıysa, dosyanın adı echo ile yazdırılır.
  7. Komut dosyasının son satırını fark ettiniz mi? Çoğu denetim yapısının çıkışını başka bir UNIX komutuna gönderebilirsiniz. Sonuçta kabuk, denetim yapısına bir komut gibi davranır. Bununla birlikte, tüm komut dosyasının çıkışı sort ile bağlanır ve daha sonra, $PATH dizininizde bulunan benzersiz komutların, alfabetik sırayla dizilmiş bir listesini oluşturmak için uniq kullanılır.

Liste 4'ü listcmds.zsh adlı yürütülebilir bir dosyaya kaydederseniz, çıkış aşağıdaki gibi görünür:

$ ./listcmds.zsh
[
a2p
ab
ac
accept
accton
aclocal

Short-circuiting komutları, komut dosyalarında çok işe yarar. Bir koşulu ve bir işlemi tek bir yerde birleştirir. Her UNIX komutu başarıyı ya da başarısızlığı belirten bir durum kodu döndürdüğünden, herhangi bir komutu yalnızca sınama işleci olarak değil, aynı zamanda bir koşul olarak da kullanabilirsiniz. Kural olarak UNIX komutları başarılı yürütme için sıfır (0), hata için sıfır dışı bir değer döndürür. Sıfır dışı değer, ortaya çıkan hatanın türünü belirtir.

Örneğin, [[ -d $directory ]] || continue satırının yerine cd $directory || continue yerleştirilseydi, pushd ve popd, Liste 4'ten kaldırılabilirdi. cd komutu başarılı olursa, 0 değerini döndürebilir ve mantıksal OR'un değerlendirmesi hemen sona erebilir. Ancak cd başarısız olursa, sıfır dışında bir değer döndürülür, değerlendirme devam eder ve continue komutu yürütülür.


Kaldırmayın. Arşivleyin!

Modern UNIX kabukları (bash, ksh, zsh), karmaşık komut dosyaları oluşturmanız için birçok denetim yapısı ve işlemi sunar. Verileri bir formdan diğerine geçirmek için tüm UNIX komutlarını çağırabilirsiniz; bu nedenle, kabuk komut dosyası oluşturma neredeyse C ya da Perl gibi tam bir dilde programlama yapmak kadar zengindir.

Kişisel ya da sistemle ilgili hemen her tür işi mekanikleştirmek için komut dosyalarını kullanabilirsiniz. Komut dosyaları verileri izleyebilir, arşivleyebilir, güncelleyebilir, karşıya ve karşıdan yükleyebilir ve dönüştürebilir. Bir komut dosyası tek bir satır olabileceği gibi çok büyük bir altsistem de olabilir. Bir kabuk komut dosyası için hiçbir iş çok küçük ya da çok büyük değildir. Gerçekten de /etc/init.d dizininize bakarsanız, bilgisayarınızı her başlatışınızda hizmetleri başlatan kabuk komut dosyalarının çok çeşitli olduğunu görürsünüz. Çok yararlı bir komut dosyası oluşturursanız, bu dosyayı sistem çapında kullanılan bir yardımcı program olarak da yerleştirebilirsiniz. Bunun için, dosyayı kullanıcıların $PATH içindeki bir dizinine atmanız yeterlidir.

Gelin, yeni kurulan mojo'nuzu uygulamak için bir yardımcı program oluşturalım. Myrm komut dosyası, sistemin kendi rm'sinin bir yedeğidir. Myrm, bir dosyayı doğrudan silmek yerine bir arşive kopyalar, dosyayı daha sonra bulabilmeniz için benzersiz bir adla adlandırır ve özgün dosyayı kaldırır. Myrm komut dosyası işlevsel, ancak basittir ve birçok nitelik ve ayrıntı ekleyebilirsiniz. Buna eşlik etmesi için kapsamlı bir unrm ("un-remove;kaldırma") komut dosyası da yazabilirsiniz. (Çeşitli uygulamaları görmek için Internet'te arama yapabilirsiniz.)

Liste 5'te myrm komut dosyası gösterilmektedir.



Liste 5. Dosya sisteminden kaldırılmadan önce bir dosyayı yedekleyen basit bir yardımcı program
        

#! /bin/zsh

backupdir=$HOME/.tomb
systemrm=/bin/rm

if [[ -z $1 || $1 == "--help" ]]
then
  exec $systemrm
fi

if [[ ! -d $backupdir ]]
then
  mkdir -m 0700 $backupdir || echo "$0: Cannot create $backupdir"; exit
fi

args$=$( getopt dfiPRrvw $* ) || exec $systemrm

count=0
flags = ""
foreach argument in $args
do
  case $argument in
    --) break;
        ;;

     *) flags="$flags $argument";
        (( count=$count + 1 ));
        ;;
  esac
done
shift $(( $count ))

for file
do
  [[ -e $file ]] || continue
  copyfile=$backupdir/$(basename $file).$(date "+%m.%d.%y.%H.%M.%S")
  /bin/cp -R $file $copyfile
done

exec $systemrm $=flags "$@"

Daha önce anlatılmamış birkaç yeni şey olsa da kabuk komut dosyasını okunabilir bulmuş olmalısınız. Şimdi bunlara bir göz atalım ve daha sonra, tüm komut dosyasını gözden geçirelim.

  1. Kabuk, cp ya da ls gibi bir komutu başlattığında, komut için yeni bir işlem oluşturur ve daha sonra, devam etmeden (alt) işlemin tamamlanmasını bekler. exec komutu da bir komut başlatır, ancak yeni bir işlem oluşturmak yerine exec, geçerli işlemi (kabuk [ya da komut dosyası] işlemi) yeni komutla "değiştirir". Diğer bir deyişle, exec, yeni bir görevi başlatmak için aynı işlemi yeniden kullanır. Komut dosyası bağlamında bir exec, komut dosyasını hemen "sonlandırır" ve belirtilen görevi başlatır.
  2. getopt UNIX yardımcı programı, belirttiğiniz bağımsız değişkenlere ilişkin konum parametrelerini tarar. Burada, dfiPRrvw listesi, -d, -f, -i, -P, -R, -r, -v ve -w'yi arar. Başka bir seçenek görünürse, getopt başarısız olur. Tersi durumunda, getopt, -- özel dizesiyle sona eren seçeneklerden oluşan bir dize döndürür.
  3. shift komutu, soldan sağa konum parametrelerini kaldırır. Örneğin, komut satırı myrm, -r -f -P file1 file2 file3 şeklinde olsaydı, shift 3 komutu, $0, $1 ve $2'yi ya da sırasıyla -r, -f ve -P parametrelerini kaldıracaktı. file1, file2 ve file3, yeni $0, $1 ve $2 olarak yeniden numaralandırılır.
  4. case deyimi, geleneksel programlama dillerindeki benzerleri gibi çalışır: Bağımsız değişkenini bir listedeki her bir kalıpla karşılaştırır; bir eşleşme bulunduğunda, karşılık gelen kod yürütülür. Kabuktaki gibi *, her şeyi karşılaştırır ve başka bir eşleşme bulunmazsa, varsayılan işlem olarak kullanılabilir.
  5. $@ mührü, (kalan) tüm konum parametrelerini açar.
  6. zsh işleci olan $=, sözcükleri boşluklara göre böler. $=, uzun bir dizeniz olduğunda ve dizeyi ayrı bağımsız değişkenlere bölmek istediğinizde yararlı olur. Örneğin, x değişkeni, beş karakterli bir sözcük olan '-r -f' dizesini içeriyorsa, $=x, dizeyi -r ve -f olmak üzere iki ayrı sözcüğe dönüştürür.

Bu bilgilerin ışığında artık komut dosyasını tam olarak parçalara ayırabiliyor olmanız gerekir. Koda bir de bloklar halinde bakalım:

  • İlk blok, komut dosyasında kullanılan değişkenleri belirler.
  • Sonraki blok daha tanıdık gelebilir: Herhangi bir bağımsız değişken sağlanmazsa, bu bir kullanım iletisi yazdırır. Neden exec komutuyla rm yardımcı programını çalıştırır? Bu komut dosyasını "rm" olarak adlandırırsanız ve bunu $PATH dizininizde daha önde bir konuma yerleştirdiyseniz, /bin/rm dizininin yerini tutabilir. Komut dosyası için kötü bir seçenek /bin/rm için de kötü bir seçenektir. Bu nedenle, komut dosyası /bin/rm'nin kullanım iletisi sağlamasına izin verir.
  • Sonraki blok, yoksa, yedekleme dizinini oluşturur. mkdir komutu başarısız olursa, komut dosyası uygun bir hata iletisiyle sonlanır.
  • Sonraki blok, konum bağımsız değişkenleri listesinde dash bağımsız değişkenlerini bulur. getopt başarılı olursa, $args bir seçenekler listesini içerir. Bir seçenek tanınmadığında olduğu gibi getopt başarısız olursa, bir hata iletisi yazdırılır ve komut dosyasından bir kullanım iletisiyle çıkar.
  • Sonraki blok, rm için tasarlanmış tüm seçenekleri bir dizede yakalar. Özel getopt seçeneği olan -- seçeneğiyle karşılaşıldığında toplama durdurulur. shift, işlenen tüm bağımsız değişkenleri bağımsız değişken listesinden kaldırırken, işlenecek dosyaların ve dizinlerin listesini bırakır.
  • for file ile başlayan blok, her bir dosyanın ya da dizinin, koruma için kişisel "gizli yerinize" kopyalandığı yerdir. Her bir dosyanın dizini olduğu gibi (-R) bu gizli yere kopyalanır ve kopyanın benzersiz olmasını ve aynı adı paylaşan önceden arşivlenmiş bir girişle karışmaması için kopyanın sonuna geçerli tarih ve saat eklenir.
  • Son olarak, dosya ya da dizin, komut dosyasına geçirilen aynı komut satırı seçenekleri kullanılarak kaldırılır.

Az önce (yanlışlıkla?) sildiğiniz dosyaya ya da dizine yeniden ihtiyacınız olursa, ilk kopya için arşivinize bakabilirsiniz!


Devam edin ve otomatikleştirin

UNIX ile ne kadar çalışırsanız, o kadar çok komut dosyası hazırlarsınız. Komut dosyaları, karmaşık ve uzun komut sıralarını yeniden yazmak için gereken zamandan ve enerjiden tasarruf sağlarken, hataları da önler. Internet, başkalarının birçok amaç için oluşturduğu yararlı komut dosyalarıyla doludur. Yakında, siz de kendi sihirlerinizi gönderiyor olacaksınız.

Kaynaklar

Bilgi Edinme

Ürün ve teknoloji edinme

Tartışma


Yazar hakkında

Martin Streicher, Linux Magazine adlı dergide Şef Editör'dür. Purdue University'den bilgisayar bilimi konusunda master derecesi almış ve 1982 yılından bu yana UNIX benzeri sistemlerde Pascal, C, Perl, Java ve (en son) Ruby programlama dillerinde programlama yapmaktadır.

İlgili Yazılar

UNIX Dilinde Konuşma, Bölüm 7: Komut satırı deyimleri

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

UNIX Dilinde Konuşma, Bölüm 3: Her şeyi komut satırından yapın

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

Geniş bir Linux kümesinin kurulması, Bölüm 1: Giriş ve Donanım Konfigürasyonu

tongucyumruk

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

GTK+ ile ilgili temel bilgiler, Bölüm 3: GTK+ nasıl yayılır?

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan yeni bir IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.

developerWorks: Linux için dokunmaya duyarlı ekran kurulması

butch

IBM Türkiye ve Fazlamesai.net işbirliği ile dilimize kazandırılan ikinci IBM developerWorks makalesi ile karşınızdayız. Diğer makalelere buradan ulaşabilirsiniz.

Makalenin özgün haline bu adresten ulaşabilirsiniz.