簡體   English   中英

C execvp 執行 sed 命令后不返回?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM