我正在學習 C 語言的程序,所以我決定制作一個簡單的 shell,比如/bin/sh接受一個命令或一個命令管道,然后執行所有命令。
我目前正在測驗一系列命令:
cat moby.txt | tr A-Z a-z | tr -C a-z \n | sed /^$/d | sort | uniq -c | sort -nr | sed 10q
我的 shell 程式執行所有這些,但它在sed 10q命令處暫停。如果我沒有執行最后一個命令,shell 將回傳如下內容:
myshell$ cat moby.txt | tr A-Z a-z | tr -C a-z \n | sed /^$/d | sort | uniq -c | sort -nr
Process 3213 exited with status 0
Process 3214 exited with status 0
Process 3215 exited with status 0
Process 3216 exited with status 0
Process 3217 exited with status 0
Process 3218 exited with status 0
...
1 abbreviate
1 abatement
1 abate
1 abasement
1 abandonedly
Process 3068 exited with status 0
myshell$
但是當我也執行最后一個命令時,shell 不會回傳任何內容并暫停:
myshell$ cat moby.txt | tr A-Z a-z | tr -C a-z \n | sed /^$/d | sort | uniq -c | sort -nr | sed 10q
Process 3213 exited with status 0
Process 3214 exited with status 0
Process 3215 exited with status 0
Process 3216 exited with status 0
Process 3217 exited with status 0
Process 3218 exited with status 0
14718 the
6743 of
6518 and
4807 a
4707 to
4242 in
3100 that
2536 it
2532 his
2127 i (it should return the message if a process is successfully completed)
這是代碼:
// main
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "shell.h"
#define BUFFER_SIZE 1024
int main(void)
{
char line_buffer[BUFFER_SIZE];
char *argv[256];
int n = 0;
pid_t pids[30];
while (1)
{
fprintf(stdout, "myshell$");
fflush(NULL);
if (!fgets(line_buffer, BUFFER_SIZE, stdin))
break;
/* File descriptors - 1 is existing, 0 is not existing */
int prev = 0; // previous command
int first = 1; // first command in the pipe
int last = 0; // last command in the pipe
// Separate commands with character '|'
char *cmd = line_buffer;
char *single_cmd = strchr(cmd, '|');
while (single_cmd != NULL)
{
*single_cmd = '\0';
parse(cmd, argv);
if (strcmp(argv[0], "exit") == 0)
exit(0);
execute(&pids[n ], argv, &prev, &first, &last);
cmd = single_cmd 1;
single_cmd = strchr(cmd, '|');
first = 0;
}
parse(cmd, argv);
if (argv[0] != NULL && (strcmp(argv[0], "exit") == 0))
exit(0);
last = 1;
execute(&pids[n ], argv, &prev, &first, &last);
int status;
for (int i = 0; i < n; i )
{
waitpid(pids[i], &status, 0);
if (WIFEXITED(status))
{
int exit_status = WEXITSTATUS(status);
fprintf(stdout, "Process %d exited with status %d\n",
pids[i], exit_status);
}
}
n = 0;
}
return 0;
}
// shell.h
#ifndef _MY_SHELL_H
#define _MY_SHELL_H
#include <sys/types.h>
/**
* @brief Parse function will parse a given line from user input into
* an array that represents the specified command and its arguments
*
* @param line String buffer to parse
* @param argv Array of tokens
*/
void parse(char *line, char **argv);
/**
* @brief Execute function will take the array of tokens and execute them
* using execvp.
*
* @param argv Array of tokens
*/
void execute(pid_t *pid, char **argv, int *prev, int *first, int *last);
#endif // _MY_SHELL_H
// shell.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "shell.h"
void parse(char *line, char **argv)
{
size_t line_length = strlen(line);
// fgets includes '\n' at the end of line
if (line[line_length - 1] && line[line_length - 1] != ' ')
line[line_length - 1] = ' ';
// Parse the line into tokens
char *token = strtok(line, " ");
while (token != NULL)
{
// Store those tokens in the argument list
*argv = token;
// @see http://www.cplusplus.com/reference/cstring/strtok/
token = strtok(NULL, " ");
}
// Bad address error - Indicate the end of the argument list
*argv = (char *)NULL;
}
void execute(pid_t *pid, char **argv, int *prev, int *first, int *last)
{
int fd[2];
pipe(fd);
*pid = fork();
if (*pid < 0)
{
fprintf(stderr, "ERROR: fork failed. Program exited with 1\n");
exit(1);
}
else if (*pid == 0)
{
// First command
if (*first == 1 && *last == 0 && *prev == 0)
{
dup2(fd[1], STDOUT_FILENO);
}
// Middle commands
else if (*first == 0 && *last == 0 && *prev != 0)
{
dup2(*prev, STDIN_FILENO);
dup2(fd[1], STDOUT_FILENO);
}
else
{
dup2(*prev, STDIN_FILENO);
}
int status = execvp(*argv, argv);
if (status < 0)
{
perror("ERROR: process cannot be executed. Program exited with 1");
exit(1);
}
}
if (*prev != 0)
close(*prev);
close(fd[1]);
if (*last == 1)
close(fd[0]);
*prev = fd[0];
}
我感到困惑的原因是,在命令的管道中,前面的sed /^$/d按預期執行,所以我認為我的 shell 程式不能與sed命令一起使用。每條評論都值得贊賞。謝謝!
uj5u.com熱心網友回復:
雖然您已經關閉了 shell 的管道,但您還沒有關閉分叉行程中的管道。
最后一個行程實際上確實退出了,它之前的行程沒有退出。最后一個行程在讀取了它需要的 10 行后退出,讓前面的行程嘗試寫入更多行,但這樣做永遠不會出錯,因為它本身仍然打開了管道的讀取端。所以它只是掛在那里等待讀取它正在寫入的資料的東西。
解決方法是在呼叫之前簡單地在每個分叉行程中close(fd[0]),以便只有您使用的副本保持打開狀態。close(fd[1])execvp()dup()
請注意,雖然這確實使您的 shell 成功完成了命令,但它仍然不會列印此行程的退出狀態,因為WIFEXITED()計算結果為 false。但是,WIFSIGNALED()將評估為 true,然后WTERMSIG()會告訴您它已被 SIGPIPE 殺死。
uj5u.com熱心網友回復:
execvp是一個系統呼叫,僅當它無法執行您要求它執行的命令時才回傳。原因是它execvp不會創建新行程(這是 的任務fork()),但它會安裝新的可執行檔案來替換實際程式。現在正在運行的程式的記憶體回傳給系統,并為新程式分配新的新鮮記憶體。新程式在execvp系統呼叫之前運行的同一行程下運行,因此,當您退出時,您不會回傳到舊程式,因為它丟失了。
您需要閱讀有關 unix 行程的書。Rob Pike 和 Brian Kernighan 的《UNIX 編程環境》是一本舊書,但解釋得很好。來自unix的一位發明者的很好的解釋(好吧,兩者都涉及到)并且仍然成立。
當然,一個很好的參考也是
$ man execvp
...
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/422215.html
標籤:
