minishell.c

Download
c 424 lines 11.3 KB
  1// minishell.c
  2// μ™„μ„±λœ λ―Έλ‹ˆ μ‰˜
  3// κΈ°λŠ₯: λ‚΄μž₯ λͺ…λ Ήμ–΄, λ¦¬λ‹€μ΄λ ‰μ…˜, νŒŒμ΄ν”„, μ‹œκ·Έλ„ 처리
  4// 컴파일: gcc -o minishell minishell.c -Wall -Wextra
  5// μ‹€ν–‰: ./minishell
  6
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <unistd.h>
 11#include <sys/wait.h>
 12#include <fcntl.h>
 13#include <signal.h>
 14#include <errno.h>
 15
 16#define MAX_INPUT 1024
 17#define MAX_ARGS 64
 18#define MAX_PIPES 10
 19
 20// ============ μ „μ—­ λ³€μˆ˜ ============
 21static int last_exit_status = 0;
 22
 23// ============ μ‹œκ·Έλ„ ν•Έλ“€λŸ¬ ============
 24void sigint_handler(int sig) {
 25    (void)sig;
 26    printf("\n");
 27    // ν”„λ‘¬ν”„νŠΈ λ‹€μ‹œ 좜λ ₯ν•˜μ§€ μ•ŠμŒ (메인 λ£¨ν”„μ—μ„œ 처리)
 28}
 29
 30// ============ μœ ν‹Έλ¦¬ν‹° ============
 31
 32// λ¬Έμžμ—΄ μ–‘μͺ½ 곡백 제거
 33char* trim(char* str) {
 34    while (*str == ' ' || *str == '\t') str++;
 35
 36    if (*str == '\0') return str;
 37
 38    char* end = str + strlen(str) - 1;
 39    while (end > str && (*end == ' ' || *end == '\t' || *end == '\n')) {
 40        *end-- = '\0';
 41    }
 42
 43    return str;
 44}
 45
 46// ============ νŒŒμ‹± ============
 47
 48int parse_args(char* input, char** args) {
 49    int argc = 0;
 50    char* token = strtok(input, " \t\n");
 51
 52    while (token && argc < MAX_ARGS - 1) {
 53        args[argc++] = token;
 54        token = strtok(NULL, " \t\n");
 55    }
 56    args[argc] = NULL;
 57
 58    return argc;
 59}
 60
 61// ============ λ¦¬λ‹€μ΄λ ‰μ…˜ ============
 62
 63typedef struct {
 64    char* infile;
 65    char* outfile;
 66    int append;
 67} Redirect;
 68
 69void parse_redirect(char** args, Redirect* r) {
 70    r->infile = NULL;
 71    r->outfile = NULL;
 72    r->append = 0;
 73
 74    int i = 0, j = 0;
 75    while (args[i]) {
 76        if (strcmp(args[i], "<") == 0 && args[i+1]) {
 77            r->infile = args[i+1];
 78            i += 2;
 79        } else if (strcmp(args[i], ">") == 0 && args[i+1]) {
 80            r->outfile = args[i+1];
 81            r->append = 0;
 82            i += 2;
 83        } else if (strcmp(args[i], ">>") == 0 && args[i+1]) {
 84            r->outfile = args[i+1];
 85            r->append = 1;
 86            i += 2;
 87        } else {
 88            args[j++] = args[i++];
 89        }
 90    }
 91    args[j] = NULL;
 92}
 93
 94int setup_redirect(Redirect* r) {
 95    if (r->infile) {
 96        int fd = open(r->infile, O_RDONLY);
 97        if (fd < 0) { perror(r->infile); return -1; }
 98        dup2(fd, STDIN_FILENO);
 99        close(fd);
100    }
101    if (r->outfile) {
102        int flags = O_WRONLY | O_CREAT | (r->append ? O_APPEND : O_TRUNC);
103        int fd = open(r->outfile, flags, 0644);
104        if (fd < 0) { perror(r->outfile); return -1; }
105        dup2(fd, STDOUT_FILENO);
106        close(fd);
107    }
108    return 0;
109}
110
111// ============ λ‚΄μž₯ λͺ…λ Ήμ–΄ ============
112
113int builtin_cd(char** args) {
114    const char* path = args[1] ? args[1] : getenv("HOME");
115
116    if (path == NULL) {
117        fprintf(stderr, "cd: HOME ν™˜κ²½λ³€μˆ˜κ°€ μ„€μ •λ˜μ§€ μ•ŠμŒ\n");
118        return 1;
119    }
120
121    if (strcmp(path, "-") == 0) {
122        path = getenv("OLDPWD");
123        if (!path) {
124            fprintf(stderr, "cd: OLDPWD not set\n");
125            return 1;
126        }
127        printf("%s\n", path);
128    } else if (strcmp(path, "~") == 0) {
129        path = getenv("HOME");
130    }
131
132    char oldpwd[1024];
133    getcwd(oldpwd, sizeof(oldpwd));
134
135    if (chdir(path) != 0) {
136        perror("cd");
137        return 1;
138    }
139
140    setenv("OLDPWD", oldpwd, 1);
141    char newpwd[1024];
142    getcwd(newpwd, sizeof(newpwd));
143    setenv("PWD", newpwd, 1);
144
145    return 0;
146}
147
148int builtin_pwd(void) {
149    char cwd[1024];
150    if (getcwd(cwd, sizeof(cwd))) {
151        printf("%s\n", cwd);
152        return 0;
153    }
154    perror("pwd");
155    return 1;
156}
157
158int builtin_echo(char** args) {
159    int newline = 1, start = 1;
160    if (args[1] && strcmp(args[1], "-n") == 0) {
161        newline = 0;
162        start = 2;
163    }
164
165    for (int i = start; args[i]; i++) {
166        // ν™˜κ²½λ³€μˆ˜ ν™•μž₯ ($VAR)
167        if (args[i][0] == '$') {
168            char* val = getenv(args[i] + 1);
169            printf("%s", val ? val : "");
170        } else {
171            printf("%s", args[i]);
172        }
173        if (args[i + 1]) printf(" ");
174    }
175    if (newline) printf("\n");
176    return 0;
177}
178
179int builtin_export(char** args) {
180    if (!args[1]) {
181        extern char** environ;
182        for (char** e = environ; *e; e++) {
183            printf("export %s\n", *e);
184        }
185        return 0;
186    }
187
188    for (int i = 1; args[i]; i++) {
189        char* eq = strchr(args[i], '=');
190        if (eq) {
191            *eq = '\0';
192            setenv(args[i], eq + 1, 1);
193            *eq = '=';
194        }
195    }
196    return 0;
197}
198
199int builtin_unset(char** args) {
200    for (int i = 1; args[i]; i++) {
201        unsetenv(args[i]);
202    }
203    return 0;
204}
205
206int builtin_help(void) {
207    printf("\n");
208    printf("╔═══════════════════════════════════════╗\n");
209    printf("β•‘        Mini Shell 도움말              β•‘\n");
210    printf("╠═══════════════════════════════════════╣\n");
211    printf("β•‘ λ‚΄μž₯ λͺ…λ Ήμ–΄:                          β•‘\n");
212    printf("β•‘   cd [dir]    디렉토리 λ³€κ²½           β•‘\n");
213    printf("β•‘   pwd         ν˜„μž¬ 디렉토리           β•‘\n");
214    printf("β•‘   echo [...]  ν…μŠ€νŠΈ 좜λ ₯             β•‘\n");
215    printf("β•‘   export V=X  ν™˜κ²½λ³€μˆ˜ μ„€μ •           β•‘\n");
216    printf("β•‘   unset VAR   ν™˜κ²½λ³€μˆ˜ μ‚­μ œ           β•‘\n");
217    printf("β•‘   help        이 도움말               β•‘\n");
218    printf("β•‘   exit [N]    μ‰˜ μ’…λ£Œ                 β•‘\n");
219    printf("╠═══════════════════════════════════════╣\n");
220    printf("β•‘ λ¦¬λ‹€μ΄λ ‰μ…˜:                           β•‘\n");
221    printf("β•‘   cmd > file  좜λ ₯을 파일둜           β•‘\n");
222    printf("β•‘   cmd >> file 좜λ ₯을 νŒŒμΌμ— μΆ”κ°€      β•‘\n");
223    printf("β•‘   cmd < file  νŒŒμΌμ—μ„œ μž…λ ₯           β•‘\n");
224    printf("╠═══════════════════════════════════════╣\n");
225    printf("β•‘ νŒŒμ΄ν”„:                               β•‘\n");
226    printf("β•‘   cmd1 | cmd2 좜λ ₯을 λ‹€μŒ λͺ…λ Ή μž…λ ₯으둜 β•‘\n");
227    printf("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n");
228    printf("\n");
229    return 0;
230}
231
232// λ‚΄μž₯ λͺ…λ Ήμ–΄ μ‹€ν–‰ (-1: λ‚΄μž₯ μ•„λ‹˜)
233int run_builtin(char** args) {
234    if (!args[0]) return -1;
235
236    if (strcmp(args[0], "cd") == 0) return builtin_cd(args);
237    if (strcmp(args[0], "pwd") == 0) return builtin_pwd();
238    if (strcmp(args[0], "echo") == 0) return builtin_echo(args);
239    if (strcmp(args[0], "export") == 0) return builtin_export(args);
240    if (strcmp(args[0], "unset") == 0) return builtin_unset(args);
241    if (strcmp(args[0], "help") == 0) return builtin_help();
242
243    return -1;
244}
245
246// ============ νŒŒμ΄ν”„ μ‹€ν–‰ ============
247
248int split_pipe(char** args, char*** cmds) {
249    int n = 0;
250    cmds[n++] = args;
251
252    for (int i = 0; args[i]; i++) {
253        if (strcmp(args[i], "|") == 0) {
254            args[i] = NULL;
255            if (args[i + 1]) {
256                cmds[n++] = &args[i + 1];
257            }
258        }
259    }
260    return n;
261}
262
263void run_pipeline(char** args) {
264    char** cmds[MAX_PIPES + 1];
265    int n = split_pipe(args, cmds);
266
267    // νŒŒμ΄ν”„ μ—†μœΌλ©΄ 단일 λͺ…λ Ή μ‹€ν–‰
268    if (n == 1) {
269        Redirect r;
270        parse_redirect(cmds[0], &r);
271
272        if (!cmds[0][0]) return;
273
274        // λ‚΄μž₯ λͺ…λ Ήμ–΄ 체크
275        int builtin_result = run_builtin(cmds[0]);
276        if (builtin_result != -1) {
277            last_exit_status = builtin_result;
278            return;
279        }
280
281        // μ™ΈλΆ€ λͺ…λ Ήμ–΄
282        pid_t pid = fork();
283        if (pid == 0) {
284            setup_redirect(&r);
285            execvp(cmds[0][0], cmds[0]);
286            fprintf(stderr, "%s: command not found\n", cmds[0][0]);
287            exit(127);
288        } else if (pid > 0) {
289            int status;
290            waitpid(pid, &status, 0);
291            last_exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : 1;
292        }
293        return;
294    }
295
296    // νŒŒμ΄ν”„κ°€ μžˆλŠ” 경우
297    int pipes[MAX_PIPES][2];
298    for (int i = 0; i < n - 1; i++) {
299        pipe(pipes[i]);
300    }
301
302    for (int i = 0; i < n; i++) {
303        pid_t pid = fork();
304
305        if (pid == 0) {
306            // μž…λ ₯ μ—°κ²°
307            if (i > 0) {
308                dup2(pipes[i-1][0], STDIN_FILENO);
309            }
310            // 좜λ ₯ μ—°κ²°
311            if (i < n - 1) {
312                dup2(pipes[i][1], STDOUT_FILENO);
313            }
314
315            // λͺ¨λ“  νŒŒμ΄ν”„ λ‹«κΈ°
316            for (int j = 0; j < n - 1; j++) {
317                close(pipes[j][0]);
318                close(pipes[j][1]);
319            }
320
321            // λ¦¬λ‹€μ΄λ ‰μ…˜ 처리 (첫/λ§ˆμ§€λ§‰ λͺ…λ Ήμ—λ§Œ 적용)
322            Redirect r;
323            parse_redirect(cmds[i], &r);
324            if (i == 0 && r.infile) {
325                int fd = open(r.infile, O_RDONLY);
326                if (fd >= 0) { dup2(fd, STDIN_FILENO); close(fd); }
327            }
328            if (i == n - 1 && r.outfile) {
329                int flags = O_WRONLY | O_CREAT | (r.append ? O_APPEND : O_TRUNC);
330                int fd = open(r.outfile, flags, 0644);
331                if (fd >= 0) { dup2(fd, STDOUT_FILENO); close(fd); }
332            }
333
334            execvp(cmds[i][0], cmds[i]);
335            fprintf(stderr, "%s: command not found\n", cmds[i][0]);
336            exit(127);
337        }
338    }
339
340    // λΆ€λͺ¨: νŒŒμ΄ν”„ λ‹«κ³  λŒ€κΈ°
341    for (int i = 0; i < n - 1; i++) {
342        close(pipes[i][0]);
343        close(pipes[i][1]);
344    }
345
346    int status;
347    for (int i = 0; i < n; i++) {
348        wait(&status);
349    }
350    last_exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : 1;
351}
352
353// ============ ν”„λ‘¬ν”„νŠΈ ============
354
355void print_prompt(void) {
356    char cwd[256];
357    char* dir = getcwd(cwd, sizeof(cwd));
358
359    // ν™ˆ 디렉토리λ₯Ό ~둜 ν‘œμ‹œ
360    char* home = getenv("HOME");
361    if (home && dir && strncmp(dir, home, strlen(home)) == 0) {
362        printf("\033[1;34m~%s\033[0m", dir + strlen(home));
363    } else {
364        printf("\033[1;34m%s\033[0m", dir ? dir : "?");
365    }
366
367    // μ’…λ£Œ μ½”λ“œμ— 따라 색상 λ³€κ²½
368    if (last_exit_status == 0) {
369        printf(" \033[1;32m❯\033[0m ");
370    } else {
371        printf(" \033[1;31m❯\033[0m ");
372    }
373
374    fflush(stdout);
375}
376
377// ============ 메인 ============
378
379int main(void) {
380    char input[MAX_INPUT];
381    char* args[MAX_ARGS];
382
383    // μ‹œκ·Έλ„ ν•Έλ“€λŸ¬ μ„€μ •
384    signal(SIGINT, sigint_handler);
385
386    printf("\n\033[1;36m=== Mini Shell ===\033[0m\n");
387    printf("'help' μž…λ ₯ν•˜μ—¬ 도움말 보기\n\n");
388
389    while (1) {
390        print_prompt();
391
392        if (fgets(input, sizeof(input), stdin) == NULL) {
393            printf("\nexit\n");
394            break;
395        }
396
397        char* trimmed = trim(input);
398        if (*trimmed == '\0') continue;
399
400        // 주석 λ¬΄μ‹œ
401        if (trimmed[0] == '#') continue;
402
403        // μž…λ ₯ 볡사 (strtok이 원본 μˆ˜μ •)
404        char input_copy[MAX_INPUT];
405        strncpy(input_copy, trimmed, sizeof(input_copy));
406
407        // νŒŒμ‹±
408        int argc = parse_args(input_copy, args);
409        if (argc == 0) continue;
410
411        // exit λͺ…λ Ήμ–΄
412        if (strcmp(args[0], "exit") == 0) {
413            int code = args[1] ? atoi(args[1]) : last_exit_status;
414            printf("exit\n");
415            exit(code);
416        }
417
418        // μ‹€ν–‰
419        run_pipeline(args);
420    }
421
422    return last_exit_status;
423}