Budistlerde bir prensip vardır : Bir işyap ama yapabildiğin en iyisini yap.
Bu prensip UNIX'in çalışma prensibine çok yakındır.Evet bir windows programıakla gelebilecek bütün işleri yapmaya çalışırken tipik bir UNIX programısadece bir işyapar ama yaptığıişi iyi yapar.
UNIX kullanıcısıbu tip programlarıbirleştirerek çeşitli kabuk scriptleri ile boru (pipe) kullanarak kendi uygulamalarını üretir.
Bir UNIX programıyazarken problemimizin bazıparçalarınızaten elimizde olan programlar ile çözüp çözemeyeceğinizi gözden geçirmeniz iyi bir fikir olacaktır.Bundan sonra önceden çözülmemişproblemler için kendi programınızıyazmaya devam edebilirisiniz.
Bu prensibi ara sıra karşılaştığım günlük hayattan bir örnek ile gösterelim:
Bir web sitesinden indirmişolduğum veritabanıdosyasının 11. elamanına ulaşmam gerekiyor. Bu veritabanıdosyasıcsv (virgülle ayrılmışdeğerler : comma seperated values) dosyasıdır.Bu biçim değişik veritabanıprogramlarıkullanan insanlar arasında paylaşımı sağlayan çok genişbir standarttır.
Dosyanın ilk satırıvirgülle ayrılmışalanların listesi şeklindedir.Geri kalan satırlar virgül ile ayrılmışverileri içerir.
Awk programınıvirgülü ayraç olarak kullanacak şekilde
Awk programınıvirgülü ayraç olarak kullanacak şekilde ayarlamaya çalıştım ama bazısatırlar tırnak içinde virgül içerdiğinden bu satırlarda hataya neden oldu.
Bu yüzden CSV dosyasının 11. elamanınıbana getirecek kendi programımıyazmam gerekti. Fakat UNIX'in ruhuna uygun olarak tek yapmam gereken sadece basit bir filtre yazmaktı.Benim filtremin yapmasıgerekenler şunlardır :
Dosyanın ilk satırınısil
Bütün alıntıiçinde olmayan virgülleri farklıbir karakter ile değiştir
Tırnak işaretlerini kaldır.
Kurallara bakılırsa benim dosyadan ilk satırısilmem için sed kullanmam gerekecekti ama bunu boru(pipeline) miktarınıdüşürmek için kendi programımda yapmayı tercih ettim.
Tüm prosedürler ile birlikte programımıtamamlamam benim yaklaşık 20 dakikamıaldı.Oysa baştan CSV dosyasının 11. elemanına ulaşan bir program yazacak olsaydım bundan çok daha fazla tutacaktı ayrıca başka bir veritabanının başka elemanlarına ulaşmak için kullanamayacaktım.
Burada öğretim yaptığımızdan birkaç küçük özellik daha ekleyelim:
Komut satırından ayar okuyabilsin
Yanlışargüman girilirse doğru kullanım mesajı göstersin
Hatalarda anlaşılabilir mesajlar göndersin
Mesela kullanım mesajı:
Usage: csv [-t<delim>] [-c<comma>] [-p] [-o
<outfile>] [-i <infile>]Bütün parametreler opsiyoneldir ve sıraya bağlıolmadan çalışırlar.
-t parametresi virgüllerin ne ile değiştirileceğini belirtir.Varsayılan tab tuşudur.Mesela -t;bütün virgülleri noktalı virgül ile değiştirir.
-c seçeneği şimdilik işimize yaramayacak fakat ileride işimize yarayabileceği için ekledik.Bu virgülden başka bir karakter ile ayırma yapacağımızda kullanılır.Mesela -c@ ile ayraç olarak @ karakterini kullanır ve program içinde bu karakter değiştirilir.Bu mail adreslerinde alan adıile kullanıcıadınıayırmada kullanışlıdır.
-p seçeneği ilk satırıkorumaya yarar bir başka deyişle silmez.Ama başlangıçta varsayılan değer silinmesidir.
-i ve -o değerleri girdi çıktıdosyalarının isimlerini belirtmek için kullanılır.Standart olarak bunlar stdin ve stdout olarak klasik bir UNIX filtresi gibi tanımlıdır.
-i filename ve -i filename biçiminde ikiside kabul edilecektir. Sadece bir girdi ve çıktıdosyasıbelirtilebilir.
Program bittikten sonra 11. elemana ulaşma amacımızı gerçekleştirmek için tek yapmamız gereken :
% csv '-t;' data.csv | awk '-F;' '{print $11}'Bu kod seçenekleri EDX yazmacına kaydeder (Dosya numaraları hariç) : virgül DH yazmacına , yeni ayraç DL yazmacına , -p seçeneği için olan bayrağıEDX yazmacının en yüksek bitine kaydeder.Bu sayede sadece sayının işaretine (pozitif mi negatif mi) olduğuna bakarak bayrağın değerini anlayabiliriz.
Kodumuz burada :
;;;;;;; csv.asm ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Convert a comma-separated file to a something-else separated file.
;
; Started: 31-May-2001
; Updated: 1-Jun-2001
;
; Copyright (c) 2001 G. Adam Stanislav
; All rights reserved.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%include 'system.inc'
%define BUFSIZE 2048
section .data
fd.in dd stdin
fd.out dd stdout
usg db 'Usage: csv [-t<delim>] [-c<comma>] [-p] [-o <outfile>] [-i <infile>]', 0Ah
usglen equ $-usg
iemsg db "csv: Can't open input file", 0Ah
iemlen equ $-iemsg
oemsg db "csv: Can't create output file", 0Ah
oemlen equ $-oemsg
section .bss
ibuffer resb BUFSIZE
obuffer resb BUFSIZE
section .text
align 4
ierr:
push dword iemlen
push dword iemsg
push dword stderr
sys.write
push dword 1 ; return failure
sys.exit
align 4
oerr:
push dword oemlen
push dword oemsg
push dword stderr
sys.write
push dword 2
sys.exit
align 4
usage:
push dword usglen
push dword usg
push dword stderr
sys.write
push dword 3
sys.exit
align 4
global _start
_start:
add esp, byte 8 ; discard argc and argv[0]
mov edx, (',' << 8) | 9
.arg:
pop ecx
or ecx, ecx
je near .init ; no more arguments
; ECX contains the pointer to an argument
cmp byte [ecx], '-'
jne usage
inc ecx
mov ax, [ecx]
.o:
cmp al, 'o'
jne .i
; Make sure we are not asked for the output file twice
cmp dword [fd.out], stdout
jne usage
; Find the path to output file - it is either at [ECX+1],
; i.e., -ofile --
; or in the next argument,
; i.e., -o file
inc ecx
or ah, ah
jne .openoutput
pop ecx
jecxz usage
.openoutput:
push dword 420 ; file mode (644 octal)
push dword 0200h | 0400h | 01h
; O_CREAT | O_TRUNC | O_WRONLY
push ecx
sys.open
jc near oerr
add esp, byte 12
mov [fd.out], eax
jmp short .arg
.i:
cmp al, 'i'
jne .p
; Make sure we are not asked twice
cmp dword [fd.in], stdin
jne near usage
; Find the path to the input file
inc ecx
or ah, ah
jne .openinput
pop ecx
or ecx, ecx
je near usage
.openinput:
push dword 0 ; O_RDONLY
push ecx
sys.open
jc near ierr ; open failed
add esp, byte 8
mov [fd.in], eax
jmp .arg
.p:
cmp al, 'p'
jne .t
or ah, ah
jne near usage
or edx, 1 << 31
jmp .arg
.t:
cmp al, 't' ; redefine output delimiter
jne .c
or ah, ah
je near usage
mov dl, ah
jmp .arg
.c:
cmp al, 'c'
jne near usage
or ah, ah
je near usage
mov dh, ah
jmp .arg
align 4
.init:
sub eax, eax
sub ebx, ebx
sub ecx, ecx
mov edi, obuffer
; See if we are to preserve the first line
or edx, edx
js .loop
.firstline:
; get rid of the first line
call getchar
cmp al, 0Ah
jne .firstline
.loop:
; read a byte from stdin
call getchar
; is it a comma (or whatever the user asked for)?
cmp al, dh
jne .quote
; Replace the comma with a tab (or whatever the user wants)
mov al, dl
.put:
call putchar
jmp short .loop
.quote:
cmp al, '"'
jne .put
; Print everything until you get another quote or EOL. If it
; is a quote, skip it. If it is EOL, print it.
.qloop:
call getchar
cmp al, '"'
je .loop
cmp al, 0Ah
je .put
call putchar
jmp short .qloop
align 4
getchar:
or ebx, ebx
jne .fetch
call read
.fetch:
lodsb
dec ebx
ret
read:
jecxz .read
call write
.read:
push dword BUFSIZE
mov esi, ibuffer
push esi
push dword [fd.in]
sys.read
add esp, byte 12
mov ebx, eax
or eax, eax
je .done
sub eax, eax
ret
align 4
.done:
call write ; flush output buffer
; close files
push dword [fd.in]
sys.close
push dword [fd.out]
sys.close
; return success
push dword 0
sys.exit
align 4
putchar:
stosb
inc ecx
cmp ecx, BUFSIZE
je write
ret
align 4
write:
jecxz .ret ; nothing to write
sub edi, ecx ; start of buffer
push ecx
push edi
push dword [fd.out]
sys.write
add esp, byte 12
sub eax, eax
sub ecx, ecx ; buffer is empty now
.ret:
retYukarıda kullandığımız kodların çoğu hex.asm kodumuzdan alınmıştır.Fakat önemli bir farkla : her bir 'linefeed' karakteri için write çağrısıyapmadık.İsterseniz bu kodu interaktif olarak da kullanabilirsiniz.
Bu bölümü yazmadan önce interaktif problem için daha güzel bir çözüm buldum. Sadece gerektiğinde satırların ayrıyazıldığından emin olmak istiyorum.Hepsinden sonra intereaktif olmayan düzende bütün satırlarıyazdırmaya gerek yoktur.
Yeni çözüm ise write sistem çağrısınıönbelleğin boşolduğu her anda çağırmaktır.Bu yolla interaktif modda çalışırken klavyeden bir satır okur onu işler sonra girişönbelleği boşolursa çıktıyıyazdırır bir sonraki satıra geçer.
Bu değişiklik çok özel durumda oluşan gizli bir kilitlenmeyi önleyecektir.Bunu önbelleklemenin karanlık yüzü olarak adlandıracağız çünkü hemen ilk bakışta farkedilemeyecek bir biçimde karşımıza çıkmaktadır.
Yukarıda CSV programına benzer programlarda ortaya çıkması muhtemel değildir. Ama gelin başka bir filtre programına göz atalım : Diyelim ki bu sefer bizim girdimiz kırmızı, yeşil,mavi (rgb) renk yoğunluklarınıiçeren bir veri dosyasıve çıktımız ise girdinin negatifi olsun.
Bunun gibi filtreyi yazmak gerçekten kolaydır. Kodun büyük bir kısmıdiğer filtreler gibi olacaktır o yüzden sadece içteki döngüyü gösterelim.
.loop: call getchar not al ;Create a negative call putchar jmp short .loop
Bu filtre veri dosyasıüzerinde çalıştığından interaktif olarak kullanmak mümkün olmayacaktır.
Ama bu filtre bir resim işleme yazılımıtarafından kullanılabilir.write çağrısınıread çağrısından önce yapmadıkça muhtemelen kilitlenecektir.
Mesela neler olabilir.
Resim editörü bizim filtremizi C dilinde popen() fonksiyonunu kullanarak yükler.
Resmin ilk satırınıokur.(Resim bitmap yada pixmap formatında olabilir.)
Filtremizin fd.in girişine bağlanan boru (pipe) ile ilk satırıyazar.
Filtremin girdiden aldığıher bir pixeli negatif yapıp çıktıönbelleğine gönderecek.
Filtremiz sonraki pixele geçmek için getchar çağrısını kullanacak.
getchar girişönbelleğini boşbulacak böylece read prosedürünü çağıracak
read ise SYS_read sistem çağrısınıgerçekleştirecek
İşletim sisteminin çekirdeği filtremizi resim düzenleyici program daha fazla veri gönderene kadar askıya alacak.
Resim düzenleyici program ise filtrenin çıkışından almış olduğu işlenmişolan ilk satırıresme ikinci satırıgöndermeden önce yerleştirecek.
Sistem çekirdeği resim editörünü filtreden bir çıktı alıncaya kadar askıya alacak.
Burada resim editörümüz filtrenin çıktıvermesi için beklerken filtre resim editöründen diğer bir satırıbekliyor.Çıktıise önbellekte öylece duruyor.
Resim editörümüz ve filtremiz bir birini böyle sonsuza kadar bekleyecekler(En azından bu işe birileri son verene kadar) . Böylece yazılımlarımız yarışdurumlarına(Race Conditions ) girmiş olacak.
Eğer filtremiz daha fazla veri beklemeden önce önbellekteki veriyi açığa çıkarsa böyle bir problem oluşmayacaktı.