Поскольку вы говорите scanf
не читать \n
, он оставляет его в stdin, поэтому, когда l oop повторяется, он все еще там и заставляет следующую итерацию немедленно возвращать пустую строку. Чтобы исправить это, есть несколько вариантов:
- Измените
"%[^\n]"
на " %[^\n]"
, чтобы игнорировать начальные пробелы. - Добавьте
getchar();
после scanf("%[^\n]", command);
, чтобы использовать символ новой строки. - Используйте
fgets
или другую функцию чтения, отличную от scanf
. (Не gets
; его нельзя безопасно использовать!)
Вы также не проверяете, вернул ли scanf
что-либо, поэтому, если он вообще ничего не анализировал, то вы ' передача неинициализированной памяти в popen
.
. Примечание: в вашей программе много других ошибок, но они не имеют отношения к вашей непосредственной проблеме:
- Вы пропускаете память
command
каждый раз через ваш l oop. Чтобы исправить это, добавьте free(command);
где-нибудь после popen
и до конца функции. - Вы не передали максимальную ширину поля для
scanf
, поэтому, если вы вводите более 14 символов , у вас будет переполнение буфера и повреждение памяти. Чтобы исправить это, измените %[^\n]
на %14[^\n]
(14, а не 15, чтобы было место для нулевого терминатора). Вы также должны обнаружить случай частичного чтения и правильно его обработать, чтобы избежать echo Their alarm is set
удаления файлов, например, is
и set
. sizeof(output)
будет размером с указатель, а не выделенная вами память, на которую он указывает, и 32000
, кажется, вышел из воздуха. Измените sizeof(output)
на 1 и 32000
на 4096
. fread
не завершит вывод на нуль, поэтому печать его с помощью %s
напечатает неинициализированную память после нее. Чтобы исправить это, либо используйте что-то отличное от fread
, чтобы получить выходные данные, либо используйте его возвращаемое значение и убедитесь, что вы печатаете только столько символов.
С вашей «фиксированной» версией есть еще несколько проблем:
- Ваш первый
fgets
не будет переполняться буфером, но слишком длинные строки будут вызывать проблемы. В частности, если пользователь вводит слишком длинную команду, он будет действовать так, как если бы он нажал Enter в середине ее, что приведет к выполнению двух частичных команд. - Выполнение
cd
в подпроцессе не ' t влияет на родительский процесс или любые другие подпроцессы. Чтобы это работало, вам нужно проверить, начинается ли введенная команда с cd
, и, если это так, вызывать chdir
напрямую, а не popen
.
Вот способ, который работает правильно:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(){
char *command = NULL;
size_t commandsize = 0;
char *output = (char *)malloc(5000);
while(1){
printf(">> ");
ssize_t commandlen = getline(&command, &commandsize, stdin);
if(commandlen < 0) {
// assume EOF. Small chance that it was an error though
break;
}
printf("%s", command);
if(!strcmp(command, "exit\n")) {
break;
}
if(!strncmp(command, "cd ", 3)) {
command[commandlen - 1] = '\0'; // remove the newline
if(chdir(command + 3)) {
perror("chdir");
}
continue;
}
FILE *cmd = popen(command, "r");
if(!cmd) {
perror("popen");
continue;
}
size_t sz;
while ((sz = fread(output, 1, 5000, cmd)) > 0){
fwrite(output, 1, sz, stdout);
}
pclose(cmd);
}
free(command);
free(output);
return 0;
}
Несколько замечаний по этому поводу:
- Вместо
malloc
и free
. каждый раз через l oop я перемещал эти переменные в область действия функции, так что это происходит только один раз за все время работы программы. - Я использую
getline
, чтобы прочитать всю строку и автоматически выделить однако много места необходимо. Это, как и popen
, не является частью стандартного C, но является частью POSIX. - Я использую
fread
и fwrite
для передачи данных из процесса, который мы начали, чтобы избежать необходимости думать о нулевых терминаторах. Я предполагаю, что вы в конечном итоге собираетесь выполнить какую-то обработку этих данных; если нет, рассмотрите возможность использования system
вместо popen
, что автоматически запишет вывод пользователю.