Removendo o caractere de nova linha à direita da entrada fgets ()

236

Estou tentando obter alguns dados do usuário e enviá-los para outra função no gcc. O código é algo como isto.

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

No entanto, acho que ele tem um \ncaractere de nova linha no final. Então, se eu entrar John, acaba enviando John\n. Como faço para remover isso \ne enviar uma string adequada.

sfactor
fonte
21
if (!fgets(Name, sizeof Name, stdin))(pelo menos não use duas negações, e =!)
4
@ Roger Pate "não use duas negações" -> hmmm, se cavarmos fundo "não" e "negação" são ambas negações . ;-). Talvez "Use if (fgets(Name, sizeof Name, stdin)) {.
chux - Reinstate Monica
3
@chux, eu tenho certeza que você quis dizerif (fgets(Name, sizeof Name, stdin) == NULL ) {
R Sahu
@RSahu True : pesky !:
chux - Restabelece Monica

Respostas:

155

A maneira um pouco feia:

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

A maneira um pouco estranha:

strtok(Name, "\n");

Observe que a strtokfunção não funciona como esperado se o usuário digitar uma string vazia (ou seja, pressionar apenas Enter). Deixa o \npersonagem intacto.

Existem outros também, é claro.

Jerry Coffin
fonte
7
Qualquer biblioteca de tempo de execução C com reconhecimento de thread (ou seja, a maioria direcionada a uma plataforma multithread) strtok()será protegida por thread (ela usará o armazenamento local do thread para o estado 'inter-call'). Dito isto, ainda é geralmente melhor usar a strtok_r()variante não padrão (mas comum o suficiente) .
Michael Burr
2
Veja minha resposta para obter uma variante totalmente segura e com reentrada, semelhante à sua strtokabordagem (e funciona com entradas vazias). De fato, uma boa maneira de implementar strtoké usar strcspne strspn.
Tim Čas 11/02/2015
2
É importante lidar com o caso contrário, se você estiver em um ambiente em que haja risco de longas filas. A entrada truncada silenciosamente pode causar erros muito prejudiciais.
Malcolm McLean
2
Se você gosta de one-liners e está usando o glibc, tente *strchrnul(Name, '\n') = '\0';.
Twobit 17/08/19
Quando strchr(Name, '\n') == NULL, além de "entrada muito longa para buffer, erro de sinalizador", existem outras possibilidades: O último texto inserido stdinnão terminou com um '\n'ou um caractere nulo incorporado raro foi lido.
chux - Restabelece Monica
440

Talvez a solução mais simples use uma das minhas funções pouco conhecidas favoritas strcspn():

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

Se você deseja que ele também manipule '\r'(por exemplo, se o fluxo for binário):

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

A função conta o número de caracteres até atingir a '\r'ou a '\n'(em outras palavras, encontra o primeiro '\r'ou '\n'). Se não acertar nada, ele pára no '\0'(retornando o comprimento da corda).

Observe que isso funciona bem, mesmo se não houver nova linha, porque strcspnpara em a '\0'. Nesse caso, a linha inteira está simplesmente substituindo '\0'por '\0'.

Tim Čas
fonte
30
Este ainda lida com a rara bufferque começa com '\0', algo que provoca dor pela buffer[strlen(buffer) - 1] = '\0';abordagem.
chux - Restabelece Monica
5
@chux: Sim, eu gostaria que mais pessoas soubessem strcspn(). Uma das funções mais úteis da biblioteca, IMO. Decidi escrever e publicar um monte de hacks C comuns como este hoje; uma strtok_rimplementação usando strcspne strspnfoi uma das primeiras: codepad.org/2lBkZk0w ( Aviso: não posso garantir que seja sem bugs; foi escrito às pressas e provavelmente possui alguns). Não sei onde os publicarei ainda, porém, pretendo fazê-lo no espírito dos famosos "pequenos truques de manipulação".
Tim Čas 11/02/2019
4
Observou maneiras de aparar com robustez . Este parece ser o único alinhamento correto. é mais rápido - embora não tão simples. fgets()strcspn()strlen
chux - Restabelece Monica
6
@sidbushes: A pergunta, tanto no título quanto no conteúdo, pergunta sobre a nova linha à direita da fgets()entrada . Que também é sempre a primeira nova linha.
Tim Čas
9
@sidbushes: entendo de onde você é, mas não posso ser responsabilizado pelos resultados de pesquisa do Google por termos específicos. Fale com o Google, não eu.
Tim Čas
83
size_t ln = strlen(name) - 1;
if (*name && name[ln] == '\n') 
    name[ln] = '\0';
James Morris
fonte
8
Provavelmente lançará uma exceção se a string estiver vazia, não é? Como índice fora do intervalo.
Edward Olamisan
1
@EdwardOlamisan, a string nunca estará vazia no entanto.
James Morris
5
@ James Morris Em casos incomuns fgets(buf, size, ....)-> strlen(buf) == 0. 1) fgets()lê como o primeiro chara '\0'. 2) size == 13) fgets()retorna, NULLentão o bufconteúdo pode ser qualquer coisa. (Embora o código do OP teste NULL) Sugerir:size_t ln = strlen(name); if (ln > 0 && name[ln-1] == '\n') name[--ln] = '\0';
chux - Restabelecer Monica
2
E se a string estiver vazia? lnseria -1, exceto o fato de size_tnão ter sinal, gravando assim na memória aleatória. Eu acho que você deseja usar ssize_te verificar se lné> 0.
abligh 16/03/2015
2
@ legends2k: Uma pesquisa por um valor em tempo de compilação (especialmente um valor zero como em strlen) pode ser implementada com muito mais eficiência do que uma pesquisa simples de caractere por caractere. Por esse motivo, consideraria esta solução melhor do que uma strchrou com strcspnbase.
AnT
17

Abaixo está uma abordagem rápida para remover um potencial '\n'de uma string salva por fgets().
Utiliza strlen(), com 2 testes.

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';
  }

Agora use buffere lenconforme necessário.

Este método tem o benefício colateral de um lenvalor para o código subseqüente. Pode ser facilmente mais rápido que strchr(Name, '\n'). Ref YMMV, mas ambos os métodos funcionam.


buffer, do original fgets()não conterá em "\n"algumas circunstâncias:
A) A linha era muito longa para bufferque apenas o charanterior '\n'seja salvo em buffer. Os caracteres não lidos permanecem no fluxo.
B) A última linha do arquivo não terminou com a '\n'.

Se a entrada incorporou caracteres nulos '\0'em algum lugar, o comprimento relatado por strlen()não incluirá o '\n'local.


Problemas de outras respostas:

  1. strtok(buffer, "\n");falha ao remover o '\n'quando bufferé "\n". A partir desta resposta - alterada após esta resposta para avisar sobre esta limitação.

  2. O seguinte falha em raras ocasiões, quando a primeira charleitura fgets()é '\0'. Isso acontece quando a entrada começa com um incorporado '\0'. Então buffer[len -1]torna-se buffer[SIZE_MAX]acessar a memória certamente fora da faixa legítima de buffer. Algo que um hacker pode tentar ou encontrar na leitura tola de arquivos de texto UTF16. Esse era o estado de uma resposta quando essa resposta foi escrita. Posteriormente, um não-OP editou-o para incluir um código como o de verificação desta resposta "".

    size_t len = strlen(buffer);
    if (buffer[len - 1] == '\n') {  // FAILS when len == 0
      buffer[len -1] = '\0';
    }
  3. sprintf(buffer,"%s",buffer);é um comportamento indefinido: ref . Além disso, ele não salva nenhum espaço em branco inicial, separador ou final. Agora excluído .

  4. [Editar devido a uma boa resposta posterior ] Não há problemas com o liner 1 buffer[strcspn(buffer, "\n")] = 0;além do desempenho em comparação com a strlen()abordagem. O desempenho no corte geralmente não é um problema, pois o código está executando a E / S - um buraco negro no tempo da CPU. Se o código a seguir precisar do comprimento da string ou tiver alto desempenho, use esta strlen()abordagem. Caso contrário, strcspn()é uma boa alternativa.

chux - Restabelecer Monica
fonte
Obrigado pela resposta útil. Podemos usar strlen(buffer)quando o tamanho do buffer é alocado dinamicamente usando malloc?
rrz0
@ Rrz0 buffer = malloc(allocation_size); length = strlen(buffer);está incorreto - os dados na memória apontados por buffersão desconhecidos. buffer = malloc(allocation_size_4_or_more); strcpy(buffer, "abc"); length = strlen(buffer);está OK
chux - Reinstala Monica
obrigado por isso !! Estou fazendo um curso de CS e isso foi muito útil para uma das tarefas. Eu creditei sua resposta no código fonte.
Nathaniel Hoyt
8

Direto para remover '\ n' da saída do fgets, se todas as linhas tiverem '\ n'

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

De outra forma:

void remove_newline_ch(char *line)
{
    int new_line = strlen(line) -1;
    if (line[new_line] == '\n')
        line[new_line] = '\0';
}
Amitabha
fonte
1
Observe que seria mais seguro usar em strnlenvez de strlen.
Mike Mertsock
3
Um comentário à primeira resposta na pergunta vincula os estados "Observe que strlen (), strcmp () e strdup () são seguros. As alternativas 'n' oferecem funcionalidade adicional".
Étienne
4
@ esker não, não seria. inserir um nnão aumenta magicamente a segurança; nesse caso, de fato, tornaria o código mais perigoso. Da mesma forma strncpy, uma função terrivelmente insegura. A postagem à qual você vinculou é um mau conselho.
MM
3
Isso falha miseravelmente para uma string vazia ( ""). Também não strlen()retorna . size_tint
alk
4
isso não é seguro para uma string vazia, ela será gravada no índice -1. Não use isso.
Jean-François Fabre
3

Para um corte único \ \ n,

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

para vários cortes \ \ n ',

void remove_multi_new_line(char* string)
{
  size_t length = strlen(string);
  while((length>0) && (string[length-1] == '\n'))
  {
      --length;
      string[length] ='\0';
  }
}
BEPP
fonte
1
Por que aninhar ifquando você pode simplesmente escrever uma condição usando &&? Esse whileloop tem uma estrutura estranha; poderia simplesmente ser while (length > 0 && string[length-1] == '\n') { --length; string[length] = '\0'; }.
Melpomene
@melpomene obrigado pela sugestão. Atualize o código.
BEPP 18/09/19
1
Eu sugiro que a primeira função é mais naturalmente definido como: size_t length = strlen(string); if (length > 0 && string[length-1] == '\n') { string[length-1] = '\0'; }. Isso também reflete melhor a segunda definição (apenas usando em ifvez de while).
Melpomene
@elpomene thanks. Faz sentido. Eu atualizei o código.
BEPP
1

My Newbie way ;-) Por favor, deixe-me saber se isso está correto. Parece estar funcionando para todos os meus casos:

#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;
}
Duck Ling
fonte
1

As etapas para remover o caractere de nova linha da maneira talvez mais óbvia:

  1. Determine o comprimento da string dentro NAMEusando strlen(), header string.h. Observe que strlen()não conta a terminação \0.
size_t sl = strlen(NAME);

  1. Veja se a sequência começa com ou inclui apenas um \0caractere (sequência vazia). Nesse caso slseria 0porque, strlen()como eu disse acima, não conta \0e para na primeira ocorrência:
if(sl == 0)
{
   // Skip the newline replacement process.
}

  1. Verifique se o último caractere da sequência correta é um caractere de nova linha '\n'. Se for esse o caso, substitua \npor a \0. Observe que as contagens de índice começam em, 0portanto, precisaremos NAME[sl - 1]:
if(NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

Observe que se você pressionar Enter apenas na fgets()solicitação de sequência (o conteúdo da sequência era composto apenas por um caractere de nova linha), a sequência NAMEserá uma sequência vazia posteriormente.


  1. Podemos combinar os passos 2. e 3. juntos em apenas um if declaração usando o operador lógico &&:
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

  1. O código finalizado:
size_t sl = strlen(NAME);
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

Se você preferir uma função para usar essa técnica, manipulando as fgetsseqüências de saída em geral sem redigitar todas as vezes, aqui está 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';
    }
}

No seu exemplo fornecido, seria:

printf("Enter your Name: ");

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

Observe que esse método não funciona se a string de entrada tiver incorporado \0 s . Se fosse esse o caso strlen(), retornaria apenas a quantidade de caracteres até o primeiro \0. Mas essa não é uma abordagem comum, já que as funções de leitura de cordas geralmente param na primeira\0 e levam a string até esse caractere nulo.

Além da pergunta por si só. Tente evitar negações duplas que tornam a sua unclearer código: if (!(fgets(Name, sizeof Name, stdin) != NULL) {}. Você pode simplesmente fazer if (fgets(Name, sizeof Name, stdin) == NULL) {}.

RobertS suporta Monica Cellio
fonte
Não sei por que você gostaria de fazer isso. O objetivo de remover novas linhas não é finalizar nulo seqüências de caracteres; é remover novas linhas. Substituir um \npor um \0no final de uma string é uma maneira de "remover" a nova linha. Mas a substituição de \ncaracteres dentro de uma sequência altera fundamentalmente a sequência. Não é incomum ter seqüências de caracteres com vários caracteres de nova linha intencionais, e isso efetivamente cortaria as extremidades dessas strings. Para remover essas novas linhas, o conteúdo da matriz precisa mudar para a esquerda para substituir o \n.
ex nihilo
@exnihilo Como alguém pode inserir uma string com várias novas linhas usando fgets()?
RobertS apoia Monica Cellio
Bem, você pode concatenar seqüências de caracteres obtidas por várias chamadas para fgets(). Mas não entendo sua objeção: é você quem propõe um código para lidar com várias novas linhas.
ex nihilo
@exnihilo Você está certo, vou pensar demais na estratégia. Eu só queria adicionar uma maneira muito dura, mas possível, de obter o resultado desejado.
RobertS apoia Monica Cellio em
@exnihilo Editou minha resposta completamente e seguiu a abordagem principal usando strlenetc. Justificação para não ser uma duplicata: 1. Explicação do código por etapas. 2. Fornecido como função e solução baseada em contexto. 3. Dica para evitar expressões de dupla negação.
RobertS apoia Monica Cellio em
0

Tim Čas um liner é incrível para seqüências de caracteres obtidas por uma chamada para fgets, porque você sabe que elas contêm uma única nova linha no final.

Se você estiver em um contexto diferente e quiser manipular seqüências de caracteres que possam conter mais de uma nova linha, talvez esteja procurando por strrspn. Não é POSIX, o que significa que você não o encontrará em todos os Unices. Eu escrevi um para minhas próprias necessidades.

/* 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;
}

Para quem procura um equivalente Perl chomp em C, acho que é isso (o chomp remove apenas a nova linha à direita).

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

A função 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;
}
Philippe A.
fonte
1
"porque você sabe que eles contêm uma única nova linha no final". -> Funciona mesmo quando não existe '\n'(ou se a string é "").
chux - Restabelece Monica
Em resposta ao seu primeiro comentário chux, minha resposta preserva isso. Eu tive que jogar resetlen strrcspnpara quando não há \n.
Philippe A.
Por que usar em goto end;vez de return len;?
chqrlie
@chqrlie Eu precisava sair desse loop deselegante de dois níveis em que entrei. O dano foi feito. Por que não um goto?
Philippe A.
Você tem dois tipos de gotos no seu código: um inútil gotoque pode ser substituído por uma returndeclaração e um reverso gotoque é considerado mau. Usando strchrajuda a implementar strrspne strrcspnde uma forma mais simples: 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; }esize_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

Se usar getlineé uma opção - não negligenciando seus problemas de segurança e se você deseja colocar ponteiros - você pode evitar as funções de cadeia, pois getlineretorna o número de caracteres. Algo como abaixo

#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;
}

Nota : Os [ problemas de segurança ] com getlinenão devem ser negligenciados.

sjsam
fonte
-1

A função abaixo faz parte da biblioteca de processamento de strings que estou mantendo no Github. Remove caracteres indesejados de uma string, exatamente o que você deseja

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;
}

Um exemplo de uso pode ser

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

Você pode verificar outras funções disponíveis ou até contribuir com o projeto :) https://github.com/fnoyanisi/zString

fnisi
fonte
Você deve remover o *em *src++;e fazer bad, tokene d const char *. Além disso, por que não usar em strchrvez de zChrSearch? *srcnão pode estar '\0'em sua zStrrmvfunção.
chqrlie
Obrigado @chqrlie! atualizou o código para refletir suas sugestões ..... zstring começou como um projeto divertido com o objectivo de criar uma biblioteca de manipulação de strings sem o uso de funções da biblioteca padrão, portanto, eu não usostrchr
fnisi
1
Escrever uma " biblioteca de manipulação de strings sem usar nenhuma função padrão da biblioteca " é um bom exercício, mas por que dizer a outras pessoas para usá-lo? Se for o caso, será mais lento e menos testado do que qualquer biblioteca padrão.
Melpomene
Isso está fazendo um trabalho diferente do que a pergunta faz. Provavelmente pode ser usado para se livrar da única nova linha, mas parece um exagero.
27618 Jonathan-Leffler-
-1
 for(int i = 0; i < strlen(Name); i++ )
{
    if(Name[i] == '\n') Name[i] = '\0';
}

Você deveria tentar. Esse código basicamente percorre a string até encontrar o '\ n'. Quando encontrado, '\ n' será substituído pelo terminador de caracteres nulos '\ 0'

Observe que você está comparando caracteres e não seqüências de caracteres nesta linha, então não há necessidade de usar strcmp ():

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

pois você usará aspas simples e não aspas duplas. Aqui está um link sobre aspas simples vs duplas, se você quiser saber mais

Matheus Martins Jerônimo
fonte
2
seria melhor se você explicasse e editasse o formato do seu código.
Anh Pham
Geralmente, é melhor explicar uma solução em vez de apenas postar algumas linhas de código anônimo. Você pode ler Como redigir uma boa resposta e também Explicar respostas inteiramente baseadas em código .
Massimiliano Kraus
1
Sinto muito, esta foi minha primeira contribuição aqui. Eu irei consertar isso. Obrigado pelo feedback
Matheus Martins Jerônimo
3
Ineficiente: for(int i = 0; i < strlen(Name); i++ )chamará strlen(Name)muitas vezes (alterações de loop Name[]), portanto, com um comprimento N, essa é uma O(N*N)solução. Apenas uma chamada para strlen(Name), se houver, é necessária para fornecer uma solução O (N) `. Não int iestá claro por que é usado em vez de size_t i. Considerefor(size_t i = 0; i < Name[i]; i++ )
chux - Restabelece Monica
@chux Mais comofor (size_t i = 0; Name[i]; i++) { if (Name[i] == '\n') { Name[i] = '\0'; break; } }
melpomene
-1

Tente este:

        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;
        }
Balazs Kiss
fonte
1
len = strlen(str)pode estourar: strlenretorna size_t, não int. O que há com os if (len>0) if (...)condicionais estranhos ? Você não conhece &&? Se você deseja remover várias instâncias finais de CR / LF, por que se limitar a 5? Por que não remover todos eles? Por que a função tem um inttipo de retorno quando sempre retorna 0? Por que não apenas retornarvoid ?
Melpomene