Удаление завершающего символа новой строки из ввода fgets ()


236

Я пытаюсь получить некоторые данные от пользователя и отправить их в другую функцию в gcc. Код примерно такой.

printf("Enter your Name: ");
if (!(fgets(Name, sizeof Name, stdin) != NULL)) {
    fprintf(stderr, "Error reading Name.\n");
    exit(1);
}

Тем не менее, я обнаружил, что \nв конце он имеет символ новой строки . Так что, если я вхожу, Johnэто заканчивается отправкой John\n. Как мне удалить это \nи отправить правильную строку.


21
if (!fgets(Name, sizeof Name, stdin))(по крайней мере, не используйте два отрицания,! и! =)

4
@ Роджер Пэйт "не используйте два отрицания" -> хммм, если мы копаем глубоко, "не" и "отрицание" - оба отрицания . ;-). Возможно "Использовать if (fgets(Name, sizeof Name, stdin)) {.
Chux - Восстановить Монику

3
@chux, я уверен, что вы имели в видуif (fgets(Name, sizeof Name, stdin) == NULL ) {
R Sahu

@RSahu Правда : надоедливый !:
chux - Восстановить Монику

Ответы:


155

Немного безобразный способ:

char *pos;
if ((pos=strchr(Name, '\n')) != NULL)
    *pos = '\0';
else
    /* input too long for buffer, flag error */

Немного странный способ:

strtok(Name, "\n");

Обратите внимание, что strtokфункция не работает должным образом, если пользователь вводит пустую строку (т.е. нажимает только Enter). Это оставляет \nперсонажа нетронутым.

Конечно, есть и другие.


7
Любая библиотека времени выполнения C, поддерживающая потоки (то есть большинство из них, предназначенные для многопоточной платформы), strtok()будет поточно-безопасной (она будет использовать локальное хранилище потоков для состояния 'inter-call'). Тем не менее, в целом все же лучше использовать нестандартный (но достаточно распространенный) strtok_r()вариант.
Майкл Берр

2
Смотрите мой ответ для полностью поточно-ориентированного и реентерабельного варианта, похожего на ваш strtokподход (и он работает с пустыми входами). На самом деле хорошим способом реализации strtokявляется использование strcspnи strspn.
Тим Час

2
Важно рассмотреть другой случай, если вы находитесь в среде, где существует риск слишком длинных линий. Беззвучное усечение ввода может привести к очень опасным ошибкам.
Малкольм Маклин

2
Если вам нравятся однострочники и вы используете glibc, попробуйте *strchrnul(Name, '\n') = '\0';.
два бита

Когда strchr(Name, '\n') == NULL, кроме «слишком длинного ввода для буфера, есть ошибка флага», существуют и другие возможности: последний текст в stdinне заканчивался '\n'символом или был прочитан редкий внедренный нулевой символ.
chux - Восстановить Монику

440

Возможно, самое простое решение использует одну из моих любимых малоизвестных функций strcspn():

buffer[strcspn(buffer, "\n")] = 0;

Если вы хотите, чтобы он также обрабатывал '\r'(скажем, если поток двоичный):

buffer[strcspn(buffer, "\r\n")] = 0; // works for LF, CR, CRLF, LFCR, ...

Функция считает количество символов до тех пор, пока не достигнет a '\r'или a '\n'(другими словами, она найдет первый '\r'или '\n'). Если он ничего не ударил, он останавливается на '\0'(возвращая длину строки).

Обратите внимание, что это работает нормально, даже если нет новой строки, потому что strcspnостанавливается на '\0'. В этом случае вся строка просто заменяется '\0'на '\0'.


30
Это даже ручки редкая , bufferчем начинается с '\0', то , что приводит к печальным последствиям для buffer[strlen(buffer) - 1] = '\0';подхода.
chux - Восстановить Монику

5
@chux: Да, я бы хотел, чтобы больше людей знали об этом strcspn(). Одна из наиболее полезных функций в библиотеке, IMO. Я решил написать и опубликовать кучу распространенных C-хаков, подобных этому сегодня; strtok_rреализация с использованием strcspnи strspnбыл одним из первых: codepad.org/2lBkZk0w ( Внимание: я не могу гарантировать , что это без ошибок, она была написана наспех и , вероятно, некоторые из них). Я пока не знаю, где я их опубликую, но я намерен сделать это в духе знаменитых «хихиканьях».
Тим Час

4
Изучил способы надежной обрезкиfgets() . Это , strcspn()кажется, единственный правильный один вкладыш. strlenбыстрее - хотя и не так просто.
chux - Восстановить Монику

6
@sidbushes: вопрос, как в названии, так и в содержании, спрашивает о конце новой строки после fgets()ввода . Который всегда является также первой новой строкой.
Тим

9
@sidbushes: я понимаю, откуда вы, но не могу нести ответственность за результаты поиска Google по определенным терминам. Поговорите с Google, а не со мной.
Тим

83
size_t ln = strlen(name) - 1;
if (*name && name[ln] == '\n') 
    name[ln] = '\0';

8
Вероятно, выдает исключение, если строка пуста, не так ли? Как индекс вне диапазона.
Эдвард Оламисан

1
@EdwardOlamisan, однако строка никогда не будет пустой.
Джеймс Моррис

5
@ Джеймс Моррис В необычных случаях fgets(buf, size, ....)-> strlen(buf) == 0. 1) fgets()читается как первый charа '\0'. 2) size == 13) fgets()возвращает NULLтогда bufсодержимое может быть что угодно. (Хотя код OP проверяет NULL). Предложить:size_t ln = strlen(name); if (ln > 0 && name[ln-1] == '\n') name[--ln] = '\0';
chux - восстановить Монику

2
Что если строка пуста? lnбудет -1, за исключением того, что факт size_tбез знака, таким образом, запись в случайную память. Я думаю, что вы хотите использовать ssize_tи проверьте ln> 0.
abligh

2
@ legends2k: поиск значения времени компиляции (особенно нулевого значения, как в strlen) может быть реализован намного эффективнее, чем простой поиск по типу символов. По этой причине я бы посчитал это решение лучше, чем основанное strchrили strcspnоснованное.
AnT

17

Ниже приведен быстрый подход к удалению потенциала '\n'из строки, сохраненной с помощью fgets().
Используется strlen()с 2-мя тестами.

char buffer[100];
if (fgets(buffer, sizeof buffer, stdin) != NULL) {

  size_t len = strlen(buffer);
  if (len > 0 && buffer[len-1] == '\n') {
    buffer[--len] = '\0';
  }

Теперь используйте bufferи по lenмере необходимости.

Этот метод имеет побочное преимущество lenзначения для последующего кода. Это может быть легко быстрее, чем strchr(Name, '\n'). Реф YMMV, но оба метода работают.


buffer, из оригинала fgets()не будет содержаться "\n"при некоторых обстоятельствах:
A) слишком длинная строка, bufferпоэтому сохраняется только charпредшествующее . Непрочитанные символы остаются в потоке. Б) Последняя строка в файле не заканчивается на . '\n'buffer
'\n'

Если во входных данных есть где-то встроенные нулевые символы '\0', длина, указанная пользователем strlen(), не будет включать '\n'местоположение.


Некоторые другие ответы на вопросы:

  1. strtok(buffer, "\n");не удается удалить, '\n'когда bufferесть "\n". Из этого ответа - исправлены после этого ответа, чтобы предупредить об этом ограничении.

  2. Следующий сбой в редких случаях , когда первое charчтение на fgets()это '\0'. Это происходит, когда ввод начинается со встроенного '\0'. Тогда buffer[len -1]становится buffer[SIZE_MAX]доступ к памяти определенно за пределами законного диапазона buffer. Что-то хакер может попробовать или найти в глупом чтении текстовых файлов UTF16. Это было состояние ответа, когда этот ответ был написан. Позже не-OP отредактировал его, включив в него код, подобный проверке этого ответа "".

    size_t len = strlen(buffer);
    if (buffer[len - 1] == '\n') {  // FAILS when len == 0
      buffer[len -1] = '\0';
    }
  3. sprintf(buffer,"%s",buffer);не определено поведение: Ref . Кроме того, он не сохраняет начальные, разделительные или конечные пробелы. Сейчас удалено .

  4. [Изменить из-за хорошего последующего ответа ] С 1 вкладышем нет проблем, buffer[strcspn(buffer, "\n")] = 0;кроме производительности по сравнению с strlen()подходом. Производительность при обрезке обычно не является проблемой, учитывая, что код выполняет ввод-вывод - черная дыра процессорного времени. Если следующий код нуждается в длине строки или имеет высокую производительность, используйте этот strlen()подход. Иначе strcspn()это прекрасная альтернатива.


Спасибо за полезный ответ. Можем ли мы использовать, strlen(buffer)когда размер буфера динамически распределяется с помощью malloc?
rrz0

@ Rrz0 buffer = malloc(allocation_size); length = strlen(buffer);плохо - данные в памяти, на которые указывает bufferнеизвестно. buffer = malloc(allocation_size_4_or_more); strcpy(buffer, "abc"); length = strlen(buffer);все в порядке
Chux - Восстановить Монику

Спасибо за это!! Я беру курс CS, и это было очень полезно для одного из заданий. я зачислил ваш ответ в исходном коде.
Натаниэль Хойт

8

Прямое удаление \ n из вывода fgets, если в каждой строке есть \ n

line[strlen(line) - 1] = '\0';

В противном случае:

void remove_newline_ch(char *line)
{
    int new_line = strlen(line) -1;
    if (line[new_line] == '\n')
        line[new_line] = '\0';
}

1
Обратите внимание, что было бы безопаснее использовать strnlenвместо strlen.
Майк Мерцок,

3
Комментарий к первому ответу в связанных состояниях вопроса: «Обратите внимание, что strlen (), strcmp () и strdup () безопасны. Альтернатива 'n' дает вам дополнительную функциональность".
Этьен

4
@ эскер нет, не будет. вставка nсимвола не повышает магическую безопасность, в этом случае это фактически делает код более опасным. Точно так же strncpy, с ужасно небезопасной функцией. Пост, на который вы ссылаетесь, - плохой совет.
ММ

3
Это с треском проваливается для пустой строки ( ""). Также не strlen()возвращается . size_tint
алк

4
это небезопасно для пустой строки, она будет писать с индексом -1. Не используйте это.
Жан-Франсуа Фабр

3

Для одиночной обрезки \ n

void remove_new_line(char* string)
{
    size_t length = strlen(string);
    if((length > 0) && (string[length-1] == '\n'))
    {
        string[length-1] ='\0';
    }
}

для многократной обрезки \ n

void remove_multi_new_line(char* string)
{
  size_t length = strlen(string);
  while((length>0) && (string[length-1] == '\n'))
  {
      --length;
      string[length] ='\0';
  }
}

1
Зачем вкладывать, ifкогда вы можете просто написать одно условие, используя &&? Этот whileцикл имеет странную структуру; это может быть просто while (length > 0 && string[length-1] == '\n') { --length; string[length] = '\0'; }.
Мельпомена

@melpomene спасибо за предложение. Обновите код.
BEPP

1
Я бы предположить , что первая функция более естественно определяется как: size_t length = strlen(string); if (length > 0 && string[length-1] == '\n') { string[length-1] = '\0'; }. Это также лучше отражает второе определение (просто используя ifвместо while).
Мельпомена

@elpomene спасибо. Это имеет смысл. Я обновил код.
БЭПП

1

Мой путь новичка ;-) Пожалуйста, дайте мне знать, если это правильно. Кажется, работает для всех моих случаев:

#define IPT_SIZE 5

int findNULL(char* arr)
{
    for (int i = 0; i < strlen(arr); i++)
    {
        if (*(arr+i) == '\n')
        {
            return i;
        }
    }
    return 0;
}

int main()
{
    char *input = malloc(IPT_SIZE + 1 * sizeof(char)), buff;
    int counter = 0;

    //prompt user for the input:
    printf("input string no longer than %i characters: ", IPT_SIZE);
    do
    {
        fgets(input, 1000, stdin);
        *(input + findNULL(input)) = '\0';
        if (strlen(input) > IPT_SIZE)
        {
            printf("error! the given string is too large. try again...\n");
            counter++;
        }
        //if the counter exceeds 3, exit the program (custom function):
        errorMsgExit(counter, 3); 
    }
    while (strlen(input) > IPT_SIZE);

//rest of the program follows

free(input)
return 0;
}

1

Шаги по удалению символа новой строки, возможно, наиболее очевидным способом:

  1. Определите длину строки внутри NAME, используя strlen()заголовок string.h. Обратите внимание, что strlen()не считается окончание \0.
size_t sl = strlen(NAME);

  1. Посмотрите, начинается ли строка с или содержит только один \0символ (пустая строка). В этом случае slбудет, 0поскольку, strlen()как я сказал выше, не учитывается \0и останавливается при первом появлении этого:
if(sl == 0)
{
   // Skip the newline replacement process.
}

  1. Проверьте, является ли последний символ правильной строки символом новой строки '\n'. Если это так, замените \nна \0. Обратите внимание, что индекс начинается с 0того, что нам нужно сделать NAME[sl - 1]:
if(NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

Обратите внимание, что если вы только нажали Enter при fgets()запросе строки (содержимое строки состояло только из символа новой строки), строка NAMEпосле будет пустой строкой.


  1. Мы можем объединить шаги 2. и 3. вместе в одном ifвыражении, используя логический оператор &&:
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

  1. Готовый код:
size_t sl = strlen(NAME);
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

Если вы предпочитаете функцию для использования этой техники, обрабатывая fgetsвыходные строки в целом, не перепечатывая каждый раз, вот fgets_newline_kill:

void fgets_newline_kill(char a[])
{
    size_t sl = strlen(a);

    if(sl > 0 && a[sl - 1] == '\n')
    {
       a[sl - 1] = '\0';
    }
}

В приведенном вами примере это будет:

printf("Enter your Name: ");

if (fgets(Name, sizeof Name, stdin) == NULL) {
    fprintf(stderr, "Error reading Name.\n");
    exit(1);
}
else {
    fgets_newline_kill(NAME);
}

Обратите внимание, что этот метод не работает, если во входную строку встроены \0s. В этом случае strlen()будет возвращаться только количество символов до первого \0. Но это не совсем обычный подход, так как большинство функций чтения строк обычно останавливаются на первом \0и принимают строку до этого нулевого символа.

Помимо вопроса сам по себе. Старайтесь избегать двойных отрицаний , которые делают ваш код unclearer: if (!(fgets(Name, sizeof Name, stdin) != NULL) {}. Вы можете просто сделать if (fgets(Name, sizeof Name, stdin) == NULL) {}.


Не уверен, почему вы хотели бы сделать это. Смысл удаления новых строк состоит не в том, чтобы завершать строки нулем; это удалить новые строки. Замена \nс \0в конце строки является способом «удаления» новой строки. Но замена \nсимволов внутри строки в корне меняет строку. Нередко встречаются строки с преднамеренным множеством символов новой строки, и это эффективно отрубает концы этих строк. Чтобы удалить такие новые строки, содержимое массива необходимо сместить влево, чтобы перезаписать \n.
ex nihilo

@exnihilo Как кто-то может ввести строку с несколькими символами новой строки внутри, используя fgets()?
RobertS поддерживает Монику Челлио

Ну, вы можете объединить строки, полученные несколькими вызовами fgets(). Но я не понимаю вашего возражения: вы единственный, кто предлагает код для работы с несколькими символами новой строки.
ex nihilo

@ exnihilo Вы правы, я продумал стратегию. Я просто хотел добавить очень резкий, но возможный способ получить желаемый результат.
RobertS поддерживает Монику Челлио

@exnihilo Полностью отредактировал мой ответ и следовал основному подходу, используя strlenи т. д. Обоснование того, что он не является дубликатом: 1. Пояснение кода по шагам. 2. Предоставляется как функционально-контекстное решение. 3. Подсказка, чтобы избежать выражений двойного отрицания.
RobertS поддерживает Монику Челлио

0

Tim Čas один вкладыш удивителен для строк, полученных при вызове fgets, потому что вы знаете, что в конце они содержат одну новую строку.

Если вы находитесь в другом контексте и хотите обрабатывать строки, которые могут содержать более одной новой строки, возможно, вы ищете strrspn. Это не POSIX, то есть вы не найдете его на всех Unices. Я написал один для своих нужд.

/* Returns the length of the segment leading to the last 
   characters of s in accept. */
size_t strrspn (const char *s, const char *accept)
{
  const char *ch;
  size_t len = strlen(s);

more: 
  if (len > 0) {
    for (ch = accept ; *ch != 0 ; ch++) {
      if (s[len - 1] == *ch) {
        len--;
        goto more;
      }
    }
  }
  return len;
}

Для тех, кто ищет Perl-аналог chomp в C, я думаю, что это так (chomp удаляет только завершающий перевод строки).

line[strrspn(string, "\r\n")] = 0;

Функция strrcspn:

/* Returns the length of the segment leading to the last 
   character of reject in s. */
size_t strrcspn (const char *s, const char *reject)
{
  const char *ch;
  size_t len = strlen(s);
  size_t origlen = len;

  while (len > 0) {
    for (ch = reject ; *ch != 0 ; ch++) {
      if (s[len - 1] == *ch) {
        return len;
      }
    }
    len--;
  }
  return origlen;
}

1
«потому что вы знаете, что они содержат одну новую строку в конце». -> Это даже работает, когда нет '\n'(или, если строка "").
Chux - Восстановить Монику

В ответ на ваш первый комментарий chux, мой ответ сохраняет это. Пришлось скинуть ресетлен в strrcspnтечение, когда нет \n.
Филипп А.

Почему использовать goto end;вместо return len;?
chqrlie

@chqrlie Мне нужно было выйти из этого неумелого двухуровневого цикла, в который я попал. Вред был нанесен. Почему не Goto?
Филипп А.

В gotoвашем коде есть два вида s: бесполезные, gotoкоторые можно заменить на returnутверждение, и обратные, gotoкоторые считаются злыми. Использование strchrпомогает реализовать strrspnи strrcspnв более простой форме: size_t strrspn(const char *s, const char *accept) { size_t len = strlen(s); while (len > 0 && strchr(accept, s[len - 1])) { len--; } return len; }иsize_t strrcspn(const char *s, const char *reject) { size_t len = strlen(s); while (len > 0 && !strchr(reject, s[len - 1])) { len--; } return len; }
chqrlie

0

Если вы используете getlineопцию - не пренебрегая ее проблемами безопасности и если вы хотите заключить указатели в скобки - вы можете избежать строковых функций, поскольку они getlineвозвращают количество символов. Что-то вроде ниже

#include<stdio.h>
#include<stdlib.h>
int main(){
char *fname,*lname;
size_t size=32,nchar; // Max size of strings and number of characters read
fname=malloc(size*sizeof *fname);
lname=malloc(size*sizeof *lname);
if(NULL == fname || NULL == lname){
 printf("Error in memory allocation.");
 exit(1);
}
printf("Enter first name ");
nchar=getline(&fname,&size,stdin);
if(nchar == -1){ // getline return -1 on failure to read a line.
 printf("Line couldn't be read.."); 
 // This if block could be repeated for next getline too
 exit(1);
}
printf("Number of characters read :%zu\n",nchar);
fname[nchar-1]='\0';
printf("Enter last name ");
nchar=getline(&lname,&size,stdin);
printf("Number of characters read :%zu\n",nchar);
lname[nchar-1]='\0';
printf("Name entered %s %s\n",fname,lname);
return 0;
}

Примечание : не следует пренебрегать [ проблемами безопасности ] с getline.


-1

Приведенная ниже функция является частью библиотеки обработки строк, которую я поддерживаю на Github. Он удаляет и нежелательные символы из строки, именно то, что вы хотите

int zstring_search_chr(const char *token,char s){
    if (!token || s=='\0')
        return 0;

    for (;*token; token++)
        if (*token == s)
            return 1;

    return 0;
}

char *zstring_remove_chr(char *str,const char *bad) {
    char *src = str , *dst = str;
    while(*src)
        if(zstring_search_chr(bad,*src))
            src++;
        else
            *dst++ = *src++;  /* assign first, then incement */

    *dst='\0';
        return str;
}

Пример использования может быть

Example Usage
      char s[]="this is a trial string to test the function.";
      char const *d=" .";
      printf("%s\n",zstring_remove_chr(s,d));

  Example Output
      thisisatrialstringtotestthefunction

Вы можете проверить другие доступные функции или даже внести свой вклад в проект :) https://github.com/fnoyanisi/zString


Вы должны удалить *в *src++;и сделать bad, tokenи d const char *. И почему бы не использовать strchrвместо zChrSearch? *srcне может быть '\0'в вашей zStrrmvфункции.
chqrlie

Спасибо @chqrlie! обновил код, чтобы отразить ваши предложения ..... zstring начался как забавный проект с целью создания библиотеки для работы со строками без использования каких-либо стандартных библиотечных функций, поэтому я не использовалstrchr
fnisi

1
Написание « библиотеки манипулирования строками без использования каких-либо стандартных библиотечных функций » является хорошим упражнением, но зачем говорить другим людям, чтобы это использовать? Во всяком случае, это будет медленнее и менее проверено, чем любая стандартная библиотека.
Мельпомена

Это делает работу, отличную от той, о которой спрашивает вопрос. Это, вероятно, может использоваться, чтобы избавиться от единственной новой строки, но это похоже на излишество.
Джонатан Леффлер

-1
 for(int i = 0; i < strlen(Name); i++ )
{
    if(Name[i] == '\n') Name[i] = '\0';
}

Вы должны попробовать. Этот код в основном перебирает строку, пока не найдет \ n. Когда он будет найден, \ n будет заменен нулевым символом-терминатором

Обратите внимание, что вы сравниваете символы, а не строки в этой строке, поэтому нет необходимости использовать strcmp ():

if(Name[i] == '\n') Name[i] = '\0';

так как вы будете использовать одинарные кавычки, а не двойные кавычки. Вот ссылка на одиночные и двойные кавычки, если вы хотите узнать больше


2
было бы лучше, если бы вы объяснили и отредактировали формат вашего кода.
Ань Фам

Обычно лучше объяснить решение, а не просто опубликовать несколько строк анонимного кода. Вы можете прочитать Как написать хороший ответ , а также Объяснить полностью основанные на коде ответы .
Массимилиано Краус

1
Извините, это был мой первый вклад. Я починю это. Спасибо за отзыв
Матеус Мартинс

3
Неэффективно: for(int i = 0; i < strlen(Name); i++ )будет вызывать strlen(Name)много раз (смена цикла Name[]), поэтому с длиной Nэто O(N*N)решение. Только один вызов strlen(Name), если таковой имеется, необходим для обеспечения решения O (N) `. Непонятно, почему int iиспользуется вместо size_t i. Рассмотримfor(size_t i = 0; i < Name[i]; i++ )
chux - Восстановить Монику

@chux Больше похоже наfor (size_t i = 0; Name[i]; i++) { if (Name[i] == '\n') { Name[i] = '\0'; break; } }
melpomene

-1

Попробуй это:

        int remove_cr_lf(char *str)
        {
          int len =0;


          len = strlen(str);

          for(int i=0;i<5;i++)
          {
            if (len>0)
            if (str[len-1] == '\n')
            {
              str[len-1] = 0;
              len--;
            }

            if (len>0)
            if (str[len-1] == '\r')
            {
              str[len-1] = 0;
              len--;
            }
          }

          return 0;
        }

1
len = strlen(str)может переполниться: strlenвозвращается size_t, нет int. Что за странные if (len>0) if (...)условия? Вы не знаете о &&? Если вы собираетесь удалить несколько завершающих экземпляров CR / LF, зачем ограничивать себя 5? Почему бы не удалить их все? Почему функция имеет intвозвращаемый тип, когда она всегда возвращает 0? Почему бы просто не вернуться void?
Мельпомена
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.