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:

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:

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.