You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言简易Shell程序如何仅打印文件管道输入而非手动输入命令

简易Shell管道输入时命令不打印的解决方法

我用C语言写了一个简易Shell程序,支持两种输入方式:

  • 命令行手动输入
  • 通过< commands.txt管道读取文件输入(项目中用make run < commands.txt运行)

手动输入时程序运行正常,但通过管道输入文件内容时,输入的命令不会被打印,只显示命令提示符和命令输出。如果自行打印输入的命令,手动输入时又会重复显示。需要实现仅在管道输入时打印命令,手动输入时不重复打印的效果。

注:之前遇到过输出乱序问题,已经通过setvbuf(stdout, NULL, _IONBF, 0)禁用标准输出缓冲解决,不确定该设置是否适用于当前场景。

原代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX 102

int main (int argc, char* argv[]) {

    // 设置命令提示符
    char* prompt;
    if (argc > 1) {
        prompt = argv[1];
    }
    else {
        prompt = "> ";
    }

    char commandString[MAX];
    char exitString[MAX] = "exit";
    setvbuf(stdout, NULL, _IONBF, 0);
    
    // 主循环:持续接收命令并执行
    while (1){
        printf("%s", prompt);

        // 清空命令字符串缓冲区
        for (int i = 0; i < MAX; i++){
            commandString[i] = '\0';
        }

        // 逐字符读取输入,直到换行或EOF
        char c;
        int counter = 0;
        while ((c = getchar()) != '\n' && c != EOF){
            if (counter < MAX){
                commandString[counter] = c;
            }
            counter++;
        }
        if (c == EOF){
            printf("EOF found, exiting\n");
            break;
        }

        // 输入exit则退出
        if (strcmp(commandString, exitString) == 0) break;

        // 拆分命令为参数数组
        char* commandToken = strtok(commandString, " ");
        int argsSize = 6;
        char* commandArgs[argsSize];
        int argsCount = 0;
        while(commandToken != NULL && argsCount < argsSize - 1){
            commandArgs[argsCount] = commandToken;
            argsCount++;
            commandToken = strtok(NULL, " ");
        }
        commandArgs[argsCount] = NULL;

        // 执行命令:fork-exec-wait流程
        if (commandArgs[0] != NULL){
            pid_t result = fork();
            if (result < 0){
                printf("Error in fork");
                break;
            }
            // 子进程执行命令
            else if (result == 0) {
                execvp(commandArgs[0], commandArgs);
                printf("%s: command not found\n", commandArgs[0]);
                return 2;
            }
            // 父进程等待子进程结束
            else {
                int status;
                waitpid(result, &status, 0);
                if ( WIFEXITED(status) ) {
                    int exitStatus = WEXITSTATUS(status);
                    printf("Child %i exited with status %i\n", result, exitStatus);
                }
            }
        }
    }
    return 0;
}

解决方法

核心原理

利用Unix/Linux系统的isatty()函数判断标准输入(stdin)是否连接到终端:

  • 手动输入时,stdin是终端,isatty(STDIN_FILENO)返回非0值
  • 管道/文件输入时,stdin不是终端,isatty(STDIN_FILENO)返回0

基于这个判断,决定是否打印输入的命令。

修改步骤

在读取完命令、排除EOF和exit的逻辑之后,添加以下判断代码:

// 仅当stdin不是终端(管道/文件输入)时,打印命令内容
if (!isatty(STDIN_FILENO)) {
    printf("%s\n", commandString);
}

修改后的完整代码

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX 102

int main (int argc, char* argv[]) {

    // 设置命令提示符
    char* prompt;
    if (argc > 1) {
        prompt = argv[1];
    }
    else {
        prompt = "> ";
    }

    char commandString[MAX];
    char exitString[MAX] = "exit";
    setvbuf(stdout, NULL, _IONBF, 0);
    
    // 主循环:持续接收命令并执行
    while (1){
        printf("%s", prompt);

        // 清空命令字符串缓冲区
        for (int i = 0; i < MAX; i++){
            commandString[i] = '\0';
        }

        // 逐字符读取输入,直到换行或EOF
        char c;
        int counter = 0;
        while ((c = getchar()) != '\n' && c != EOF){
            if (counter < MAX){
                commandString[counter] = c;
            }
            counter++;
        }
        if (c == EOF){
            printf("EOF found, exiting\n");
            break;
        }

        // 输入exit则退出
        if (strcmp(commandString, exitString) == 0) break;

        // 仅当stdin不是终端时打印命令
        if (!isatty(STDIN_FILENO)) {
            printf("%s\n", commandString);
        }

        // 拆分命令为参数数组
        char* commandToken = strtok(commandString, " ");
        int argsSize = 6;
        char* commandArgs[argsSize];
        int argsCount = 0;
        while(commandToken != NULL && argsCount < argsSize - 1){
            commandArgs[argsCount] = commandToken;
            argsCount++;
            commandToken = strtok(NULL, " ");
        }
        commandArgs[argsCount] = NULL;

        // 执行命令:fork-exec-wait流程
        if (commandArgs[0] != NULL){
            pid_t result = fork();
            if (result < 0){
                printf("Error in fork");
                break;
            }
            // 子进程执行命令
            else if (result == 0) {
                execvp(commandArgs[0], commandArgs);
                printf("%s: command not found\n", commandArgs[0]);
                return 2;
            }
            // 父进程等待子进程结束
            else {
                int status;
                waitpid(result, &status, 0);
                if ( WIFEXITED(status) ) {
                    int exitStatus = WEXITSTATUS(status);
                    printf("Child %i exited with status %i\n", result, exitStatus);
                }
            }
        }
    }
    return 0;
}

说明

  • isatty(STDIN_FILENO)函数定义在<unistd.h>中,你的代码已经包含该头文件,无需额外添加。
  • 之前设置的setvbuf(stdout, NULL, _IONBF, 0)禁用了标准输出缓冲,能避免打印命令和后续输出乱序,完全适用于当前场景。

内容的提问来源于stack exchange,提问作者fifthfiend

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 06:47:47