[英]Why does execvp() failed when executing more than 1 command in C?
[英]C execvp does not return after executing sed command?
我正在學習 C 的過程,所以我決定制作一個簡單的 shell,例如/bin/sh
接受命令或 pipe 的命令然后執行所有命令。
我目前正在測試 pipe 命令:
cat moby.txt | tr AZ az | tr -C az \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];
}
The reason why I'm confused is that in the pipe of commands, the previous sed /^$/d
as expected, so I don't think that my shell program doesn't work with sed
command. 每條評論都值得贊賞。 謝謝!
雖然您已經關閉了 shell 的管道,但您還沒有在分叉過程中關閉管道。
最后一個進程實際上確實退出了,它之前的進程沒有退出。 最后一個進程在讀取它需要的 10 行后退出,讓前面的進程嘗試寫入更多行,但這樣做永遠不會出錯,因為它本身仍然打開 pipe 的讀取端。 所以它只是掛在那里等待讀取它正在寫入的數據的東西。
解決方法是在調用execvp()
之前在每個分叉進程中簡單地close(fd[0])
和close(fd[1])
,以便只有您使用dup()
制作的副本保持打開狀態。
請注意,雖然這確實使您的 shell 成功完成命令,但它仍然不會打印此進程的退出狀態,因為WIFEXITED()
評估為 false。 但是, WIFSIGNALED()
將評估為 true,然后WTERMSIG()
將告訴您它已被 SIGPIPE 殺死。
execvp
是一個系統調用,僅當它無法執行您要求它執行的命令時才返回。 原因是execvp
不會創建新進程(這是fork()
的任務),但它會安裝新的可執行文件來替換實際程序。 現在運行的程序的memory返回系統,並為新程序分配新的新鮮memory。 新程序在execvp
系統調用之前運行的同一進程下運行,因此,當您退出時,您不會返回到舊程序,因為它已丟失。
你需要閱讀一本關於 unix 進程的書。 一本舊書,但解釋得很好,是 Rob Pike 和 Brian Kernighan 的“UNIX 編程環境” 。 unix 的發明者之一的很好的解釋(嗯,兩者都涉及到)並且仍然成立。
當然,一個很好的參考也是
$ man execvp
...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.