Eliminando el carácter de nueva línea final de la entrada fgets ()


236

Estoy tratando de obtener algunos datos del usuario y enviarlos a otra función en gcc. El código es algo como esto.

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

Sin embargo, me parece que tiene un \ncarácter de nueva línea al final. Entonces, si entro John, termina enviando John\n. ¿Cómo elimino eso \ny envío una cadena adecuada?


21
if (!fgets(Name, sizeof Name, stdin))(¡al menos no use dos negaciones,! y! =)

44
@Roger Pate "no uses dos negaciones" -> hmmm, si profundizamos "no" y "negación" son ambas negaciones . ;-). Quizás "Use if (fgets(Name, sizeof Name, stdin)) {.
chux - Reinstate Monica

3
@chux, estoy seguro de que quisiste decirif (fgets(Name, sizeof Name, stdin) == NULL ) {
R Sahu

@RSahu True : molesto !:
chux - Restablece a Mónica el

Respuestas:


155

La forma un poco fea:

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

La forma un poco extraña:

strtok(Name, "\n");

Tenga en cuenta que la strtokfunción no funciona como se esperaba si el usuario ingresa una cadena vacía (es decir, solo presiona Enter). Deja el \npersonaje intacto.

También hay otros, por supuesto.


77
Cualquier biblioteca de tiempo de ejecución C que sea compatible con subprocesos (es decir, la mayoría de los que se dirigen a una plataforma multiproceso) strtok()será segura para subprocesos (usará almacenamiento local de subprocesos para el estado 'entre llamadas'). Dicho esto, todavía es generalmente mejor usar la strtok_r()variante no estándar (pero lo suficientemente común) .
Michael Burr

2
Vea mi respuesta para una variante completamente segura y reentrante para subprocesos, similar a su strtokenfoque (y funciona con entradas vacías). De hecho, una buena manera de implementar strtokes usar strcspny strspn.
Tim Čas

2
Es importante manejar el caso else si se encuentra en un entorno donde existe el riesgo de líneas demasiado largas. Truncar silenciosamente la entrada puede causar errores muy dañinos.
Malcolm McLean

2
Si te gustan las frases sencillas y estás usando glibc, inténtalo *strchrnul(Name, '\n') = '\0';.
twobit

Cuando strchr(Name, '\n') == NULL, además de "entrada demasiado larga para el búfer, error de marca", existen otras posibilidades: el último texto stdinno terminó con un '\n'carácter nulo incrustado raro o se leyó.
chux - Restablece a Mónica el

440

Quizás la solución más simple utiliza una de mis funciones favoritas poco conocidas strcspn():

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

Si desea que también maneje '\r'(por ejemplo, si la secuencia es binaria):

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

La función cuenta el número de caracteres hasta que toca a '\r'o a '\n'(en otras palabras, encuentra el primero '\r'o '\n'). Si no golpea nada, se detiene en '\0'(devolviendo la longitud de la cadena).

Tenga en cuenta que esto funciona bien incluso si no hay una nueva línea, porque se strcspndetiene en a '\0'. En ese caso, toda la línea simplemente se reemplaza '\0'con '\0'.


30
Esto incluso se encarga de la rara bufferque comienza con '\0', algo que causa dolor por el buffer[strlen(buffer) - 1] = '\0';enfoque.
chux - Restablece a Mónica

55
@chux: Sí, desearía que más personas lo supieran strcspn(). Una de las funciones más útiles en la biblioteca, IMO. He decidido escribir y publicar un montón de hacks C comunes como este hoy; una strtok_rimplementación usando strcspny strspnfue una de las primeras: codepad.org/2lBkZk0w ( Advertencia: no puedo garantizar que no tenga errores; se escribió apresuradamente y probablemente tenga algunos). Sin embargo, todavía no sé dónde los publicaré, pero tengo la intención de hacerlo en el espíritu de los famosos "trucos de tonterías".
Tim Čas

44
Buscó formas de recortar robustamente . Esta parece ser la única línea correcta. es más rápido, aunque no tan simple. fgets()strcspn()strlen
chux - Restablece a Mónica

66
@sidbushes: La pregunta, tanto en el título como en el contenido, pregunta sobre la nueva línea final desde la fgets()entrada . Que siempre es también la primera línea nueva.
Tim Čas

99
@sidbushes: entiendo de dónde vienes, pero no me hago responsable de los resultados de búsqueda de Google para términos específicos. Habla con Google, no conmigo.
Tim Čas

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

8
Probablemente arrojará una excepción si la cadena está vacía, ¿no? Como índice fuera de rango.
Edward Olamisan

1
@EdwardOlamisan, sin embargo, la cadena nunca estará vacía.
James Morris

55
@James Morris En casos inusuales fgets(buf, size, ....)-> strlen(buf) == 0. 1) se fgets()lee como el primer chara '\0'. 2) size == 13) fgets()devuelve, NULLentonces el bufcontenido puede ser cualquier cosa (Sin embargo, el código de OP prueba NULL) Sugerir:size_t ln = strlen(name); if (ln > 0 && name[ln-1] == '\n') name[--ln] = '\0';
chux - Vuelva a instalar Monica el

2
¿Qué pasa si la cadena está vacía? lnsería -1, salvo por el hecho de size_tque no está firmado, escribiendo así en la memoria aleatoria. Creo que quieres usar ssize_ty comprobar lnes> 0.
Abligh

2
@ legends2k: una búsqueda de un valor de tiempo de compilación (especialmente un valor cero como en strlen) puede implementarse de manera mucho más eficiente que una simple búsqueda char-by-char. Por esta razón, consideraría esta solución mejor que una strchro strcspnbasada.
An

17

A continuación se muestra un enfoque rápido para eliminar un potencial '\n'de una cadena guardada por fgets().
Utiliza strlen(), con 2 pruebas.

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

Ahora use buffery lensegún sea necesario.

Este método tiene el beneficio secundario de un lenvalor para el código posterior. Puede ser fácilmente más rápido que strchr(Name, '\n'). Ref. YMMV, pero ambos métodos funcionan.


buffer, Desde el original, fgets()no contendrá en "\n"bajo algunas circunstancias:
A) La línea era demasiado largo para bufferlo que sólo charprecede a la '\n'se guarda en buffer. Los caracteres no leídos permanecen en la secuencia.
B) La última línea del archivo no terminó con a '\n'.

Si la entrada tiene caracteres nulos incrustados '\0'en alguna parte, la longitud informada por strlen()no incluirá la '\n'ubicación.


Algunos problemas de otras respuestas:

  1. strtok(buffer, "\n");no puede eliminar el '\n'cuando bufferes "\n". De esta respuesta : modificada después de esta respuesta para advertir sobre esta limitación.

  2. Lo siguiente falla en raras ocasiones cuando la primera charlectura fgets()es '\0'. Esto sucede cuando la entrada comienza con un incrustado '\0'. Luego se buffer[len -1]convierte en buffer[SIZE_MAX]acceder a la memoria ciertamente fuera del rango legítimo de buffer. Algo que un hacker puede probar o encontrar al leer tontamente archivos de texto UTF16. Este era el estado de una respuesta cuando se escribió esta respuesta. Más tarde, un no OP lo editó para incluir código como la comprobación de esta respuesta "".

    size_t len = strlen(buffer);
    if (buffer[len - 1] == '\n') {  // FAILS when len == 0
      buffer[len -1] = '\0';
    }
  3. sprintf(buffer,"%s",buffer);es comportamiento indefinido: Ref . Además, no guarda ningún espacio en blanco inicial, de separación o final. Ahora eliminado .

  4. [Editar debido a una buena respuesta posterior ] No hay problemas con el trazador de líneas 1 buffer[strcspn(buffer, "\n")] = 0;aparte del rendimiento en comparación con el strlen()enfoque. El rendimiento en el recorte generalmente no es un problema dado que el código está haciendo E / S, un agujero negro del tiempo de CPU. Si el siguiente código necesita la longitud de la cadena o es altamente consciente del rendimiento, utilice este strlen()enfoque. De lo contrario, strcspn()es una buena alternativa.


Gracias por la útil respuesta. ¿Podemos usar strlen(buffer)cuando el tamaño del búfer se asigna dinámicamente usando malloc?
rrz0

@ Rrz0 buffer = malloc(allocation_size); length = strlen(buffer);es malo: bufferse desconocen los datos en la memoria señalados por . buffer = malloc(allocation_size_4_or_more); strcpy(buffer, "abc"); length = strlen(buffer);está bien
chux - Restablecer Mónica

¡¡gracias por esto!! Estoy tomando un curso de CS y esto fue de gran ayuda para una de las tareas. He acreditado su respuesta en el código fuente.
Nathaniel Hoyt

8

Directo para eliminar el '\ n' de la salida de fgets si cada línea tiene '\ n'

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

De otra manera:

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

1
Tenga en cuenta que sería más seguro usarlo en strnlenlugar de strlen.
Mike Mertsock

3
Un comentario a la primera respuesta en la pregunta vincula los estados "Tenga en cuenta que strlen (), strcmp () y strdup () son seguros. Las alternativas 'n' le brindan funcionalidad adicional".
Étienne

44
@esker no, no lo haría. insertar un nno aumenta mágicamente la seguridad, en este caso, de hecho, haría que el código sea más peligroso. De manera similar con strncpy, una función terriblemente insegura. La publicación que vinculaste es un mal consejo.
MM

3
Esto falla miserablemente para una cadena vacía ( ""). También strlen()devuelve size_tno int.
alk

44
esto no es seguro para una cadena vacía, escribirá en el índice -1. No uses esto.
Jean-François Fabre

3

Para el recorte simple '\ n',

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

para múltiples '\ n' recortes,

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

1
¿Por qué anidar ifcuando simplemente puedes escribir una condición usando &&? Ese whilebucle tiene una estructura extraña; simplemente podría ser while (length > 0 && string[length-1] == '\n') { --length; string[length] = '\0'; }.
melpomene

@melpomene gracias por la sugerencia. Actualiza el código.
BEPP

1
Yo sugeriría que la primera función se define de forma más natural como: size_t length = strlen(string); if (length > 0 && string[length-1] == '\n') { string[length-1] = '\0'; }. Esto también refleja mejor la segunda definición (solo usando en iflugar de while).
melpomene

@elpomene gracias. Que tiene sentido. Actualicé el código.
BEPP

1

My Newbie way ;-) Avísame si es correcto. Parece estar funcionando para todos mis 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;
}

1

Los pasos para eliminar el carácter de nueva línea de la forma quizás más obvia:

  1. Determine la longitud de la cadena dentro NAMEusando strlen(), encabezado string.h. Tenga en cuenta que strlen()no cuenta la terminación \0.
size_t sl = strlen(NAME);

  1. Mire si la cadena comienza con o solo incluye un \0carácter (cadena vacía). En este caso slsería 0porque, strlen()como dije anteriormente, no cuenta \0y se detiene en el primer caso:
if(sl == 0)
{
   // Skip the newline replacement process.
}

  1. Compruebe si el último carácter de la cadena adecuada es un carácter de nueva línea '\n'. Si este es el caso, reemplácelo \ncon a \0. Tenga en cuenta que los recuentos de índices comienzan en, 0así que tendremos que hacer NAME[sl - 1]:
if(NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

Tenga en cuenta que si solo presionó Entrar en la fgets()solicitud de cadena (el contenido de la cadena solo consistía en un carácter de nueva línea), la cadena NAMEserá una cadena vacía a partir de entonces.


  1. Podemos combinar los pasos 2. y 3. juntos en una sola ifdeclaración utilizando el operador lógico &&:
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

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

Si prefiere una función para usar esta técnica manejando fgetscadenas de salida en general sin volver a escribir cada vez, aquí 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';
    }
}

En su ejemplo proporcionado, sería:

printf("Enter your Name: ");

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

Tenga en cuenta que este método no funciona si la cadena de entrada tiene \0s integrados . Si ese fuera el caso strlen(), solo devolvería la cantidad de caracteres hasta el primero \0. Pero este no es un enfoque bastante común, ya que la mayoría de las funciones de lectura de cadenas generalmente se detienen al principio \0y toman la cadena hasta ese carácter nulo.

Aparte de la pregunta por sí solo. Trate de evitar las negaciones dobles que hacen que su unclearer código: if (!(fgets(Name, sizeof Name, stdin) != NULL) {}. Simplemente puedes hacer if (fgets(Name, sizeof Name, stdin) == NULL) {}.


No estoy seguro de por qué querrías hacer esto. El punto de eliminar nuevas líneas no es terminar las cadenas nulo; es para eliminar nuevas líneas. Reemplazar a \npor \0a al final de una cadena es una forma de "eliminar" la nueva línea. Pero reemplazar \ncaracteres dentro de una cadena cambia fundamentalmente la cadena. No es raro tener cadenas con múltiples caracteres intencionales de nueva línea, y esto cortaría efectivamente los extremos de esas cadenas. Para eliminar estas nuevas líneas, el contenido de la matriz debe desplazarse hacia la izquierda para sobrescribir el \n.
ex nihilo

@exnihilo ¿Cómo puede alguien ingresar una cadena con varias líneas nuevas dentro usando fgets()?
RobertS apoya a Monica Cellio el

Bueno, puede concatenar cadenas obtenidas por múltiples llamadas a fgets(). Pero no entiendo su objeción: usted es el único que propone el código para manejar múltiples líneas nuevas.
ex nihilo

@exnihilo Tienes razón, pensaré demasiado en la estrategia. Solo quería agregar una forma muy dura pero posible de obtener el resultado deseado.
RobertS apoya a Monica Cellio el

@exnihilo Edité mi respuesta completamente y seguí el enfoque principal usando strlenetc. Justificación para no ser un duplicado: 1. Explicación del código por pasos. 2. Proporcionado como función y solución basada en el contexto. 3. Sugerencia para evitar expresiones de doble negación.
RobertS apoya a Monica Cellio el

0

Tim Čas one liner es increíble para las cadenas obtenidas por una llamada a fgets, porque sabes que contienen una nueva línea al final.

Si se encuentra en un contexto diferente y desea manejar cadenas que pueden contener más de una nueva línea, es posible que esté buscando strrspn. No es POSIX, lo que significa que no lo encontrará en todos los Unices. Escribí uno para mis propias necesidades.

/* 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 aquellos que buscan un equivalente de Perl chomp en C, creo que esto es todo (chomp solo elimina la nueva línea final).

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

La función 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
"porque sabes que contienen una nueva línea al final". -> Incluso funciona cuando no hay '\n'(o si la cadena es "").
chux - Restablece a Monica el

En respuesta a su primer comentario chux, mi respuesta lo conserva. Tuve que tirar resetlen strrcspnpara cuando no hay \n.
Philippe A.

¿Por qué usar en goto end;lugar de return len;?
chqrlie

@chqrlie Necesitaba salir de este elegante ciclo de 2 niveles en el que me metí. El daño ya estaba hecho. ¿Por qué no un goto?
Philippe A.

Tiene dos tipos de correos gotoelectrónicos en su código: uno inútil gotoque puede reemplazarse por una returndeclaración y uno al revés gotoque se considera malvado. El uso strchrayuda a implementar strrspny strrcspnde una manera más simple: 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; }ysize_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

Si usar getlinees una opción, sin descuidar sus problemas de seguridad y si desea colocar punteros, puede evitar las funciones de cadena ya que getlinedevuelve el número de caracteres. Algo como abajo

#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 : Sin embargo, los [ problemas de seguridad ] con getlineno deben descuidarse.


-1

La siguiente función es parte de la biblioteca de procesamiento de cadenas que mantengo en Github. Elimina los caracteres no deseados de una cadena, exactamente lo que quieres

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

Un ejemplo de uso podría 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

Es posible que desee comprobar otras funciones disponibles, o incluso contribuir al proyecto :) https://github.com/fnoyanisi/zString


Debe eliminar el *in *src++;y make bad, tokeny d const char *. Además, ¿por qué no usar en strchrlugar de zChrSearch? *srcno puede estar '\0'en tu zStrrmvfunción.
chqrlie

Gracias @chqrlie! actualicé el código para reflejar sus sugerencias ..... zstring comenzó como un proyecto divertido con el objetivo de crear una biblioteca de manipulación de cadenas sin usar ninguna función de biblioteca estándar, por lo tanto, no strchr
usé

1
Escribir una " biblioteca de manipulación de cadenas sin usar ninguna función de biblioteca estándar " es un buen ejercicio, pero ¿por qué decirles a otras personas que lo usen? En todo caso, será más lento y menos probado que cualquier biblioteca estándar.
melpomene

Esto está haciendo un trabajo diferente al de la pregunta. Probablemente se pueda usar para deshacerse de la única línea nueva, pero se siente como una exageración.
Jonathan Leffler

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

Usted debe darle una oportunidad. Este código básicamente recorre la cadena hasta que encuentra el '\ n'. Cuando se encuentre, '\ n' será reemplazado por el terminador de caracteres nulos '\ 0'

Tenga en cuenta que está comparando caracteres y no cadenas en esta línea, entonces no hay necesidad de usar strcmp ():

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

ya que usará comillas simples y no comillas dobles. Aquí hay un enlace sobre comillas simples vs dobles si quieres saber más


2
Sería mejor si explicas y editas el formato de tu código.
Anh Pham

Por lo general, es mejor explicar una solución en lugar de solo publicar algunas filas de código anónimo. Puede leer Cómo escribo una buena respuesta , y también Explicar respuestas completamente basadas en código .
Massimiliano Kraus

1
Lo siento, esta fue mi primera contribución aquí. Yo lo arreglare. Gracias por los comentarios
Matheus Martins Jerônimo

3
Ineficiente: for(int i = 0; i < strlen(Name); i++ )llamará strlen(Name)muchas veces (cambios de bucle Name[]), por lo que con una longitud N, esta es una O(N*N)solución. Solo strlen(Name)se necesita 1 llamada a , si la hay, para proporcionar una solución O (N) `. No int iestá claro por qué se usa en lugar de size_t i. Considerefor(size_t i = 0; i < Name[i]; i++ )
chux

@chux Más bienfor (size_t i = 0; Name[i]; i++) { if (Name[i] == '\n') { Name[i] = '\0'; break; } }
melpomene

-1

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

1
len = strlen(str)puede desbordarse: strlenretornos size_t, no int. ¿Qué pasa con los extraños if (len>0) if (...)condicionales? ¿No lo sabes &&? Si va a eliminar varias instancias finales de CR / LF, ¿por qué limitarse a 5? ¿Por qué no eliminarlos a todos? ¿Por qué la función tiene un inttipo de retorno cuando siempre regresa 0? ¿Por qué no solo volver void?
melpomene
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.