C 标准输入因文件重定向而损坏?
C stdin corrupted from file redirection?
我正在做一个作业,其中包括编写一个使用 fork 和 execvp
执行其他程序的迷你 shell。当一切似乎都正常时,我尝试使用 bash 上的 ./myshell < file.txt
直接将文件中的命令列表传递到我的 shell,并得到了一个无限*的命令流,这永远不会完成执行。
我不确定是什么原因造成的,所以我使用 gdb
进行了调试。令我惊讶的是,当到达文件的假定最后一行时,一个额外的字符被添加到从 stdin 读取的行中。此外,之前执行的更多行将 return 到提要并重新执行。这是提供给 shell:
的文件
-trimmed for compactness-
chdir file
chdir ../
touch hello.txt index.html app.py
ls
rm hello.txt
ls -1
wc app.py
cat index.html
pwd
history
touch .hidden.txt
ls -a
history
echo preparing to exit
cd ../../
ip route show
ls -R
rm -rf path
invalid command
history
注意在 invalid command
之后是 history
和 /
然后下一行是 touch hello.txt index.html app.py
等等。我尝试了多种方法来调试这个问题,将我的 readLine 函数放入一个单独的文件并单独测试它,但程序在读取最后一行后正确终止。我还在 MacOS 上编译了我的 shell ,令我惊讶的是,这个问题也没有发生。作为参考,该错误发生在系统 运行 Ubuntu 18.04.3 LTS.
我完全不明白为什么会这样。我假设这可能与我的 stdin 由其自身的分叉副本编写有关,但我真的不确定。一些见解将不胜感激。
*不确定它是否真的是无限的
编辑 1:这是我的部分代码(抱歉,我无法在不解决问题的情况下进一步减小它的大小,因为我不知道是什么原因造成的)
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <signal.h>
#include <fcntl.h>
char* get_a_line();
char * extract_arguments(char** line, char delimiter);
int my_system(char* line);
// buffer size to read stdin
size_t BUFFER_SIZE = 256;
// string node for tailq
struct string_node {
char* value;
TAILQ_ENTRY(string_node) entries;
};
// macro to init node struct for string
TAILQ_HEAD(str_list, string_node);
struct str_list history;
int main(int argc, char** argv) {
int user_input = isatty(0);
while (1) {
if (user_input) {
printf("$ ");
}
char* line = get_a_line();
if (feof(stdin)) {
exit(0);
}
if (strlen(line) > 0) {
my_system(line);
}
// won't free since line is used in the queue to display `history`
// free(line);
}
return 0;
}
char* get_a_line() {
char * buffer = (char*)malloc(BUFFER_SIZE * sizeof(char));
size_t len = getline(&buffer, &BUFFER_SIZE, stdin);
// transform `\n' to '[=11=]' to terminate string
if (len != -1 && buffer[len-1] == '\n') {
buffer[len-1] = '[=11=]';
}
return buffer;
}
int parse(char** line, char*** parsed) {
// init string list to contain arguments
struct str_list strings_list;
TAILQ_INIT(&strings_list);
struct string_node *tmp_node;
// number of argument parts
int count = 0 ;
char * s;
while((s = extract_arguments(line, ' ')) != NULL) {
tmp_node = malloc(sizeof(struct string_node));
tmp_node->value = s;
TAILQ_INSERT_TAIL(&strings_list, tmp_node, entries);
count++;
}
// save arguments into array of strings( array of char array)
char ** arguments = malloc (sizeof(char**) * (count+1));
int i=0;
while ((tmp_node = TAILQ_FIRST(&strings_list))) {
arguments[i++] = tmp_node->value;
TAILQ_REMOVE(&strings_list, tmp_node, entries);
free(tmp_node);
}
// terminate array
arguments[count] = NULL;
// check the type of termination
*parsed = arguments;
if (**line == '|') {
(*line) += 1;
return 1;
}
return 0;
}
// extract string from the start of the *line until length by allocating through malloc
char * extract_string(char ** line, int length) {
char * str = NULL;
if (length > 0) {
str = malloc((length+1) * sizeof(char));
strncpy(str, *line, length);
str[length] = '[=11=]';
*line += (length);
}
return str;
}
/*
Merges two string str1 and str2 by calloc on str1 and freeing str2
(prob should not free str2 in this but w/e)
*/
char * strcat_alloc(char * str1, char * str2) {
if (str1 == NULL) {
return str2;
}
if (str2 == NULL) {
return str1;
}
size_t length1 = strlen(str1) ;
size_t length2 = strlen(str2);
str1 = realloc(str1, length1 + length2+1);
strcpy(str1+length1, str2);
str1[length1+length2] = '[=11=]';
free(str2);
return str1;
}
/*
Extract a single argument of the line, terminated by the delimiter
Basic quotes and escaping implemented in order to support multiword arguments
*/
char * extract_arguments(char** line, char delimiter) {
// remove trailing spaces
while (**line == ' ') {
(*line)+=1;
}
int right = 0;
char * str_p = NULL;
while ((*line)[right] != delimiter &&
(*line)[right] != EOF &&
(*line)[right] != '[=11=]' &&
(*line)[right] != '|')
{
if ((*line)[right] == '\'){
str_p = extract_string(line, right);
// the escaped character is one after '\'
*line+=1;
char *c = malloc(sizeof(char));
*c = **line;
*line +=1;
return strcat_alloc(strcat_alloc(str_p, c), extract_arguments(line, delimiter));
}
if ((*line)[right] == '\''){
str_p = extract_string(line, right);
*line+=1;
char * str_p2 = extract_arguments(line, '\'');
return strcat_alloc(strcat_alloc(str_p, str_p2), extract_arguments(line, ' '));
} else if ((*line)[right] == '\"') {
str_p = extract_string(line, right);
*line+=1;
char * str_p2 = extract_arguments(line, '\"');
return strcat_alloc(strcat_alloc(str_p, str_p2), extract_arguments(line, ' '));
}
right++;
}
str_p = extract_string(line, right);
if (**line == delimiter) {
*line+=1;
}
return str_p;
}
/*
Execute command defined by **args dending on the flag (pipe or normal execution)
*/
int execute(char **args, int flag, int * wait_count) {
pid_t pid = fork();
if (pid == 0) {
// exit to prevent multiple instance of shell
exit(0);
} else {
// PARENT
wait(NULL);
}
return 0;
}
int my_system(char* line) {
char** args;
int flag = 0;
// wait count keeps tracks of the amount of fork to wait for
// max 2 in this case
int wait_count= 0;
while(*line != '[=11=]') {
flag = parse(&line, &args);
if (*args == NULL) {
return 0;
}
// exit can't be in fork nor chdir
if (strcasecmp(args[0], "exit") == 0) {
exit(0);
} else if (strcasecmp(args[0], "chdir") == 0 || strcasecmp(args[0], "cd") == 0) {
if(chdir(args[1]) < 0) {
printf("chdir: change directory failed\n");
}
return 0;
}
execute(args, flag, &wait_count);
}
return 0;
}
一个朋友帮我弄明白了。问题源于在子分支中使用 exit
。显然, stdin
以某种方式被刷新了两次,并在稍后被父级读取时损坏了。要修复它,只需将其更改为 _exit
。
我正在做一个作业,其中包括编写一个使用 fork 和 execvp
执行其他程序的迷你 shell。当一切似乎都正常时,我尝试使用 bash 上的 ./myshell < file.txt
直接将文件中的命令列表传递到我的 shell,并得到了一个无限*的命令流,这永远不会完成执行。
我不确定是什么原因造成的,所以我使用 gdb
进行了调试。令我惊讶的是,当到达文件的假定最后一行时,一个额外的字符被添加到从 stdin 读取的行中。此外,之前执行的更多行将 return 到提要并重新执行。这是提供给 shell:
-trimmed for compactness-
chdir file
chdir ../
touch hello.txt index.html app.py
ls
rm hello.txt
ls -1
wc app.py
cat index.html
pwd
history
touch .hidden.txt
ls -a
history
echo preparing to exit
cd ../../
ip route show
ls -R
rm -rf path
invalid command
history
注意在 invalid command
之后是 history
和 /
然后下一行是 touch hello.txt index.html app.py
等等。我尝试了多种方法来调试这个问题,将我的 readLine 函数放入一个单独的文件并单独测试它,但程序在读取最后一行后正确终止。我还在 MacOS 上编译了我的 shell ,令我惊讶的是,这个问题也没有发生。作为参考,该错误发生在系统 运行 Ubuntu 18.04.3 LTS.
我完全不明白为什么会这样。我假设这可能与我的 stdin 由其自身的分叉副本编写有关,但我真的不确定。一些见解将不胜感激。
*不确定它是否真的是无限的
编辑 1:这是我的部分代码(抱歉,我无法在不解决问题的情况下进一步减小它的大小,因为我不知道是什么原因造成的)
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <signal.h>
#include <fcntl.h>
char* get_a_line();
char * extract_arguments(char** line, char delimiter);
int my_system(char* line);
// buffer size to read stdin
size_t BUFFER_SIZE = 256;
// string node for tailq
struct string_node {
char* value;
TAILQ_ENTRY(string_node) entries;
};
// macro to init node struct for string
TAILQ_HEAD(str_list, string_node);
struct str_list history;
int main(int argc, char** argv) {
int user_input = isatty(0);
while (1) {
if (user_input) {
printf("$ ");
}
char* line = get_a_line();
if (feof(stdin)) {
exit(0);
}
if (strlen(line) > 0) {
my_system(line);
}
// won't free since line is used in the queue to display `history`
// free(line);
}
return 0;
}
char* get_a_line() {
char * buffer = (char*)malloc(BUFFER_SIZE * sizeof(char));
size_t len = getline(&buffer, &BUFFER_SIZE, stdin);
// transform `\n' to '[=11=]' to terminate string
if (len != -1 && buffer[len-1] == '\n') {
buffer[len-1] = '[=11=]';
}
return buffer;
}
int parse(char** line, char*** parsed) {
// init string list to contain arguments
struct str_list strings_list;
TAILQ_INIT(&strings_list);
struct string_node *tmp_node;
// number of argument parts
int count = 0 ;
char * s;
while((s = extract_arguments(line, ' ')) != NULL) {
tmp_node = malloc(sizeof(struct string_node));
tmp_node->value = s;
TAILQ_INSERT_TAIL(&strings_list, tmp_node, entries);
count++;
}
// save arguments into array of strings( array of char array)
char ** arguments = malloc (sizeof(char**) * (count+1));
int i=0;
while ((tmp_node = TAILQ_FIRST(&strings_list))) {
arguments[i++] = tmp_node->value;
TAILQ_REMOVE(&strings_list, tmp_node, entries);
free(tmp_node);
}
// terminate array
arguments[count] = NULL;
// check the type of termination
*parsed = arguments;
if (**line == '|') {
(*line) += 1;
return 1;
}
return 0;
}
// extract string from the start of the *line until length by allocating through malloc
char * extract_string(char ** line, int length) {
char * str = NULL;
if (length > 0) {
str = malloc((length+1) * sizeof(char));
strncpy(str, *line, length);
str[length] = '[=11=]';
*line += (length);
}
return str;
}
/*
Merges two string str1 and str2 by calloc on str1 and freeing str2
(prob should not free str2 in this but w/e)
*/
char * strcat_alloc(char * str1, char * str2) {
if (str1 == NULL) {
return str2;
}
if (str2 == NULL) {
return str1;
}
size_t length1 = strlen(str1) ;
size_t length2 = strlen(str2);
str1 = realloc(str1, length1 + length2+1);
strcpy(str1+length1, str2);
str1[length1+length2] = '[=11=]';
free(str2);
return str1;
}
/*
Extract a single argument of the line, terminated by the delimiter
Basic quotes and escaping implemented in order to support multiword arguments
*/
char * extract_arguments(char** line, char delimiter) {
// remove trailing spaces
while (**line == ' ') {
(*line)+=1;
}
int right = 0;
char * str_p = NULL;
while ((*line)[right] != delimiter &&
(*line)[right] != EOF &&
(*line)[right] != '[=11=]' &&
(*line)[right] != '|')
{
if ((*line)[right] == '\'){
str_p = extract_string(line, right);
// the escaped character is one after '\'
*line+=1;
char *c = malloc(sizeof(char));
*c = **line;
*line +=1;
return strcat_alloc(strcat_alloc(str_p, c), extract_arguments(line, delimiter));
}
if ((*line)[right] == '\''){
str_p = extract_string(line, right);
*line+=1;
char * str_p2 = extract_arguments(line, '\'');
return strcat_alloc(strcat_alloc(str_p, str_p2), extract_arguments(line, ' '));
} else if ((*line)[right] == '\"') {
str_p = extract_string(line, right);
*line+=1;
char * str_p2 = extract_arguments(line, '\"');
return strcat_alloc(strcat_alloc(str_p, str_p2), extract_arguments(line, ' '));
}
right++;
}
str_p = extract_string(line, right);
if (**line == delimiter) {
*line+=1;
}
return str_p;
}
/*
Execute command defined by **args dending on the flag (pipe or normal execution)
*/
int execute(char **args, int flag, int * wait_count) {
pid_t pid = fork();
if (pid == 0) {
// exit to prevent multiple instance of shell
exit(0);
} else {
// PARENT
wait(NULL);
}
return 0;
}
int my_system(char* line) {
char** args;
int flag = 0;
// wait count keeps tracks of the amount of fork to wait for
// max 2 in this case
int wait_count= 0;
while(*line != '[=11=]') {
flag = parse(&line, &args);
if (*args == NULL) {
return 0;
}
// exit can't be in fork nor chdir
if (strcasecmp(args[0], "exit") == 0) {
exit(0);
} else if (strcasecmp(args[0], "chdir") == 0 || strcasecmp(args[0], "cd") == 0) {
if(chdir(args[1]) < 0) {
printf("chdir: change directory failed\n");
}
return 0;
}
execute(args, flag, &wait_count);
}
return 0;
}
一个朋友帮我弄明白了。问题源于在子分支中使用 exit
。显然, stdin
以某种方式被刷新了两次,并在稍后被父级读取时损坏了。要修复它,只需将其更改为 _exit
。