Wie schneide ich führende/nachgestellte Leerzeichen standardmäßig ab?

Lesezeit: 9 Minuten

Benutzeravatar von coledot
coledot

Gibt es eine saubere, vorzugsweise Standardmethode zum Trimmen führender und nachgestellter Leerzeichen aus einer Zeichenfolge in C? Ich würde mein eigenes rollen, aber ich würde denken, dass dies ein häufiges Problem mit einer ebenso häufigen Lösung ist.

Benutzeravatar von indiv
Einzeln

Hier ist einer, der die Zeichenfolge an die erste Position Ihres Puffers verschiebt. Möglicherweise möchten Sie dieses Verhalten, damit Sie die Zeichenfolge, wenn Sie sie dynamisch zugewiesen haben, immer noch auf demselben Zeiger freigeben können, den trim() zurückgibt:

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

Korrektheit testen:

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

Quelldatei war trim.c. Kompiliert mit ‘cc -Wall trim.c -o trim’.

  • Sie müssen das Argument für werfen isspace zu unsigned charandernfalls rufen Sie undefiniertes Verhalten auf.

    – Roland Illig

    18. September 2016 um 1:58 Uhr

  • @RolandIllig: Danke, ich habe nie gedacht, dass das notwendig ist. Behoben.

    – individuell

    19. September 2016 um 19:00 Uhr

  • @Simas: Warum sagst du das? Die Funktion ruft auf isspace() warum sollte es also einen unterschied zwischen geben " " und "\n"? Ich habe Einheitentests für Zeilenumbrüche hinzugefügt und es sieht für mich in Ordnung aus … ideone.com/bbVmqo

    – individuell

    17. Oktober 2016 um 20:55 Uhr

  • @indiv greift bei manueller Zuweisung auf einen ungültigen Speicherblock zu. Nämlich diese Zeile: *(endp + 1) = '\0';. Der Beispieltest für die Antwort verwendet einen Puffer von 64, wodurch dieses Problem vermieden wird.

    – Simas

    18. Oktober 2016 um 6:59 Uhr

  • @nolandda: Danke für die Details. Ich habe es behoben und den Test aktualisiert, um den Pufferüberlauf zu erkennen, da ich im Moment keinen Zugriff auf Valgrind habe.

    – individuell

    26. Juli 2019 um 20:21 Uhr

  • trim() ruft UB auf, wenn s ist "" Als der erste isspace() Anruf wäre isspace(p[-1]) und p[-1] verweist nicht unbedingt auf einen legalen Ort.

    – chux – Wiedereinsetzung von Monica

    6. November 2013 um 16:39 Uhr

  • Sie müssen das Argument für werfen isspace zu unsigned charandernfalls rufen Sie undefiniertes Verhalten auf.

    – Roland Illig

    18. September 2016 um 1:58 Uhr

  • hinzufügen sollte if(l==0)return; um Nulllänge zu vermeiden str

    – ch271828n

    5. März 2019 um 0:01 Uhr

Schießt auf den Benutzeravatar des Mondes
Schießt auf den Mond

Hier ist meine C-Mini-Bibliothek zum Trimmen links, rechts, beides, alle, an Ort und Stelle und separat und zum Trimmen einer Reihe bestimmter Zeichen (oder standardmäßig Leerzeichen).

Inhalt von 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

Inhalt von 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); }

Die eine Hauptroutine erledigt alles. Es trimmt an Ort und Stelle, wenn Quelle == dstansonsten funktioniert es wie die strcpy Routinen. Es schneidet eine Reihe von Zeichen ab, die in der Zeichenfolge angegeben sind delim, oder Leerzeichen, wenn null. Es trimmt links, rechts, beide und alle (wie tr). Es gibt nicht viel zu tun, und es wird nur einmal über die Zeichenfolge iteriert. Einige Leute werden sich vielleicht darüber beschweren, dass das Trimmen rechts links beginnt, aber es wird kein Strlen benötigt, das sowieso links beginnt. (Auf die eine oder andere Weise müssen Sie für die richtigen Trimmungen bis zum Ende der Zeichenfolge gelangen, also können Sie die Arbeit genauso gut erledigen, wie Sie gehen.) Es gibt möglicherweise Argumente über Pipelining und Cache-Größen und dergleichen – wer weiß . Da die Lösung von links nach rechts arbeitet und nur einmal iteriert, kann sie auch auf Streams erweitert werden. Einschränkungen: tut es nicht weiterarbeiten Unicode Saiten.

  • Ich habe dies positiv bewertet und ich weiß, dass es alt ist, aber ich denke, es gibt einen Fehler. dtab[*d] wirft nicht *d zu unsigned int bevor Sie es als Array-Index verwenden. Auf einem System mit signiertem Zeichen wird dies bis zu lauten dtab[-127] was zu Fehlern und möglicherweise zum Absturz führen wird.

    – Zan Luchs

    12. Dezember 2013 um 1:20 Uhr

  • Potenziell undefiniertes Verhalten an dtab[*delim++] Weil char Indexwerte müssen gecastet werden unsigned char. Der Code setzt 8-Bit voraus char. delim sollte als deklariert werden const char *. dtab[0xFF & (unsigned int)*d] wäre klarer als dtab[(unsigned char)*d]. Der Code funktioniert mit UTF-8-codierten Zeichenfolgen, entfernt jedoch keine Nicht-ASCII-Abstandssequenzen.

    – chqrlie

    8. Oktober 2016 um 12:41 Uhr

  • @michael-plainer, das sieht interessant aus. Warum testest du es nicht und stellst es auf GitHub?

    – Daisuke Aramaki

    7. Juni 2019 um 12:15 Uhr

Hier ist mein Versuch einer einfachen, aber korrekten In-Place-Trimmfunktion.

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

  • Ich habe dies positiv bewertet und ich weiß, dass es alt ist, aber ich denke, es gibt einen Fehler. dtab[*d] wirft nicht *d zu unsigned int bevor Sie es als Array-Index verwenden. Auf einem System mit signiertem Zeichen wird dies bis zu lauten dtab[-127] was zu Fehlern und möglicherweise zum Absturz führen wird.

    – Zan Luchs

    12. Dezember 2013 um 1:20 Uhr

  • Potenziell undefiniertes Verhalten an dtab[*delim++] Weil char Indexwerte müssen gecastet werden unsigned char. Der Code setzt 8-Bit voraus char. delim sollte als deklariert werden const char *. dtab[0xFF & (unsigned int)*d] wäre klarer als dtab[(unsigned char)*d]. Der Code funktioniert mit UTF-8-codierten Zeichenfolgen, entfernt jedoch keine Nicht-ASCII-Abstandssequenzen.

    – chqrlie

    8. Oktober 2016 um 12:41 Uhr

  • @michael-plainer, das sieht interessant aus. Warum testest du es nicht und stellst es auf GitHub?

    – Daisuke Aramaki

    7. Juni 2019 um 12:15 Uhr

Zu spät zur Trimm-Party

Merkmale:
1. Schneiden Sie den Anfang schnell ab, wie in einer Reihe anderer Antworten.
2. Nachdem Sie zum Ende gegangen sind, trimmen Sie die rechte Seite mit nur 1 Test pro Schleife. Wie @jfm3, funktioniert aber für eine Zeichenfolge, die nur aus Leerzeichen besteht)
3. Um undefiniertes Verhalten zu vermeiden, wenn char ist ein signiertes chargießen *s zu unsigned char.

Umgang mit Zeichen „In allen Fällen ist das Argument ein intderen Wert als darstellbar sein soll unsigned char oder soll gleich dem Wert des Makros sein EOF. Wenn das Argument einen anderen Wert hat, ist das Verhalten undefiniert.“ 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 kommentierte, dass das obige die getrimmte Zeichenfolge nicht verschiebt. Dazu ….

// 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, endlich jemand, der sich mit dem undefinierten Verhalten von ctype auskennt.

    – Roland Illig

    18. September 2016 um 2:21 Uhr

1425640cookie-checkWie schneide ich führende/nachgestellte Leerzeichen standardmäßig ab?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy