-
-
Save pasali/5272260 to your computer and use it in GitHub Desktop.
#include<stdio.h> | |
#include<stdlib.h> | |
#include<sys/types.h> | |
#include<unistd.h> | |
#include<string.h> | |
#include<readline/readline.h> | |
#include<readline/history.h> | |
#include<sys/wait.h> | |
/* pasali */ | |
static char * arguments[10]; | |
static char * komut; | |
void argv_free(void); | |
int run(void); | |
int parse(char *); | |
int | |
main(void) | |
{ | |
while(1) { | |
komut = readline("#> "); | |
if (komut == NULL) { | |
exit(EXIT_SUCCESS); | |
}else if (!strcmp(komut, "")) { | |
continue; | |
} | |
else { | |
parse(komut); | |
run(); | |
argv_free(); | |
} | |
free(komut); | |
} | |
atexit(argv_free); | |
return 0; | |
} | |
void | |
CD(void) | |
{ | |
if (!strcmp(arguments[1], "~") || !strcmp(arguments[1], " ") || !strcmp(arguments[1], "")) { | |
chdir(getenv("HOME")); | |
}else if (!strcmp(arguments[1], ".")) { | |
/* none */ | |
}else if (!strcmp(arguments[1], "..")) { | |
char * parent_directory = strrchr(getenv("PWD"), '/'); | |
char * pwd = getenv("PWD"); | |
int index = parent_directory - pwd; | |
pwd[index] = '\0'; | |
chdir(pwd); | |
}else | |
chdir(arguments[1]); | |
main(); | |
} | |
void | |
argv_free(void) | |
{ | |
int i; | |
for (i = 0; arguments[i]; i++) { | |
free(arguments[i]); | |
} | |
} | |
int | |
run(void) | |
{ | |
int status; | |
pid_t pid; | |
pid = fork(); | |
if (!pid) { | |
int ret; | |
if ( **arguments == '/') | |
ret = execv(arguments[0], arguments); | |
else | |
ret = execvp(arguments[0], arguments); | |
if (ret == -1) { | |
perror("execv"); | |
exit(EXIT_FAILURE); | |
} | |
} else if (pid == -1) | |
perror("fork"); | |
if (waitpid(-1, &status, 0) == -1) { | |
perror("waitpid"); | |
exit(EXIT_FAILURE); | |
} | |
return 0; | |
} | |
int | |
parse(char *string) | |
{ | |
char ** str_p = &string; | |
char * token; | |
int len; | |
add_history(string); | |
for (len = 0; (token = strsep(str_p, " \t")); len++) { | |
arguments[len] = strdup(token); | |
} | |
arguments[len] = NULL; | |
if (!strcmp(arguments[0], "exit") || !strcmp(arguments[0], "quit")) { | |
exit(EXIT_SUCCESS); | |
} | |
if (!strcmp(arguments[0], "cd")) { | |
CD(); | |
} | |
if (komut[0] != '/' && strchr(komut, '/')) { | |
printf("full path of program or just name !!!\n"); | |
main(); | |
} | |
return 0; | |
} |
@pasali: Tartışmayı buradan yürütelim :-)
Hocam, yorumlarınızı dikkate alarak kodu biraz değiştirmeye çalıştım. Yalnız argv[k]= NULL; sorunu çözmedi. Bu hatayı readline() işin içine girdikten sonra almaya başladım bence onda bir sorun var :) PATH ile ilgili söylediklerinizi, bu kodu hatasız çalıştırınca deneyecektim. Malesef Make it Work aşamasını geçemedim daha. Bir de argv için ayırdığım bellek alanlarını free() ederken hata alıyorum hocam.
argv[k] = NULL
eklediğin bu son sürümde mailda bahsettiğin "program ilk komutta hata veriyor ikinci komutta ise çalışmaya başlıyor." hatasını bu makinde almıyorum. Kastettiğim bu idi. Kodun tamamını incelemeden yaptığım ilk değişiklikti bu. Platforma bağlı olarak başka hatalar alabilirsin. Ona ayrıca bakmak lazım.
argv
nin free
edilmesine gelince... argv
'de sadece "malloced" edilen elemanları "free" etmelisin, diğer elemanları free etmeye teşebbüs etme. Yani şöyle bir şey lazım sana:
for (i = 0; argv[i] != NULL && i < 10; i ++)
free(argv[i]);
strdup
kullanmışsın güzel. Ama hala kodunda modülerleşmemiş yerler var. Örneğin " stringi bosluklara göre parse edip diziye atıyoruz" kısmı. Bunu ayrı bir fonksiyon yap, okunurluk çok artar. Unutmayın kodda bir bloğu düzgün bir comment'le ifade edebiliyorsanız pek çok durumda o düzgün comment "beni bir işleve çevir" işareti veriyor demektir. Bu işareti hemen görün ve comment yazmak yerine bir işlev yazın. Böylelikle karmaşık bir problemi parçalara ayırmış olursunuz. Karmaşayla mücadele etmenin en etkili yoludur bu. Yani:
//stringi bosluklara göre parse edip diziye atıyoruz.
...
yerine
strsplit(string, argv);
Bu şekilde hem kod okunurluğu artar hem de parçalar ayrı ayrı test edilebilir.
Ayrıca ilkemizi hep hatırlayın, "en garantili iş, hiç yapılmamış iştir". Bak şimdi yukarıda "strsplit" olarak isimlendirdiğim bir işleve ihtiyaç duyduğumuzu gösterdim. Hemen balta kürek işe başlamadan önce, tıpkı strdup
'ta yaptığımız gibi; dur, nefeslen ve araştır; bu sık yapılan bir işlem olduğunda göre belki birileri sizin için bunu zaten yazmıştır. Bak mesela strsep
var (hatırladığım kadarıyla standart kitaplıkta yok; bu bir BSD eklentisi):
@roktas hocam, free() olayını halletim hocam sayenizde, string split etmek için fonksiyon aradım başlarda strtok diye bir fonksiyon buldum ama anladığım kadarıyla sorunluymuş bende parser() o amaçla yazdım. Son olarak hocam artık "ikinci seferde değil, 7-8 sefer sonrası çalışıyor program" :) Bir printf() koyup argv[0] kontrol edeyim dedim:
#> date
datep�v�rd-word
execv: No such file or directory
#> date
datep�v�"
execv: No such file or directory
#> date
datep�v�
execv: No such file or directory
böyle bir sonuç aldım.
Bellek ayırırken bir yerlerde hata yapıyorsun, anlamı basitçe bu :-) argv
ve buna yazdığın geçici stringleri dikkatlice incele; ben bulmayayım onu :-)
Diğer hususlar:
strsep
'i hala kullanmamışsın. Kendinize eziyet etmeyin :-) Bak kılavuz dokümanı verdim, ondan yararlan.parse
işlevinin tasarımı kusurlu. Bu işlev ismine bakılacak olursa sadece ayrıştırma işlemi yapmalıydı. Halbuki bununla yetinmiyor, bir de çalıştırıyor. İkisini yaniparse
verun
'ı ayırman halinde "argv
'yi bunlar arasında nasıl aktaracağım?" sorusunu cevaplamaktan korkmuşsun sanıyorum :-) İpucu:argv
ve hatta satır tamponu tüm işlevler arasında paylaşılan bir statik global değişken olabilir mi?) Eğer bu ipucunu değerlendirmek istemiyorsan işlevin ismini düzelt en azından:parse_run
gibi. Ama unutma bu bir ilkenin ihlali olur: "Bir program/fonksiyon/sınıf vs, sadece bir işi yapmalı, onu da iyi yapmalı".
@roktas hocam dedikleriniz doğrultusunda kodu düzenledim. Şuan /bin/ altındaki bütün programlar için sorunsuz çalışıyor :) Şimdi PATH içinde aratma kısmını yapmaya çalışıyorum.
- Kritik değil ama
strtok
değilstrsep
kullan.strtok
kötü bir fonksiyondur :-) (ipucu: reentrant değil) Man linki gönderdim ya sana, oradaki örneği adapte et gitsin,strtok
ile niye uğraşıyorsun? - Kodunda hala ciddi stil sorunları var. Anahtar kelimelerden sonra boşluk bırakmamak, hatalı girintiler, girinti seviyesi (1 tab: 8 boşluk) vs.
- fonksiyon ismini bir eylem yap:
parser
değilparse
. Sınıf olsaydıparser
olurdu ama buraya uymaz. Bakın bu isimlendirmelere dikkat edin lütfen. Çok yalap şalap isimler kullanıyorsunuz :-) - Ansi C üzerinden gitmeye çalış. Ansı C'de deklarasyonlar hemen işlevin girişinde yapılır, arada yapılmaz. Aşağıdaki seçeneklerle derleme yap ve uyarıları sıfırla lütfen:
$ cc -pedantic -ansi -Wall -o kabuk kabuk.c
Readline'ı nasıl kullanıyoruz? Şu örneği de bir incele: http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC48
Nispeten kompleks bir örnek bu. Her tarafını anlamak zorunda değilsin. :-)
@roktas hocam , strsep ve fonksiyon isimlerini düzellettim. ANSI C ile ilgili uyarıları düzeltirken strdup , waitpid ve strsep için "örtük bildirim" uyarısı veriyor. Hazır fonksiyonlarında prototipini yazmak mı lazım ? Bir de tab için 4 ve ya 8 boşluk fark eder mi ? Benim gözüme 4 daha düzgün geldiği için 4 kullanıyorum :) Bu örneği bende görmüştüm add_history() yi burdan aldım. Ama nereye kaydettiğini ya da nasıl bizim verdiğimiz bir dosyaya yazacak bilmiyorum :)
strsep
ANSI'de bulunmadığından uyarı alman normal. string.h
'da tanımı bulamıyor ve "örtük olarak" tanımlıyor. waitpid
için ise sys/wait.h
eklemen gerekir. Derlemede -ansi
anahtarını kaldırarak (diğerleri kalsın) devam et.
Stil konusunda şu erken yaşlarda "gözüme bu daha hoş geldi" diyerek moda uydurmayın :-) büyükler ne yapmışsa onları izleyin. Bakın uymanız gereken stil şu:
http://www.openbsd.org/cgi-bin/man.cgi?query=style§ion=9
(bu da olabilir: https://www.kernel.org/doc/Documentation/CodingStyle)
Şimdilik readline
'sız tamamla, sonra ona da bakarız.
Stil düzeltmek için:
$ sudo apt-get install astyle
$ astyle --style=linux shellim.c
$ git diff # farkı gör
@pasali: programın ciddi bir sorunu var, bu kabuktan normal yolla nasıl çıkacağız?
- Kullanıcı
Ctrl-d
tuşuna basar. Bunu nasıl yakalayacağız? Kolay,readline
bu durumdaNULL
döner. Bunu gerçeklemen kolay. - Kullanıcı
exit
veyaquit
yazar. Bunu da gerçeklemen kolay,strcmp
vs ile.
Hadi bakalım bunları da bir ekle :-) Türkiye'de çalışana böyle yapıyorlar biliyorsun. Ne kadar iş yaparsan o kadar ekstra iş alırsın. :-) Bu ödevi sallayarak yatan arkadaşlara duyurulur. :-)
Aferin iyi gidiyorsun...
Teşekkürler hocam , Ctrl -d ve exit- quit i ekledim. Stilimi de değiştirmeye çalşıyorum.
Birde @roktas hocam gcc ve ya cc derleyicilerinin verdiği hataları ingilizce olarak vermesini nasıl sağlarız ? Türkçe ile bir sorunum yok ama ingilizce olunca googleda daha kolay cevap bulunuyor :)
İngilizce mesajlar için:
$ LC_ALL=C cc ...
şeklinde derle.
Programın exit
veya Ctrl-d
ile sonlanması, programın hatasız sonlanması demektir (yani exit(1)
yerine exit(EXIT_SUCCES)
).
Düzelttim hocam " cc -pedantic -Wall csh.c -lreadline " derleyince hata ve ya uyarı gelmiyor artık :)
Güzel :-) Stili de düzelt artık. Bu stille nasıl rahat ediyorsun bilmiyorum. :-) astyle
...
En kısa zamanda onuda hallederim inşallah :)
Yavrum 10 sn'lik bir işlemden bahsediyoruz:
$ git clone [email protected]:5272260.git # hala klonlamamışsan
$ cd 5272260
$ astyle --style=linux *.c
$ git commit -a -m "stil düzelt"
$ git push origin master
Bundan sonra stili bozuk bir koda review yapmayacağım, notu düşüreceğim. Duyan duymayana bildirsin. Başka türlü anlatamıyorum meseleyi size.
Peki soru? Kullanıcı programdan çıkarken kullanılan belleğin boşaltılmasını nasıl garanti altına alırız (her ne kadar İşletim Sistemi bu işlemi her sonlanan proses için yapsa bile)... İpucu: atexit
@roktas hocam, ben stili elimin alışması bakımından söylemiştim tam ifade edememişim kendimi :) Bellekle ilgili olarak argv_free() zaten istenileni yapmıyor mu hocam ? Orayı anlayamadım.
exit
işlevi çağrıldığı noktada program sonlanır; argv_free
'nin çağrıldığı noktaya dikkat et ;-)
Onuda ekledim hocam. Son bir cd işlevi kaldı onu bu akşamlık pes ettim yarın o da inşallah çalışacak :)
@pasali: Programda sıkıntılar var:
execv
işlevinde ikinci argüman yaniargv
,NULL
ile sonlandırılmış bir string dizisi olmalı. Ör.{ "/bin/date", NULL }
gibi.argv
'yiNULL
ile kapatmadığından mailda bahsettiğin hatayı alıyorsun. 31'nci satırdakifor
döngüsünden hemen sonra örneğin 43'nci satıraargv[k] = NULL;
eklersen sorun çözülür.bin
altında aranıyor. Bu doğru bir lojik değil. Komut tam yolla verilmişse (ör./foo/bar/baz
) değiştirmeden çalıştırılmalı, aksi halde (ör.bar
)PATH
'te aratarak çalıştırılmalı. Bu ikincisi için iseexecv
değilexecvp
daha uygun olur (YOL
değişkenindenPATH
'e aktarım konusunu bir yana bırakıyorum).strdup
Şimdilik bu kadar :-)