Como aparo os espaços em branco iniciais / finais de uma maneira padrão?

177

Existe um método limpo, de preferência padrão, de aparar espaços em branco à esquerda e à direita de uma string em C? Eu lançaria o meu, mas acho que esse é um problema comum com uma solução igualmente comum.

coledot
fonte

Respostas:

164

Se você pode modificar a sequência:

// Note: This function returns a pointer to a substring of the original string.
// If the given string was allocated dynamically, the caller must not overwrite
// that pointer with the returned value, since the original pointer must be
// deallocated using the same allocator with which it was allocated.  The return
// value must NOT be deallocated using free() etc.
char *trimwhitespace(char *str)
{
  char *end;

  // Trim leading space
  while(isspace((unsigned char)*str)) str++;

  if(*str == 0)  // All spaces?
    return str;

  // Trim trailing space
  end = str + strlen(str) - 1;
  while(end > str && isspace((unsigned char)*end)) end--;

  // Write new null terminator character
  end[1] = '\0';

  return str;
}

Se você não pode modificar a string, pode usar basicamente o mesmo método:

// Stores the trimmed input string into the given output buffer, which must be
// large enough to store the result.  If it is too small, the output is
// truncated.
size_t trimwhitespace(char *out, size_t len, const char *str)
{
  if(len == 0)
    return 0;

  const char *end;
  size_t out_size;

  // Trim leading space
  while(isspace((unsigned char)*str)) str++;

  if(*str == 0)  // All spaces?
  {
    *out = 0;
    return 1;
  }

  // Trim trailing space
  end = str + strlen(str) - 1;
  while(end > str && isspace((unsigned char)*end)) end--;
  end++;

  // Set output size to minimum of trimmed string length and buffer size minus 1
  out_size = (end - str) < len-1 ? (end - str) : len-1;

  // Copy trimmed string and add null terminator
  memcpy(out, str, out_size);
  out[out_size] = 0;

  return out_size;
}
Adam Rosenfield
fonte
6
Desculpe, a primeira resposta não é boa, a menos que você não se importe com vazamentos de memória. Agora você tem duas cordas sobrepostas (a original, que possui seus espaços finais aparados, e a nova). Somente a cadeia original pode ser liberada, mas, se o fizer, a segunda aponta para a memória liberada.
David Nehme 06/11
7
@ nvl: Não há memória sendo alocada, portanto não há memória para liberar.
Adam Rosenfield
15
@nvl: No. stré uma variável local e sua alteração não altera o ponteiro original que está sendo passado. As chamadas de função em C são sempre passadas por valor, nunca passadas por referência.
Adam Rosenfield
11
@Raj: Não há nada inerentemente errado em retornar um endereço diferente daquele que foi passado. Não é necessário aqui que o valor retornado seja um argumento válido da free()função. Muito pelo contrário - projetei isso para evitar a necessidade de alocação de memória para obter eficiência. Se o endereço passado foi alocado dinamicamente, o responsável pela chamada ainda é responsável por liberar essa memória e o responsável pela chamada precisa garantir que não substitua esse valor pelo valor retornado aqui.
Adam Rosenfield
3
Você tem que lançar o argumento para isspacea unsigned char, caso contrário, você invocar um comportamento indefinido.
Roland Illig 18/09/16
37

Aqui está um que muda a string para a primeira posição do seu buffer. Você pode querer esse comportamento para que, se você alocou dinamicamente a sequência, ainda pode liberá-la no mesmo ponteiro que trim () retorna:

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

Teste de correção:

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

O arquivo de origem foi trim.c. Compilado com 'cc -Wall trim.c -o trim'.

indiv
fonte
2
Você tem que lançar o argumento para isspacea unsigned char, caso contrário, você invocar um comportamento indefinido.
Roland Illig 18/09/16
@RolandIllig: Obrigado, nunca percebi que isso era necessário. Corrigido.
indiv 19/09/16
@ Simas: Por que você diz isso? A função chama isspace()então por que haveria uma diferença entre " "e "\n"? Eu adicionei testes de unidade para novas linhas e parece OK para mim ... ideone.com/bbVmqo
indiv 17/10/16
1
@indiv acessará o bloco de memória inválido quando alocado manualmente. Ou seja, esta linha: *(endp + 1) = '\0';. O teste de exemplo na resposta usa um buffer de 64 que evita esse problema.
Simas
1
@nolandda: Obrigado pelos detalhes. Corrigi-o e atualizei o teste para detectar a saturação do buffer, pois não tenho acesso ao valgrind no momento.
indiv 26/07/19
23

Minha solução A sequência deve ser alterável. A vantagem, acima de algumas das outras soluções, de que ela move a parte não espacial para o início, para que você possa continuar usando o ponteiro antigo, caso precise liberá-lo () mais tarde.

void trim(char * s) {
    char * p = s;
    int l = strlen(p);

    while(isspace(p[l - 1])) p[--l] = 0;
    while(* p && isspace(* p)) ++p, --l;

    memmove(s, p, l + 1);
}   

Esta versão cria uma cópia da string com strndup () em vez de editá-la no lugar. strndup () requer _GNU_SOURCE, então talvez você precise criar seu próprio strndup () com malloc () e strncpy ().

char * trim(char * s) {
    int l = strlen(s);

    while(isspace(s[l - 1])) --l;
    while(* s && isspace(* s)) ++s, --l;

    return strndup(s, l);
}
jkramer
fonte
4
trim()chama UB se sfor ""como isspace()seria a primeira chamada isspace(p[-1])e p[-1]não necessariamente faz referência a um local legal.
chux - Restabelece Monica
1
Você tem que lançar o argumento para isspacea unsigned char, caso contrário, você invocar um comportamento indefinido.
Roland Illig 18/09/16
1
deve adicionar if(l==0)return;para evitar comprimento zero str
ch271828n 5/03/19
11

Aqui está minha mini biblioteca C para aparar à esquerda, direita, ambos, todos, no lugar e separados, e aparar um conjunto de caracteres especificados (ou espaço em branco por padrão).

conteúdo de strlib.h:

#ifndef STRLIB_H_
#define STRLIB_H_ 1
enum strtrim_mode_t {
    STRLIB_MODE_ALL       = 0, 
    STRLIB_MODE_RIGHT     = 0x01, 
    STRLIB_MODE_LEFT      = 0x02, 
    STRLIB_MODE_BOTH      = 0x03
};

char *strcpytrim(char *d, // destination
                 char *s, // source
                 int mode,
                 char *delim
                 );

char *strtriml(char *d, char *s);
char *strtrimr(char *d, char *s);
char *strtrim(char *d, char *s); 
char *strkill(char *d, char *s);

char *triml(char *s);
char *trimr(char *s);
char *trim(char *s);
char *kill(char *s);
#endif

conteúdo de strlib.c:

#include <strlib.h>

char *strcpytrim(char *d, // destination
                 char *s, // source
                 int mode,
                 char *delim
                 ) {
    char *o = d; // save orig
    char *e = 0; // end space ptr.
    char dtab[256] = {0};
    if (!s || !d) return 0;

    if (!delim) delim = " \t\n\f";
    while (*delim) 
        dtab[*delim++] = 1;

    while ( (*d = *s++) != 0 ) { 
        if (!dtab[0xFF & (unsigned int)*d]) { // Not a match char
            e = 0;       // Reset end pointer
        } else {
            if (!e) e = d;  // Found first match.

            if ( mode == STRLIB_MODE_ALL || ((mode != STRLIB_MODE_RIGHT) && (d == o)) ) 
                continue;
        }
        d++;
    }
    if (mode != STRLIB_MODE_LEFT && e) { // for everything but trim_left, delete trailing matches.
        *e = 0;
    }
    return o;
}

// perhaps these could be inlined in strlib.h
char *strtriml(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_LEFT, 0); }
char *strtrimr(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_RIGHT, 0); }
char *strtrim(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_BOTH, 0); }
char *strkill(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_ALL, 0); }

char *triml(char *s) { return strcpytrim(s, s, STRLIB_MODE_LEFT, 0); }
char *trimr(char *s) { return strcpytrim(s, s, STRLIB_MODE_RIGHT, 0); }
char *trim(char *s) { return strcpytrim(s, s, STRLIB_MODE_BOTH, 0); }
char *kill(char *s) { return strcpytrim(s, s, STRLIB_MODE_ALL, 0); }

A única rotina principal faz tudo. Ele apara se src == dst , caso contrário, funciona como as strcpyrotinas. Apara um conjunto de caracteres especificado no delim da stringou espaço em branco se nulo. Apara esquerda, direita, ambos e tudo (como tr). Não há muito e itera sobre a string apenas uma vez. Algumas pessoas podem reclamar que o corte à direita começa à esquerda, no entanto, não é necessário nenhum strlen que comece à esquerda. (De uma forma ou de outra, você precisa chegar ao final da sequência para obter as guarnições corretas, para que você possa fazer o trabalho à medida que for avançando.) . Como a solução funciona da esquerda para a direita e itera apenas uma vez, ela pode ser expandida para funcionar também em fluxos. Limitações: não funciona em cadeias unicode .

Atira a Lua
fonte
2
Votei isso de novo e sei que é velho, mas acho que há um bug. dtab[*d]não é convertido *dpara unsigned intantes de usá-lo como um índice de matriz. Em um sistema com char assinado, será lido o dtab[-127]que causará bugs e possivelmente travará.
Zan Lynx
2
Comportamento indefinido em potencial dtab[*delim++]porque os charvalores do índice devem ser convertidos em unsigned char. O código assume 8 bits char. delimdeve ser declarado como const char *. dtab[0xFF & (unsigned int)*d]seria mais claro como dtab[(unsigned char)*d]. O código funciona em cadeias codificadas em UTF-8, mas não remove seqüências de espaçamento não ASCII.
chqrlie
@ Michael-plainer, isso parece interessante. Por que você não o testa e coloca no GitHub?
Daisuke Aramaki
9

Aqui está minha tentativa de uma função de ajuste simples, mas correta no local.

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.
}
suíço
fonte
2
Sugira a alteração para while ((end >= begin) && isspace(str[end]))evitar UB quando str is "" . Prevents str [-1] `.
chux - Restabelece Monica
Btw, eu tenho que mudar isso para str [i - começam + 1], a fim de obras
truongnm
1
Você tem que lançar o argumento para isspacea unsigned char, caso contrário, você invocar um comportamento indefinido.
Roland Illig 18/09/16
@RolandIllig, por que seria um comportamento indefinido? A função destina-se a trabalhar com caracteres.
wovano 5/03
@wovano Não, não é. As funções de <ctype.h>destinam-se a trabalhar com ints, que representam um unsigned charou o valor especial EOF. Consulte stackoverflow.com/q/7131026/225757 .
Roland Illig 5/03
8

Tarde para a festa

Características:
1. Apare o início rapidamente, como em várias outras respostas.
2. Depois de ir para o final, corte a direita com apenas 1 teste por loop. Como @ jfm3, mas funciona para uma sequência de espaços em branco)
3. Para evitar um comportamento indefinido quando charé assinado char, faça a conversão *spara unsigned char.

Manipulação de caracteres "Em todos os casos, o argumento é um int, cujo valor deve ser representável como unsigned charou deve ser igual ao valor da macro EOF. Se o argumento tiver outro valor, o comportamento será indefinido." 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 comentou que o item acima não altera a sequência aparada. Para fazer isso ....

// 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);
}
chux - Restabelecer Monica
fonte
3
Bem, finalmente alguém que conhece o comportamento indefinido do ctype.
Roland Illig 18/09/16
2
@chux Eu acho que deveria ser len = (size_t) (ps) +1; caso contrário, a última letra se sobrepõe.
theriver
4

Aqui está uma solução semelhante à rotina de modificação no local @ adam-rosenfields, mas sem recorrer desnecessariamente ao strlen (). Como @jkramer, a string é ajustada à esquerda no buffer para que você possa liberar o mesmo ponteiro. Não é ideal para cadeias grandes, pois não usa memmove. Inclui os operadores ++ / - mencionados pelo @ jfm3. Testes de unidade baseados em FCTX incluídos.

#include <ctype.h>

void trim(char * const a)
{
    char *p = a, *q = a;
    while (isspace(*q))            ++q;
    while (*q)                     *p++ = *q++;
    *p = '\0';
    while (p > a && isspace(*--p)) *p = '\0';
}

/* See http://fctx.wildbearsoftware.com/ */
#include "fct.h"

FCT_BGN()
{
    FCT_QTEST_BGN(trim)
    {
        { char s[] = "";      trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "   ";   trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "\t";    trim(s); fct_chk_eq_str("",    s); } // Trivial
        { char s[] = "a";     trim(s); fct_chk_eq_str("a",   s); } // NOP
        { char s[] = "abc";   trim(s); fct_chk_eq_str("abc", s); } // NOP
        { char s[] = "  a";   trim(s); fct_chk_eq_str("a",   s); } // Leading
        { char s[] = "  a c"; trim(s); fct_chk_eq_str("a c", s); } // Leading
        { char s[] = "a  ";   trim(s); fct_chk_eq_str("a",   s); } // Trailing
        { char s[] = "a c  "; trim(s); fct_chk_eq_str("a c", s); } // Trailing
        { char s[] = " a ";   trim(s); fct_chk_eq_str("a",   s); } // Both
        { char s[] = " a c "; trim(s); fct_chk_eq_str("a c", s); } // Both

        // Villemoes pointed out an edge case that corrupted memory.  Thank you.
        // http://stackoverflow.com/questions/122616/#comment23332594_4505533
        {
          char s[] = "a     ";       // Buffer with whitespace before s + 2
          trim(s + 2);               // Trim "    " containing only whitespace
          fct_chk_eq_str("", s + 2); // Ensure correct result from the trim
          fct_chk_eq_str("a ", s);   // Ensure preceding buffer not mutated
        }

        // doukremt suggested I investigate this test case but
        // did not indicate the specific behavior that was objectionable.
        // http://stackoverflow.com/posts/comments/33571430
        {
          char s[] = "         foobar";  // Shifted across whitespace
          trim(s);                       // Trim
          fct_chk_eq_str("foobar", s);   // Leading string is correct

          // Here is what the algorithm produces:
          char r[16] = { 'f', 'o', 'o', 'b', 'a', 'r', '\0', ' ',                     
                         ' ', 'f', 'o', 'o', 'b', 'a', 'r', '\0'};
          fct_chk_eq_int(0, memcmp(s, r, sizeof(s)));
        }
    }
    FCT_QTEST_END();
}
FCT_END();
Rhys Ulerich
fonte
Esta solução é absolutamente perigosa! Se a sequência original não contiver caracteres que não sejam espaços em branco, a última linha de corte substituirá felizmente o que precede a, se esses bytes contiverem bytes em 'espaço em branco'. Compile isso sem otimizações e veja o que acontece com y: unsigned x = 0x20202020; char s [4] = ""; y não assinado = 0x20202020; printf ("& x, & s, & y =% p,% p,% p \ n", & x, & s, & y); printf ("x, [s], y =% 08x, [% s],% 08x \ n", x, s, y); trim_whitespace (s); printf ("x, [s], y =% 08x, [% s],% 08x \ n", x, s, y);
Villemoes
@ Villemoes, obrigado pelo relatório de erros. Atualizei a lógica para evitar sair do lado esquerdo do buffer quando a string contém apenas espaço em branco. Esta nova versão atende às suas preocupações?
Rhys Ulerich
Os advogados de idiomas provavelmente gritariam com você pelo mero pensamento de especular sobre a criação de um ponteiro para o caractere que precede o que 'a' aponta (que é o que seu '--p' fará). No mundo real, você provavelmente está bem. Mas você também pode simplesmente mudar '> =' para '>' e mover o decremento de p para 'isspace (* - p)'.
Villemoes
Acho que os advogados ficariam bem, pois é apenas comparar um endereço sem tocá-lo, mas também gosto da sua sugestão sobre o decréscimo. Eu atualizei de acordo. Obrigado.
Rhys Ulerich
1
doukremt, é sua preocupação que todo o buffer após foobar não seja preenchido com zeros? Nesse caso, seria um pouco mais útil se você dissesse isso explicitamente, em vez de atirar pedras vagas.
Rhys Ulerich 02/03/2014
3

Outro, com uma linha fazendo o trabalho real:

#include <stdio.h>

int main()
{
   const char *target = "   haha   ";
   char buf[256];
   sscanf(target, "%s", buf); // Trimming on both sides occurs here
   printf("<%s>\n", buf);
}
Daniel
fonte
1
Boa ideia usar scanf; mas ele só funcionará com uma única palavra que pode não ser o que o OP queria (por exemplo, aparar "abc" provavelmente deve resultar em "ab c", enquanto o seu scanf único resulta em "a"). Então, precisamos de um loop e um contador para os caracteres ignorados com o %nespecificador de conversão, e no final é apenas mais simples fazê-lo manualmente, receio.
Peter - Restabelece Monica
Muito útil quando você deseja que a primeira palavra da string desconsidere qualquer espaço inicial.
J ... S
3

Não gostei da maioria dessas respostas porque elas fizeram um ou mais dos seguintes ...

  1. Retornou um ponteiro diferente dentro da cadeia do ponteiro original (uma dor ao manipular dois ponteiros diferentes para a mesma coisa).
  2. Uso gratuito de coisas como strlen () que pré-iteram toda a string.
  3. Utilizou funções lib específicas do SO não portáveis.
  4. Backscanned.
  5. Comparação usada para '' em vez de isspace () para que TAB / CR / LF sejam preservados.
  6. Memória desperdiçada com grandes buffers estáticos.
  7. Ciclos desperdiçados com funções de alto custo como sscanf / sprintf .

Aqui está a minha versão:

void fnStrTrimInPlace(char *szWrite) {

    const char *szWriteOrig = szWrite;
    char       *szLastSpace = szWrite, *szRead = szWrite;
    int        bNotSpace;

    // SHIFT STRING, STARTING AT FIRST NON-SPACE CHAR, LEFTMOST
    while( *szRead != '\0' ) {

        bNotSpace = !isspace((unsigned char)(*szRead));

        if( (szWrite != szWriteOrig) || bNotSpace ) {

            *szWrite = *szRead;
            szWrite++;

            // TRACK POINTER TO LAST NON-SPACE
            if( bNotSpace )
                szLastSpace = szWrite;
        }

        szRead++;
    }

    // TERMINATE AFTER LAST NON-SPACE (OR BEGINNING IF THERE WAS NO NON-SPACE)
    *szLastSpace = '\0';
}
Jason Stewart
fonte
2
Você tem que lançar o argumento para isspacea unsigned char, caso contrário, você invocar um comportamento indefinido.
Roland Illig 18/09/16
Como esta resposta está preocupada com "Ciclos desperdiçados", observe que o código copia desnecessariamente toda a picada quando não há espaço. Uma liderança while (isspace((unsigned char) *szWrite)) szWrite++;impediria isso. O código também copia todo o espaço em branco à direita.
chux - Restabelece Monica
@chux, essa implementação muda no local com ponteiros de leitura e gravação separados (em vez de retornar um novo ponteiro em um local diferente); portanto, a sugestão de pular o szWrite para o primeiro não espaço na linha um deixaria o espaço principal em a sequência original.
Jason Stewart
@chux, você está certo de que ele copia o espaço em branco à direita (antes de adicionar um nulo após o último caractere não-espaço), mas esse foi o preço que escolhi pagar para evitar a verificação prévia da string. Para quantidades modestas de WS à direita, é mais barato copiar os bytes do que pré-varrer toda a cadeia de caracteres para o último caractere não WS. Para grandes quantidades de WS à direita, a pré-varredura provavelmente valeria a redução nas gravações.
Jason Stewart
@chux, para a situação "cópias quando não há espaço", apenas o desempenho *szWrite = *szReadquando os ponteiros não são iguais ignoraria as gravações nesse caso, mas adicionamos outra comparação / ramificação. Com a CPU / MMU / BP moderna, não tenho idéia se essa verificação seria uma perda ou um ganho. Com processadores e arquiteturas de memória mais simples, é mais barato fazer a cópia e pular a comparação.
Jason Stewart
2

Muito tarde para a festa ...

Solução de varredura direta de passagem única sem retorno. Cada caractere na cadeia de origem é testado exatamente uma vez duas vezes. (Portanto, deve ser mais rápido que a maioria das outras soluções aqui, especialmente se a cadeia de origem tiver muitos espaços à direita.)

Isso inclui duas soluções, uma para copiar e aparar uma sequência de origem em outra sequência de destino e a outra para aparar a sequência de origem no local. Ambas as funções usam o mesmo código.

A sequência (modificável) é movida no local, portanto, o ponteiro original permanece inalterado.

#include <stddef.h>
#include <ctype.h>

char * trim2(char *d, const char *s)
{
    // Sanity checks
    if (s == NULL  ||  d == NULL)
        return NULL;

    // Skip leading spaces        
    const unsigned char * p = (const unsigned char *)s;
    while (isspace(*p))
        p++;

    // Copy the string
    unsigned char * dst = (unsigned char *)d;   // d and s can be the same
    unsigned char * end = dst;
    while (*p != '\0')
    {
        if (!isspace(*dst++ = *p++))
            end = dst;
    }

    // Truncate trailing spaces
    *end = '\0';
    return d;
}

char * trim(char *s)
{
    return trim2(s, s);
}
David R Tribble
fonte
1
Cada caractere na cadeia de origem é testado exatamente uma vez : na verdade, a maioria dos caracteres na cadeia de origem é testada duas vezes: comparada '\0'e testada com isspace(). Parece um desperdício testar todos os personagens isspace(). O retorno do final da sequência deve ser mais eficiente para casos não patológicos.
chqrlie
@chqrlie - Sim, cada personagem é testado duas vezes. Eu gostaria de ver esse código realmente testado, especialmente considerando seqüências de caracteres com muitos espaços à direita, em comparação com outros algoritmos aqui.
David R Tribble
trim()ESTÁ BEM. Caixa de canto: trim2(char *d, const char *s)apresenta problemas ao se d,ssobrepor e s < d.
chux - Restabelece Monica
@chux - Nesse caso de canto, como deve trim()se comportar? Você está pedindo para aparar e copiar uma sequência na memória ocupada pela própria sequência. Ao contrário memmove(), isso requer determinar o comprimento da cadeia de origem antes de fazer o corte em si, o que requer a varredura de toda a cadeia por um tempo adicional. Melhor escrever uma rtrim2()função diferente que saiba copiar a fonte para o destino de trás para frente e provavelmente use um argumento adicional para o comprimento da string da fonte.
David R Tribble
1

Não sei ao certo o que você considera "indolor".

C strings são bastante dolorosas. Podemos encontrar a primeira posição de caractere sem espaço em branco trivialmente:

while (isspace (* p)) p ++;

Podemos encontrar a última posição de caractere não-espaço em branco com dois movimentos triviais semelhantes:

enquanto (* q) q ++;
faça {q--; } while (isspace (* q));

(Poupei o trabalho de usar os operadores *e ++ao mesmo tempo.)

A questão agora é o que você faz com isso? O tipo de dados em questão não é realmente um grande resumo robusto Stringe fácil de se pensar, mas, na verdade, quase nada além de uma matriz de bytes de armazenamento. Na falta de um tipo de dados robusto, é impossível escrever uma função que faça o mesmo que a chompfunção do PHperytonby . O que essa função em C retornaria?

jfm3
fonte
Isso funciona bem, a menos que a string seja composta de todos os espaços em branco. Precisa de uma verificação única antes do { q--; } ...de saber *q != 0.
chux - Restabelece Monica
1

Use uma biblioteca de cadeias , por exemplo:

Ustr *s1 = USTR1(\7, " 12345 ");

ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));

... como você diz que esse é um problema "comum", sim, você precisa incluir um #include ou não, e ele não está incluído na libc, mas não invente seu próprio trabalho de hack, armazenando ponteiros aleatórios e size_t, que só levam a estouros de buffer.

James Antill
fonte
1

Apenas para continuar crescendo, mais uma opção com uma string modificável:

void trimString(char *string)
{
    size_t i = 0, j = strlen(string);
    while (j > 0 && isspace((unsigned char)string[j - 1])) string[--j] = '\0';
    while (isspace((unsigned char)string[i])) i++;
    if (i > 0) memmove(string, string + i, j - i + 1);
}
wallek876
fonte
1
strlen()retorna um size_tque pode exceder o intervalo de int. espaço em branco não é restrito ao caractere de espaço. Finalmente, mas mais importante: Comportamento indefinido ativado strcpy(string, string + i * sizeof(char));porque as matrizes de origem e destino se sobrepõem. Use em memmove()vez de strcpy().
chqrlie
@chqrlie você está certo, apenas incluiu suas sugestões. Entendo que copiar quando a origem e o destino se sobrepõem pode causar um comportamento indefinido, mas quero ressaltar que, nesse caso em particular, isso não deve causar nenhum problema, pois sempre vamos copiar de uma posição posterior da memória para o início, Obrigado pelo feedback.
wallek876
1
não importa como as matrizes de origem e destino se sobrepõem, é um comportamento indefinido. Não confie no pressuposto de que a cópia pode ocorrer um byte por vez, ao longo de endereços cada vez maiores. Também esqueci de mencionar que while (isspace((int)string[i])) string[i--] = '\0';pode fazer um loop além do início da string. Você deve combinar esse loop com as linhas anteriores e seguintes e escreverwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
chqrlie 10/10
@chqrlie bom ponto, uma string com todos os espaços em branco teria causado um loop além do início, não pensou nisso.
wallek876
Na verdade, minha sugestão estava incorreta, endpois não apontava para o byte nulo à direita e você end = ++i;ainda tinha um problema para cadeias contendo todos os caracteres de espaço em branco. Eu apenas consertei o código.
chqrlie
1

Sei que tenho muitas respostas, mas posto aqui minha resposta para ver se minha solução é boa o suficiente.

// Trims leading whitespace chars in left `str`, then copy at almost `n - 1` chars
// into the `out` buffer in which copying might stop when the first '\0' occurs, 
// and finally append '\0' to the position of the last non-trailing whitespace char.
// Reture the length the trimed string which '\0' is not count in like strlen().
size_t trim(char *out, size_t n, const char *str)
{
    // do nothing
    if(n == 0) return 0;    

    // ptr stop at the first non-leading space char
    while(isspace(*str)) str++;    

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

    size_t i = 0;    

    // copy char to out until '\0' or i == n - 1
    for(i = 0; i < n - 1 && *str != '\0'; i++){
        out[i] = *str++;
    }    

    // deal with the trailing space
    while(isspace(out[--i]));    

    out[++i] = '\0';
    return i;
}
Ekeyme Mo
fonte
2
Nota: isspace(*str)UB quando *str < 0.
chux - Restabelece Monica
1
O uso de size_t né bom, mas a interface não informa o chamador de maneira alguma sobre nser muito pequeno para uma sequência cortada completa. Consideretrim(out, 12, "delete data not")
chux
1

A maneira mais fácil de ignorar espaços à esquerda em uma string é imho,

#include <stdio.h>

int main()
{
char *foo="     teststring      ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
    return 0;
}
Zibri
fonte
1
Isso não funcionará para cadeias de caracteres com espaços no meio, como " foo bar ".
David R Tribble
1

Ok, esta é a minha opinião sobre a questão. Eu acredito que é a solução mais concisa que modifica a string no lugar ( freefuncionará) e evita qualquer UB. Para strings pequenas, provavelmente é mais rápido que uma solução que envolva memmove.

void stripWS_LT(char *str)
{
    char *a = str, *b = str;
    while (isspace((unsigned char)*a)) a++;
    while (*b = *a++)  b++;
    while (b > str && isspace((unsigned char)*--b)) *b = 0;
}
poby
fonte
O b > strteste é necessário apenas uma vez. *b = 0;necessário apenas uma vez.
chux - Restabelece Monica
1
#include <ctype.h>
#include <string.h>

char *trim_space(char *in)
{
    char *out = NULL;
    int len;
    if (in) {
        len = strlen(in);
        while(len && isspace(in[len - 1])) --len;
        while(len && *in && isspace(*in)) ++in, --len;
        if (len) {
            out = strndup(in, len);
        }
    }
    return out;
}

isspace ajuda a aparar todos os espaços em branco.

  • Execute um primeiro loop para verificar o caractere de espaço do último byte e reduzir a variável length
  • Execute um segundo loop para verificar no primeiro byte o caractere de espaço e reduzir a variável length e aumentar o ponteiro de char.
  • Finalmente, se a variável length for maior que 0, use strnduppara criar um novo buffer de string excluindo espaços.
rashok
fonte
Apenas um pouco de nitpick, strndup()não faz parte do padrão C, mas apenas do Posix. Mas, como é muito fácil de implementar, não é grande coisa.
Patrick Schlüter
trim_space("")retorna NULL. Eu esperaria um ponteiro para "". int len;deveria ser size_t len;. isspace(in[len - 1])UB quando in[len - 1] < 0.
chux - Restabelece Monica
Uma inicial while (isspace((unsigned char) *in) in++;antes len = strlen(in);seria mais eficiente que a posteriorwhile(len && *in && isspace(*in)) ++in, --len;
chux - Reinstate Monica
0

Pessoalmente, eu rolaria o meu. Você pode usar o strtok, mas precisa tomar cuidado (principalmente se estiver removendo caracteres iniciais) para saber qual é a memória.

Livrar-se dos espaços à direita é fácil e bastante seguro, pois você pode simplesmente colocar um 0 no topo do último espaço, contando desde o final. Livrar-se dos espaços principais significa mudar as coisas. Se você quiser fazê-lo no lugar (provavelmente sensato), basta mudar tudo de volta para um personagem até que não haja espaço à frente. Ou, para ser mais eficiente, você pode encontrar o índice do primeiro caractere não espacial e mudar tudo de volta por esse número. Ou, você pode simplesmente usar um ponteiro para o primeiro caractere não espacial (mas, em seguida, você precisa ter cuidado da mesma maneira que no strtok).

Ben
fonte
4
O strtok geralmente não é uma ferramenta muito boa de usar - principalmente porque não é reentrante. Se você permanecer dentro de uma única função, ela poderá ser usada com segurança, mas se houver alguma possibilidade de threads ou chamar outras funções que possam usar o strtok, você está com problemas.
Jonathan Leffler
0
#include "stdafx.h"
#include "malloc.h"
#include "string.h"

int main(int argc, char* argv[])
{

  char *ptr = (char*)malloc(sizeof(char)*30);
  strcpy(ptr,"            Hel  lo    wo           rl   d G    eo rocks!!!    by shahil    sucks b i          g       tim           e");

  int i = 0, j = 0;

  while(ptr[j]!='\0')
  {

      if(ptr[j] == ' ' )
      {
          j++;
          ptr[i] = ptr[j];
      }
      else
      {
          i++;
          j++;
          ptr[i] = ptr[j];
      }
  }


  printf("\noutput-%s\n",ptr);
        return 0;
}
Balkrishna Talele
fonte
3
Isso me fez rir, porque eu pensei que o dreamlax havia editado a sequência de teste para incluir "é péssimo". Não. O autor original é honesto.
James Morris
1
Não use esse código. Produz um estouro de buffer.
Roland Illig 18/09/16
0

Um pouco tarde para o jogo, mas vou jogar minhas rotinas na briga. Provavelmente, eles não são os mais eficientes, mas acredito que estão corretos e são simples (com o rtrim()empurrão do envelope da complexidade):

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

/*
    Public domain implementations of in-place string trim functions

    Michael Burr
    [email protected]
    2010
*/

char* ltrim(char* s) 
{
    char* newstart = s;

    while (isspace( *newstart)) {
        ++newstart;
    }

    // newstart points to first non-whitespace char (which might be '\0')
    memmove( s, newstart, strlen( newstart) + 1); // don't forget to move the '\0' terminator

    return s;
}


char* rtrim( char* s)
{
    char* end = s + strlen( s);

    // find the last non-whitespace character
    while ((end != s) && isspace( *(end-1))) {
            --end;
    }

    // at this point either (end == s) and s is either empty or all whitespace
    //      so it needs to be made empty, or
    //      end points just past the last non-whitespace character (it might point
    //      at the '\0' terminator, in which case there's no problem writing
    //      another there).    
    *end = '\0';

    return s;
}

char*  trim( char* s)
{
    return rtrim( ltrim( s));
}
Michael Burr
fonte
1
você deve lançar o charargumento para isspace()a (unsigned char)evitar um comportamento indefinido em valores potencialmente negativos. Evite também mover a corda, se ltrim()for necessário.
chqrlie
0

Até agora, a maioria das respostas segue um destes procedimentos:

  1. Retroceder no final da sequência (ou seja, encontre o final da sequência e, em seguida, procure para trás até encontrar um caractere que não seja o espaço) ou
  2. Ligue strlen()primeiro, fazendo uma segunda passagem por toda a cadeia.

Esta versão faz apenas uma passagem e não retrocede. Portanto, ele pode ter um desempenho melhor que os outros, embora apenas se for comum ter centenas de espaços à direita (o que não é incomum ao lidar com a saída de uma consulta SQL).

static char const WHITESPACE[] = " \t\n\r";

static void get_trim_bounds(char  const *s,
                            char const **firstWord,
                            char const **trailingSpace)
{
    char const *lastWord;
    *firstWord = lastWord = s + strspn(s, WHITESPACE);
    do
    {
        *trailingSpace = lastWord + strcspn(lastWord, WHITESPACE);
        lastWord = *trailingSpace + strspn(*trailingSpace, WHITESPACE);
    }
    while (*lastWord != '\0');
}

char *copy_trim(char const *s)
{
    char const *firstWord, *trailingSpace;
    char *result;
    size_t newLength;

    get_trim_bounds(s, &firstWord, &trailingSpace);
    newLength = trailingSpace - firstWord;

    result = malloc(newLength + 1);
    memcpy(result, firstWord, newLength);
    result[newLength] = '\0';
    return result;
}

void inplace_trim(char *s)
{
    char const *firstWord, *trailingSpace;
    size_t newLength;

    get_trim_bounds(s, &firstWord, &trailingSpace);
    newLength = trailingSpace - firstWord;

    memmove(s, firstWord, newLength);
    s[newLength] = '\0';
}
finnw
fonte
1
Se você está preocupado com o desempenho, não use strspn()e strcspn()em um circuito fechado. Isso é muito ineficiente e a sobrecarga diminui a vantagem não comprovada do passe único para frente. strlen()geralmente é expandido em linha com código muito eficiente, não é uma preocupação real. Aparar o início e o fim da sequência será muito mais rápido do que testar todos os caracteres da sequência quanto à brancura, mesmo no caso especial de sequências com muito poucos ou nenhum caractere não branco.
chqrlie
0

Esta é a implementação mais curta possível em que consigo pensar:

static const char *WhiteSpace=" \n\r\t";
char* trim(char *t)
{
    char *e=t+(t!=NULL?strlen(t):0);               // *e initially points to end of string
    if (t==NULL) return;
    do --e; while (strchr(WhiteSpace, *e) && e>=t);  // Find last char that is not \r\n\t
    *(++e)=0;                                      // Null-terminate
    e=t+strspn (t,WhiteSpace);                           // Find first char that is not \t
    return e>t?memmove(t,e,strlen(e)+1):t;                  // memmove string contents and terminator
}
Michał Gawlas
fonte
1
Como sobre isso:char *trim(char *s) { char *p = s, *e = s + strlen(s); while (e > s && isspace((unsigned char)e[-1])) { *--e = '\0'; } while (isspace((unsigned char)*p)) { p++; } if (p > s) { memmove(s, p, e + 1 - p); } return s; }
chqrlie 8/16
0

Essas funções modificarão o buffer original, portanto, se alocado dinamicamente, o ponteiro original poderá ser liberado.

#include <string.h>

void rstrip(char *string)
{
  int l;
  if (!string)
    return;
  l = strlen(string) - 1;
  while (isspace(string[l]) && l >= 0)
    string[l--] = 0;
}

void lstrip(char *string)
{
  int i, l;
  if (!string)
    return;
  l = strlen(string);
  while (isspace(string[(i = 0)]))
    while(i++ < l)
      string[i-1] = string[i];
}

void strip(char *string)
{
  lstrip(string);
  rstrip(string);
}
Telc
fonte
rstrip()chama um comportamento indefinido na sequência vazia. lstrip()é desnecessariamente lento na string com uma longa porção inicial de caracteres de espaço em branco. isspace()não deve receber um charargumento porque invoca um comportamento indefinido em valores negativos diferentes de EOF.
chqrlie
0

Para aparar minhas cordas dos dois lados, uso o oldie, mas o bobo;) Ele pode aparar qualquer coisa com ascii menor que um espaço, o que significa que os caracteres de controle também serão aparados!

char *trimAll(char *strData)
{
  unsigned int L = strlen(strData);
  if(L > 0){ L--; }else{ return strData; }
  size_t S = 0, E = L;
  while((!(strData[S] > ' ') || !(strData[E] > ' ')) && (S >= 0) && (S <= L) && (E >= 0) && (E <= L))
  {
    if(strData[S] <= ' '){ S++; }
    if(strData[E] <= ' '){ E--; }
  }
  if(S == 0 && E == L){ return strData; } // Nothing to be done
  if((S >= 0) && (S <= L) && (E >= 0) && (E <= L)){
    L = E - S + 1;
    memmove(strData,&strData[S],L); strData[L] = '\0';
  }else{ strData[0] = '\0'; }
  return strData;
}
Деян Добромиров
fonte
Você deve usar em size_tvez de unsigned int. O código possui muitos testes redundantes e invoca um comportamento indefinido strncpy(strData,&strData[S],L)porque as matrizes de origem e destino se sobrepõem. Use em memmove()vez de strncpy().
chqrlie
Nesse caso, está ok, pois o endereço de destino sempre tem um índice menor que a fonte, mas sim, o memmove será realmente melhor.
Деян Добромиров
não, não está tudo bem. não importa como as matrizes de origem e destino se sobrepõem, ele invoca um comportamento indefinido porque você não pode fazer suposições seguras sobre a implementação das funções da biblioteca além da especificação padrão. Os compiladores modernos tendem a tirar vantagem injusta de situações com um comportamento indefinido em potencial, protegê-lo e ficar longe do UB, e não permitem que o novato faça suposições inseguras.
chqrlie
0

Estou incluindo apenas o código porque o código postado até o momento parece subótimo (e ainda não tenho o representante para comentar).

void inplace_trim(char* s)
{
    int start, end = strlen(s);
    for (start = 0; isspace(s[start]); ++start) {}
    if (s[start]) {
        while (end > 0 && isspace(s[end-1]))
            --end;
        memmove(s, &s[start], end - start);
    }
    s[end - start] = '\0';
}

char* copy_trim(const char* s)
{
    int start, end;
    for (start = 0; isspace(s[start]); ++start) {}
    for (end = strlen(s); end > 0 && isspace(s[end-1]); --end) {}
    return strndup(s + start, end - start);
}

strndup()é uma extensão GNU. Se você não o possui ou algo equivalente, faça o seu. Por exemplo:

r = strdup(s + start);
r[end-start] = '\0';
sfink
fonte
1
isspace(0)é definido como falso, você pode simplificar as duas funções. Mova também o memmove()interior do ifbloco.
chqrlie
0

Aqui eu uso a alocação dinâmica de memória para aparar a string de entrada na função trimStr. Primeiro, descobrimos quantos caracteres não vazios existem na string de entrada. Em seguida, alocamos uma matriz de caracteres com esse tamanho e cuidamos do caractere terminado nulo. Quando usamos essa função, precisamos liberar a memória dentro da função principal.

#include<stdio.h>
#include<stdlib.h>

char *trimStr(char *str){
char *tmp = str;
printf("input string %s\n",str);
int nc = 0;

while(*tmp!='\0'){
  if (*tmp != ' '){
  nc++;
 }
 tmp++;
}
printf("total nonempty characters are %d\n",nc);
char *trim = NULL;

trim = malloc(sizeof(char)*(nc+1));
if (trim == NULL) return NULL;
tmp = str;
int ne = 0;

while(*tmp!='\0'){
  if (*tmp != ' '){
     trim[ne] = *tmp;
   ne++;
 }
 tmp++;
}
trim[nc] = '\0';

printf("trimmed string is %s\n",trim);

return trim; 
 }


int main(void){

char str[] = " s ta ck ove r fl o w  ";

char *trim = trimStr(str);

if (trim != NULL )free(trim);

return 0;
}
saeed_falahat
fonte
0

Aqui está como eu faço isso. Ele apara a sequência no lugar, portanto, não se preocupe em desalocar uma sequência retornada ou perder o ponteiro para uma sequência alocada. Pode não ser a resposta mais curta possível, mas deve ficar clara para a maioria dos leitores.

#include <ctype.h>
#include <string.h>
void trim_str(char *s)
{
    const size_t s_len = strlen(s);

    int i;
    for (i = 0; i < s_len; i++)
    {
        if (!isspace( (unsigned char) s[i] )) break;
    }

    if (i == s_len)
    {
        // s is an empty string or contains only space characters

        s[0] = '\0';
    }
    else
    {
        // s contains non-space characters

        const char *non_space_beginning = s + i;

        char *non_space_ending = s + s_len - 1;
        while ( isspace( (unsigned char) *non_space_ending ) ) non_space_ending--;

        size_t trimmed_s_len = non_space_ending - non_space_beginning + 1;

        if (s != non_space_beginning)
        {
            // Non-space characters exist in the beginning of s

            memmove(s, non_space_beginning, trimmed_s_len);
        }

        s[trimmed_s_len] = '\0';
    }
}
Isaac para
fonte
absolutamente claro para os leitores, mas strlen executa outro loop .. :)
ingconti
0
char* strtrim(char* const str)
{
    if (str != nullptr)
    {
        char const* begin{ str };
        while (std::isspace(*begin))
        {
            ++begin;
        }

        auto end{ begin };
        auto scout{ begin };
        while (*scout != '\0')
        {
            if (!std::isspace(*scout++))
            {
                end = scout;
            }
        }

        auto /* std::ptrdiff_t */ const length{ end - begin };
        if (begin != str)
        {
            std::memmove(str, begin, length);
        }

        str[length] = '\0';
    }

    return str;
}
Mitch Laber
fonte
2
Embora esse código possa responder à pergunta, fornecer um contexto adicional sobre como e / ou por que resolve o problema melhoraria o valor a longo prazo da resposta.
precisa saber é o seguinte