¿Cómo recorte los espacios en blanco iniciales/posteriores de forma estándar?

5 minutos de lectura

avatar de usuario
coledoto

¿Existe un método limpio, preferiblemente estándar, para recortar los espacios en blanco iniciales y finales de una cadena en C? Haría el mío, pero creo que este es un problema común con una solución igualmente común.

Si, y SOLO SI, solo hay un bloque de texto contiguo entre espacios en blanco, puede usar una sola llamada para strtok(3)al igual que:

char *trimmed = strtok(input, "\r\t\n ");

Esto funciona para cadenas como las siguientes:

"   +1.123.456.7890 "
" 01-01-2020\n"
"\t2.523"

Esto no funcionará para cadenas que contienen espacios en blanco entre bloques que no son espacios en blanco, como " hi there ". Probablemente sea mejor evitar este enfoque, pero ahora está aquí en su caja de herramientas si lo necesita.

  • deberías lanzar el char argumento a isspace() para (unsigned char) para evitar un comportamiento indefinido en valores potencialmente negativos. También evite mover la cuerda si en ltrim() si no es necesario.

    – chqrlie

    8 oct 2016 a las 13:34

avatar de usuario
chux – Reincorporar a Monica

Tarde a la fiesta de recortes

Características:
1. Recorte el comienzo rápidamente, como en varias otras respuestas.
2. Después de llegar al final, recortar el derecho con solo 1 prueba por bucle. Como @ jfm3, pero funciona para una cadena de espacios en blanco)
3. Para evitar un comportamiento indefinido cuando char es un firmado charemitir *s para unsigned char.

Manejo de personajes “En todos los casos el argumento es un intcuyo valor será representable como un unsigned char o será igual al valor de la macro EOF. Si el argumento tiene cualquier otro valor, el comportamiento no está definido”. C11 §7.4 1

#include <ctype.h>

// Return a pointer to the trimmed string
char *string_trim_inplace(char *s) {
  while (isspace((unsigned char) *s)) s++;
  if (*s) {
    char *p = s;
    while (*p) p++;
    while (isspace((unsigned char) *(--p)));
    p[1] = '\0';
  }

  // If desired, shift the trimmed string

  return s;
}

@chqrlie comentó que lo anterior no cambia la cadena recortada. Para hacerlo….

// Return a pointer to the (shifted) trimmed string
char *string_trim_inplace(char *s) {
  char *original = s;
  size_t len = 0;

  while (isspace((unsigned char) *s)) {
    s++;
  } 
  if (*s) {
    char *p = s;
    while (*p) p++;
    while (isspace((unsigned char) *(--p)));
    p[1] = '\0';
    // len = (size_t) (p - s);   // older errant code
    len = (size_t) (p - s + 1);  // Thanks to @theriver
  }

  return (s == original) ? s : memmove(original, s, len + 1);
}

  • Yay, finalmente alguien que conoce el comportamiento indefinido de ctype.

    – Roland Illig

    18 de septiembre de 2016 a las 2:21

Aquí hay uno que cambia la cadena a la primera posición de su búfer. Es posible que desee este comportamiento para que, si asignó dinámicamente la cadena, aún pueda liberarla en el mismo puntero que devuelve trim():

char *trim(char *str)
{
    size_t len = 0;
    char *frontp = str;
    char *endp = NULL;

    if( str == NULL ) { return NULL; }
    if( str[0] == '\0' ) { return str; }

    len = strlen(str);
    endp = str + len;

    /* Move the front and back pointers to address the first non-whitespace
     * characters from each end.
     */
    while( isspace((unsigned char) *frontp) ) { ++frontp; }
    if( endp != frontp )
    {
        while( isspace((unsigned char) *(--endp)) && endp != frontp ) {}
    }

    if( frontp != str && endp == frontp )
            *str="\0";
    else if( str + len - 1 != endp )
            *(endp + 1) = '\0';

    /* Shift the string so that it starts at str so that if it's dynamically
     * allocated, we can still free it on the returned pointer.  Note the reuse
     * of endp to mean the front of the string buffer now.
     */
    endp = str;
    if( frontp != str )
    {
            while( *frontp ) { *endp++ = *frontp++; }
            *endp = '\0';
    }

    return str;
}

Prueba de corrección:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

/* Paste function from above here. */

int main()
{
    /* The test prints the following:
    [nothing to trim] -> [nothing to trim]
    [    trim the front] -> [trim the front]
    [trim the back     ] -> [trim the back]
    [    trim front and back     ] -> [trim front and back]
    [ trim one char front and back ] -> [trim one char front and back]
    [ trim one char front] -> [trim one char front]
    [trim one char back ] -> [trim one char back]
    [                   ] -> []
    [ ] -> []
    [a] -> [a]
    [] -> []
    */

    char *sample_strings[] =
    {
            "nothing to trim",
            "    trim the front",
            "trim the back     ",
            "    trim front and back     ",
            " trim one char front and back ",
            " trim one char front",
            "trim one char back ",
            "                   ",
            " ",
            "a",
            "",
            NULL
    };
    char test_buffer[64];
    char comparison_buffer[64];
    size_t index, compare_pos;

    for( index = 0; sample_strings[index] != NULL; ++index )
    {
        // Fill buffer with known value to verify we do not write past the end of the string.
        memset( test_buffer, 0xCC, sizeof(test_buffer) );
        strcpy( test_buffer, sample_strings[index] );
        memcpy( comparison_buffer, test_buffer, sizeof(comparison_buffer));

        printf("[%s] -> [%s]\n", sample_strings[index],
                                 trim(test_buffer));

        for( compare_pos = strlen(comparison_buffer);
             compare_pos < sizeof(comparison_buffer);
             ++compare_pos )
        {
            if( test_buffer[compare_pos] != comparison_buffer[compare_pos] )
            {
                printf("Unexpected change to buffer @ index %u: %02x (expected %02x)\n",
                    compare_pos, (unsigned char) test_buffer[compare_pos], (unsigned char) comparison_buffer[compare_pos]);
            }
        }
    }

    return 0;
}

El archivo fuente era trim.c. Compilado con ‘cc -Wall trim.c -o trim’.

  • Yay, finalmente alguien que conoce el comportamiento indefinido de ctype.

    – Roland Illig

    18 de septiembre de 2016 a las 2:21

Aquí está mi intento de una función de recorte en el lugar simple pero correcta.

void trim(char *str)
{
    int i;
    int begin = 0;
    int end = strlen(str) - 1;

    while (isspace((unsigned char) str[begin]))
        begin++;

    while ((end >= begin) && isspace((unsigned char) str[end]))
        end--;

    // Shift all characters back to the start of the string array.
    for (i = begin; i <= end; i++)
        str[i - begin] = str[i];

    str[i - begin] = '\0'; // Null terminate string.
}

  • Sugerir cambiar a while ((end >= begin) && isspace(str[end])) para prevenir UB cuando str is “”. Prevents calle[-1]`.

    – chux – Reincorporar a Monica

    6 de noviembre de 2013 a las 16:43

  • Por cierto, tengo que cambiar esto a str[i – begin + 1] para que funcione

    – truongnm

    9 de septiembre de 2016 a las 8:53

  • Tienes que lanzar el argumento para isspace para unsigned charde lo contrario, invoca un comportamiento indefinido.

    – Roland Illig

    18 de septiembre de 2016 a las 2:00

  • @RolandIllig, ¿por qué sería un comportamiento indefinido? La función está diseñada para trabajar con caracteres.

    – wovano

    5 de marzo de 2020 a las 23:32

  • @wovano No, no lo es. Las funciones de <ctype.h> están destinados a trabajar con enteros, que representan unsigned char o el valor especial EOF. Consulte stackoverflow.com/q/7131026/225757.

    – Roland Illig

    5 de marzo de 2020 a las 23:43

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad