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
相关产品推荐
相关产品推荐

