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:
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:
- 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.
- 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.
[[ -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.
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.
- İç
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.
- Son olarak, önceki tüm koşullar karşılandıysa, dosyanın adı
echo ile yazdırılır.
- 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.
- 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.
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.
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.
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.
$@ mührü, (kalan) tüm konum parametrelerini açar.
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
- zsh: İşbirliği yapın, tartışın ve zsh deneyiminizi, zsh wiki uygulamasında paylaşın.
- developerWorks bloglarına ve developerWorks topluluğuna katılın.
- AIX ve UNIX forumlarına katılın:
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.
|
|