C语言实现一个迷你Shell

简介

俄勒冈州立大学(Oregon State University) CS 344作业,使用C语言实现一个Shell.

要求

  • 内置 cd,statusexit 命令
  • 支持输入和输出的重定向
  • 支持后台运行(&)
  • Ctrl-Z 与 Ctrl-C
  • 调用系统其他命令
  • 替换命令中的$$为Shell的PID

参考资料

作业pdf

测试脚本

p3testscriptview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/bin/bash

echo "PRE-SCRIPT INFO"
echo " Grading Script PID: $$"
echo ' Note: your smallsh will report a different PID when evaluating $$'

./smallsh <<'___EOF___'
echo BEGINNING TEST SCRIPT
echo
echo --------------------
echo Using comment (5 points if only next prompt is displayed next)
#THIS COMMENT SHOULD DO NOTHING
echo
echo
echo --------------------
echo ls (10 points for returning dir contents)
ls
echo
echo
echo --------------------
echo ls out junk
ls > junk
echo
echo
echo --------------------
echo cat junk (15 points for correctly returning contents of junk)
cat junk
echo
echo
echo --------------------
echo wc in junk (15 points for returning correct numbers from wc)
wc < junk
echo
echo
echo --------------------
echo wc in junk out junk2; cat junk2 (10 points for returning correct numbers from wc)
wc < junk > junk2
cat junk2
echo
echo
echo --------------------
echo test -f badfile (10 points for returning error value of 1, note extraneous &)
test -f badfile
status &
echo
echo
echo --------------------
echo wc in badfile (10 points for returning text error)
wc < badfile
echo
echo
echo --------------------
echo badfile (10 points for returning text error)
badfile
echo
echo
echo --------------------
echo sleep 100 background (10 points for returning process ID of sleeper)
sleep 100 &
echo
echo
echo --------------------
echo pkill -signal SIGTERM sleep (10 points for pid of killed process, 10 points for signal)
echo (Ignore message about Operation Not Permitted)
pkill sleep
echo
echo
echo --------------------
echo sleep 1 background (10 pts for pid of bg ps when done, 10 for exit value)
sleep 1 &
sleep 1
echo
echo
echo --------------------
echo pwd
pwd
echo
echo
echo --------------------
echo cd
cd
echo
echo
echo --------------------
echo pwd (10 points for being in the HOME dir)
pwd
echo
echo
echo --------------------
echo mkdir testdir$$
mkdir testdir$$
echo
echo
echo --------------------
echo cd testdir$$
cd testdir$$
echo
echo
echo --------------------
echo pwd (5 points for being in the newly created dir)
pwd
echo --------------------
echo Testing foreground-only mode (20 points for entry & exit text AND ~5 seconds between times)
kill -SIGTSTP $$
date
sleep 5 &
date
kill -SIGTSTP $$
exit
___EOF___

源码

smallsh.cview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>

#define MAX_INPUT_LENGTH 2048
#define MAX_ARG_NUMBER 512
#define SPLIT_CHAR " \t\r\n"

int allowBg = 1;

char *readLine()
{
size_t maxLength = MAX_INPUT_LENGTH;
char *line = (char *)malloc(sizeof(char) * maxLength);
fgets(line, maxLength, stdin);
int i = 0;
for (i = 0; i < maxLength; i++)
{
if (line[i] == '\n')
{
line[i] = '\0';
}
}
return line;
}
char **parse(char *line, int *num, int *back)
{
size_t maxNumber = MAX_ARG_NUMBER;
int i = 0;
char **arr = (char **)malloc(sizeof(char *) * maxNumber);
char *token;
token = strtok(line, SPLIT_CHAR);
while (token != NULL)
{
arr[i] = token;
// replace $$
int j = 0;
for (j = 0; j < strlen(arr[i])-1; j++)
{
if (arr[i][j] == '$' && arr[i][j + 1] == '$')
{
arr[i][j] = '\0';
arr[i][j+1] = '\0';
char t[10];
char newArg[50];
memset(t, '\0', 10);
memset(newArg, '\0', 50);
strncpy(newArg, arr[i], j);
sprintf(t, "%d", getpid());
strcat(newArg, t);
// sprintf(arr[i], newArg);
arr[i] = newArg;
}
}
i++;
token = strtok(NULL, SPLIT_CHAR);
}
if (i >= 2 && strcmp(arr[i - 1], "&") == 0)
{
arr[i - 1] = NULL;
i--;
if(allowBg == 1){
*back = 1;
}
}
*num = i;
arr[i] = NULL;
return arr;
}
void printStatus(int status)
{
if (status == 0 || status == 1)
printf("exit value %d\n", status);
else
printf("terminated by signal %d\n", status);
}
void catchSIGTSTP(int sig)
{
if (allowBg == 1)
{
char *message = "Entering foreground-only mode(& is now ignored)\n";
write(STDOUT_FILENO, message, 49);
fflush(stdout);
allowBg = 0;
}
else
{
char *message = "Exiting foreground-only mode\n";
write(STDOUT_FILENO, message, 30);
fflush(stdout);
allowBg = 1;
}
}
void catchSIGINT(int signo)
{
char message[] = "terminated by signal ";
char t[10];
memset(t, '\0', 10);
sprintf(t, "%d", signo);
strcat(message, t);
strcat(message, "\n");
printf("%s", message);
fflush(stdout);
fflush(stdin);
}

int newProcess(char **arr, int num, int back)
{
pid_t pid = fork();
int status, fd;
int input_flag = -1;
int output_flag = -1;
int i = 0;
for (i = 0; i < num; i++)
{
if (strcmp(arr[i], "<") == 0)
input_flag = i;
else if (strcmp(arr[i], ">") == 0)
output_flag = i;
}
if (pid == 0)
{
struct sigaction sigint = {0};
sigint.sa_handler = catchSIGINT;
sigemptyset(&sigint.sa_mask);
sigint.sa_flags = 0;
sigaction(SIGINT, &sigint, NULL);

// input redirect
if (input_flag > -1)
{
fd = open(arr[input_flag + 1], O_RDONLY, 0644);
if (fd == -1)
{
printf("cannot open %s for input\n", arr[input_flag + 1]);
exit(1);
}
else
{
if (dup2(fd, 0) == -1)
{
printf(": Could not redirect stdin for input file %s\n", arr[input_flag + 1]);
exit(1);
}
arr[input_flag] = NULL;
close(fd);
}
}
//output redirect
if (output_flag > -1)
{
fd = open(arr[output_flag + 1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
{
printf("cannot not open %s for output\n", arr[output_flag + 1]);
exit(1);
}
else
{
if (dup2(fd, 1) == -1)
{
printf("Could not redirect stdout for output file %s\n", arr[output_flag + 1]);
exit(1);
}
arr[output_flag] = NULL;
close(fd);
}
}
if (back == 1 && input_flag < 0)
{
fd = open("/dev/null", O_RDONLY, 0644);
if (fd == -1)
{
printf("Could not open \"/dev/null\"\n");
exit(1);
}
else
{
if (dup2(fd, 0) == -1)
{
printf("Could not redirect stdin to \"/dev/null\"\n");
exit(1);
}
close(fd);
}
}
// printf("%c\n", arr[0][0]);
if (execvp(arr[0], arr) == -1 && arr[0][0] >= 'a' && arr[0][0] <= 'z')
{
printf("%s: no such file or directory\n", arr[0]);
fflush(stdout);
exit(1);
}
}
else if (pid < 0)
{
printf("Smallsh !\n");
return 1;
}
else
{ // parent
if (back == 1)
{
printf("Background pid is %d.\n", pid);
fflush(stdout);
}
else
{
waitpid(pid, &status, WUNTRACED);
if (WIFEXITED(status))
return (WEXITSTATUS(status));
else if (WIFSIGNALED(status))
return (WTERMSIG(status));
}
}
return status;
}
int executeCommand(char **arr, int status, int num, int back)
{
if (arr[0] == NULL || strcmp("#", arr[0]) == 0)
return status;
else if (strcmp(arr[0], "exit") == 0)
exit(0);
else if (strcmp(arr[0], "status") == 0)
printStatus(status);
else if (strcmp(arr[0], "cd") == 0)
{
char *directory;
if (arr[1] == NULL)
directory = getenv("HOME");
else
directory = arr[1];
if (chdir(directory) == -1)
{
printf("cd: No such file or directory\n");
fflush(stdout);
}
return status;
}
else
{
int t = newProcess(arr, num, back);
// printf("------------%d---\n",t);
// fflush(stdout);
return t;
}
}


int main()
{
char *input;
char **arr;
int status = 0;

struct sigaction sigint = {0};
sigint.sa_handler = SIG_IGN;
sigemptyset(&sigint.sa_mask);
sigint.sa_flags = 0;
sigaction(SIGINT, &sigint, NULL);

struct sigaction sigtstp = {0};
sigtstp.sa_handler = catchSIGTSTP;
sigfillset(&sigtstp.sa_mask);
sigtstp.sa_flags = 0;
sigaction(SIGTSTP, &sigtstp, NULL);
while (1)
{
int num = 0;
int back = 0;
printf(": ");
fflush(stdout);
input = readLine();
arr = parse(input, &num, &back);
status = executeCommand(arr, status, num, back);

//background
pid_t childPid = waitpid(-1, &status, WNOHANG);
if (childPid > 0)
{
printf("background pid %d is done: ", childPid);
if (WIFEXITED(status))
printf("exit value %d\n", WEXITSTATUS(status));
else
printf("terminated by signal %d\n", status);
fflush(stdout);
}
fflush(stdin);
free(input);
free(arr);
}
return 0;
}

总结

wait()函数

1
2
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);
  • WNOHANG 如果该子进程没有结束,则以非阻塞方式等待子进程,并且返回0;如果该子进程已经结束,返回其pid.如果没有子进程退出,则立刻返回-1
  • WUNTRACED 若子进程进入暂停状态,则马上返回

WIFEXTED与WEXITSTATUS

  • WIFEXTED(status) 这个宏用来获取是否正常退出,正常退出获得true
  • WEXITSTATUS(status) 只可在WIFEXITED为true时使用,获取正常退出的状态码

WIFSIGNALED与WTERMSIG

  • WIFSIGNALED(status) 这个宏用来获取是否异常退出,异常退出获得true
  • WTERMSIG(status) 只可在WIFSIGNALED为true时使用,获取异常退出的状态码
    smallsh.cview raw
    1
    2
    3
    4
    5
    waitpid(pid, &status, WUNTRACED);
    if (WIFEXITED(status))
    return (WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
    return (WTERMSIG(status));
您的支持是我继续创作最大的动力!

欢迎关注我的其它发布渠道