C'de Temel Shell inşa: Komutlar ve Görevler
C'de bir kabuk inşa etmek, basit bir döngüye kadar en iyi yollardan biri olmaya devam ediyor: Bir komut, parse, işlemleri yönetme, kullanıcı girişi ve programları yürütmek.Bash, Zsh ve Fish gibi modern kabuklar inanılmaz derecede sofistike olsa da, temel işlevleri basit bir döngüye kadar kaynatıyor: bir komut, parse, yeni bir süreç oluşturun ve bitirmeyi beklersiniz.[Döneticileri kontrol eder).
Shell Aslında Ne Yapar?
Kalpte, bir kabuk bir komut satırı tercümandır. Kullanıcılara ait komutlar ve kabuk, işletim sistemi tarafından yapılan eylemlere ilişkin bu komutları tercüme eder.In the heart, ashell is a command-line tercüman.It provides a textual Interface where users type command, and the shell translate the shell operator system.When you typeur theTAY:0)
Küçük bir kabuk inşa etmek size öğretir:
- Okuma ve giriş standart girişten giriş
- Çocuk süreçleri, [[0)fork) ile yaratmak ve yönetmek.
- Bir sürecin hafıza imajını [[0)exec) ile yeniden yükleyin.
- Ebeveynleri ve çocuğunu [DÜŞÜNCÜŞÜNÜ:0) Bekleyin[Üye Olmayanlar[Üye Olmayanlar 1 )
- Yaygın hataları özenle bastırın
Bu bina bloklarının anlaşılması, tüm Unix benzeri sistemlerin hood altında nasıl çalıştığını ve sinyal işleme, iş kontrolü ve iş işleme iletişimi gibi daha gelişmiş özellikleri öğrenmek için sağlam bir temel sağlar.
Shell Loop Ayarını Ayarlayın
Her kabuk, kullanıcının çıkış isteğine kadar son zamanlarda tekrarlanan ana döngü etrafında inşa edilir.Bu döngü bir giriş çizgisinde, parsed komut üzerinde çalışır ve sonra parsed komut üzerinde çalışır. en basit versiyon bunu beğenir:Beğenilen komut:
while (1) {
print_prompt();
read_input();
parse_input();
execute_command();
}
C, genellikle [[DÜSÜSÜ:0)DÜŞÜNCÜŞÜŞÜŞÜNÜŞÜNÜŞÜŞÜNÜŞÜNÜŞÜNÜŞÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜŞÜŞÜNÜŞÜŞÜŞÜŞÜŞÜNÜŞÜŞÜŞÜŞÜŞÜŞÜŞÜNÜŞÜNÜŞÜNÜŞÜŞÜNÜŞÜNÜŞÜŞÜŞÜNÜŞÜNÜŞÜNÜŞÜŞÜNÜŞÜŞÜŞÜŞÜŞÜŞÜŞÜŞÜŞÜŞÜN
Edge Cases in Access
Kullanıcı girişi nadiren temizdir. Sağlam bir kabuk boş hatları ele almalıdır, lider ve beyaz alanı takip etmek, son derece uzun komutlar ve son derece uzun komutlar ve son derece net bir şekilde ortaya çıkmak gerekir (Ctrl+D).Eğer kriptonun herhangi bir şeyi yerine getirmeye çalışmadan yeniden ortaya çıkması gerekir.[D)
#define MAX_INPUT 1024
char input[MAX_INPUT];
if (fgets(input, MAX_INPUT, stdin) == NULL) {
printf("\n");
break; // EOF
}
// Remove trailing newline, if present
size_t len = strlen(input);
if (len > 0 && input[len-1] == '\n') {
input[len-1] = '\0';
} else {
// Input too long, flush remaining characters
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}
Komutanları Tartışmaya
Temiz bir giriş dizeniz olduğunda, bunu Jetonlara bölmeniz gerekir. İlk token komut adı (örneğin, [[DüzD:0)[Dönemliler[Dönemliler[Dönemliler) ve kalan jetonlar bu komut için doğru değildir.C standart kütüphaneye sahip değildir.
char *args[MAX_ARGS];
int arg_count = 0;
args[arg_count] = strtok(input, " \t");
while (args[arg_count] != NULL && arg_count < MAX_ARGS - 1) {
arg_count++;
args[arg_count] = strtok(NULL, " \t");
}
args[arg_count] = NULL; // execvp expects a NULL-terminated array
Bu tokenizer, ikisinden daha sofistike bir ⁇ gerektiren değerli bir geliştirmeyi yanlış bir şekilde parçalayacaktır, ancak temel kabuk için bu basit yaklaşım en tek kelime komutlarını çalıştırmak için yeterlidir.)
Tokenizasyondan sonra, ilk token NULL (boş komut) olup olmadığını kontrol etmelisiniz.Eğer öyleyse, sadece bir sonraki iterasyona devam edin.
Yapılı-In Commands
Tüm komutlar yeni bir süreçle uğraşmalıdır. Bazı komutlar, örneğin,FLT:0)cd) ve ) için bir çocuk işlemi iptal etmek gerekir, çünkü kendi başına kabuk durumunu etkileyecektir. Örneğin,FLT:4).
Çıkış Çıkış Çıkış Çıkış Çıkış
[FONT:0][Dönetici[Dönetici], bir çıkış durumunu kabul eder ve hemen doğru bir şekilde aktarabilirsiniz.[Dönetici:0) Sisteme ilk giriş yapın: sadece ilk token eşitler “exit” ve ana döngüden ayrılırsanız, çıkış durumunu kabul edebilirsiniz.
Change Directory (cd)
[FONT=0]cd[D][/FONT=0)[FONT=0)))[0))))))))) Kullanıcının ev dizinine varsayılan olarak (örneğin, [[Dönetici/veya kontrol)))))))))))))))))))))))))))))))))
if (strcmp(args[0], "cd") == 0) {
const char *path = args[1];
if (path == NULL) {
path = getenv("HOME");
if (path == NULL) {
fprintf(stderr, "cd: HOME not set\n");
continue;
}
}
if (chdir(path) != 0) {
perror("cd");
}
continue; // skip fork/exec
}
Fork ile Yaratılış
Bir inşa edilmiş olmayan herhangi bir komut için, kabuğunuz onu idam etmek için bir çocuk süreci yaratmalıdır. [FONTT:0]Fork) Sistem çağrısı, arama işlemine yeni bir işlem oluşturur.Yeni işlem, çocuk olarak adlandırılır ve orijinali 0'dan sonra ebeveyne geri döner.).
pid_t pid = fork();
if (pid == -1) {
perror("fork");
continue;
}
if (pid == 0) {
// Child process
// ...
} else {
// Parent process
// ...
}
Neden Fork?
Neden tümünde ayrı bir süreç oluşturmanız gerektiğini merak edebilirsiniz. sebebi, [FONTD:0)exec[DÜT:1) ilk olarak, çocuğun tamamen mevcut süreci değiştirip, ebeveyn kabuğunu etkilemeden yeni komutlar almaya geri dönmeyeceğini merak edebilirsiniz.
Bir Programı Exec ile yürütmek
[FONT=0]exec[FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=SNT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FONT=FO
if (pid == 0) {
// Child process
execvp(args[0], args);
// If execvp returns, an error occurred
perror("exec");
exit(EXIT_FAILURE);
}
Not the call toETHFLT:0)exit[[Dönetici:2) Aşağıdakiler:2. Terör[DÜye Olmayanlar İçindekiler[Dönemler) veya diğer taraftan da, iki kabuklu koşup, giriş için rekabet eder.
Çocuk Süreci için Bekliyor
Bunu yaptıktan sonra, ebeveyn süreci genellikle tekrar bitirmeden önce bitirmek için bekler.Bu, çocuğun hangi çocuğun beklemesini bekleyebilir (örneğin PIDT:1) veya ) beklenmeyen (Dönetici[D) ve potansiyel olarak engellemeden kaçınmak için seçenekler oluşturabilirsiniz.
int status;
waitpid(pid, &status, 0);
[FONT=0]status[DÜDÜDÜDÜSÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜ
vs. – Non-Blocking
Basit bir şekilde, (geçmiş) ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
Full Shell Birlikte Oluşturun
İşte şimdiye kadar tartışılan tüm parçaları entegre eden tam, minimum kabuk. Dış komutlar için ele alınır ve çocuğun bitirmesini bekler.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_INPUT 1024
#define MAX_ARGS 64
int main(void) {
char input[MAX_INPUT];
char *args[MAX_ARGS];
int should_run = 1;
while (should_run) {
printf("mysh> ");
fflush(stdout);
if (fgets(input, MAX_INPUT, stdin) == NULL) {
printf("\n");
break;
}
// Remove trailing newline
size_t len = strlen(input);
if (len > 0 && input[len-1] == '\n') {
input[len-1] = '\0';
}
// Tokenize
int i = 0;
args[i] = strtok(input, " \t");
while (args[i] != NULL && i < MAX_ARGS - 1) {
i++;
args[i] = strtok(NULL, " \t");
}
args[i] = NULL;
if (args[0] == NULL) {
continue; // empty line
}
// Handle built-in commands
if (strcmp(args[0], "exit") == 0) {
should_run = 0;
continue;
}
if (strcmp(args[0], "cd") == 0) {
const char *path = args[1];
if (path == NULL) {
path = getenv("HOME");
}
if (chdir(path) != 0) {
perror("cd");
}
continue;
}
// Fork and execute external command
pid_t pid = fork();
if (pid < 0) {
perror("fork");
continue;
}
if (pid == 0) {
// Child
execvp(args[0], args);
perror("exec");
exit(EXIT_FAILURE);
} else {
// Parent waits
int status;
waitpid(pid, &status, 0);
}
}
return 0;
}
Bu kod tam, çalışma kabuğudur.Bunu bir dosyaya yazın.()))[Dönetici:0))[Döntgen:2) {0|Dönemli:0|Dönemli: {0|Dönemli) {0|Dönderimler {0|Dönderler {0|Dönemli d|Dönemli d|s d|s d|s d|s d|s d|s d d d d d d d d d d d b b b b b b b b b b b d b b b b b b b d d d b b b b b b b b b b b b b b b b d d d d d b d d d d d d d d b b d d b
Ortak Pitfalls ve Debugging Tips
Bu küçük kod miktarı ile bile, birkaç şey yanlış gidebilir. İşte en sık sorunlar ve onları nasıl düzeltin:
Komuta bulunamadı
Eğer mevcut olmayan bir komut türü varsa (örneğin, [[FONT=0)foobar)) [[FONT=D=3|execvp) geri döner ve çocuk baskıları “exec: No such file or directory” çıktıktan önce ayırt edemez.
Eksik Yeniline veya Truncated Giriş
Eğer hızlı giriş beklemeden görünürseniz, büyük olasılıkla girişte sol karakterler önceki bir çağrıdan uzaktır.Her zaman bunu kontrol edin:0)fgets), tüm çizgiyi tüketmiş (örneğin, null terminatörün yeni bir karakterdir).Eğer daha önce gösterilmezse, girişin geri kalanını da erken gösterildiği gibi sil.
Zombi Süreçleri
Eğer bunu tekrardan çağıran çocuk süreçleri: 0,5. ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
Shell'i genişletmek
Temel kabuk çalışmasına sahipseniz, gerçek dünya kabuğuna daha yakın getiren özellikleri ekleyebilirsiniz.Her özellik size işletim sistemi hakkında daha fazla öğretir.
Giriş/Output Rey
[FONT=0) ve ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇ ⁇
Pipes
Bir komutun çıktısını başka bir şeye ([Dönetici:0) yazdırın.([Dönetici: 1 ).
İş Kontrolü
Arka plan yürütme ([DÜ:0)&[Dönetici: · 5) ve [Dönetici] için iş kurma yeteneği, iş masasını yönetme, [[Dönetici:2)) .
Komutanlık Tarihi
Basit bir tarih mekanizması (daha önceki komutları hatırlamaya/veya geri yükleme) ham terminal modu kullanarak girişleri yakalamayı içerir (TAD:0)tcgetattr). ve [[Dönder|[D][/D][/D][/D][/D][/D][/FONT=)
Ayrıca okuma ve Kaynaklar
Burada sunulan kavramların anlayışını derinleştirmek için aşağıdaki kaynaklar paha biçilmezdir:
- [FONT:0)GNU C Library manuel: Süreç Yaratılış Örnekleri) - [[Dönetici Üzerine Resmi belgeler[Dönetici:2)[Dönetici: 3 ), [[DÜyetim|DÜye Olmayanlar İçindekiler[DÜye Olmayanlar İçindekiler ve Koşullarlar[Üye Olmayanlar İçindekiler)
- [FONT:0) Open Group Base Özellikler: Shell Komut Dili) - POSIX kabuk davranışı için resmi bir spesifik; standart bir eşleşmek istiyorsanız kullanışlı.
- [[0)Wikipedia: Unix kabuk) - Geniş bir kabuk geçmişi, varyantlar ve özellikler hakkında genel bir bakış.
- [FONT:0)Beej'in Interprocess İletişime Rehberi) – Kapaklar borular, FIFOs, mesaj kuyrukları ve pratik örneklerle hafıza paylaştı.
Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç
C'de temel bir kabuk oluşturmak, akademik bir egzersizden daha fazlasıdır; işletim sistemi ile doğrudan bağlantı kurmak için güç verir.Bu yazının temel özetlerini kullanarak, bir programla nasıl okumanız ve parse kullanıcı girişi, nasıl çalıştırılmalıdırFLT:0)Fork için yeni bir işlem oluşturur ve bu makalede verilen tam bir kabuk kodu, çalışmanızın ve uygulamanızın bir araya getirilmesi için gerekli olan bir noktaya kadar uzatılabilir.