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}