странное поведение стрток

char line[255];
char *token = NULL;
char *line2 = NULL;
char *temporaryToken = NULL;

if( scanf(" %[^\n]", line) > 0)
    token = strtok( line, ";" ); //divide the line by ;
    do
    {
        line2 = token;
        temporaryToken = strtok(line2, " ");
        do
        {
            //divide the line2 by spaces into command and args, not the question here]
            temporaryToken = strtok( NULL, " " );
        }while (temporaryToken != NULL );
        token = strtok( NULL, ";" );
    }while(token != NULL);

это не мой код дословно, кстати, просто пример того, как он изложен

В моей программе, когда я печатаю переменную "токен" перед тем, как разделить второй раз, она будет печатать все до тех пор, пока ; символ.

Например, если stdIn принял "ls -la; mkdir lololol; ls -la", он напечатает "ls -la". Но тогда, после второго разделения, печать «токена» будет печатать только «ls».

Почему это так и как я могу это исправить?


person Sam P    schedule 11.08.2012    source источник
comment
Я не понимаю, в чем проблема! Вы можете объяснить?   -  person TOC    schedule 11.08.2012


Ответы (2)


Есть две проблемы с strtok().

  1. Он изменяет свою входную строку.
  2. Одновременно может быть активен только один набор strtok() вызовов.

Я думаю, что ваша проблема в последнем. У вас также есть проблема с отступами в коде:

if (scanf(" %[^\n]", line) > 0)
    token = strtok( line, ";" );
do
{
    line2 = token;
    temporaryToken = strtok(line2, " ");
    do
    {
        //divide the line2 by spaces into command and args, not the question here]
        temporaryToken = strtok(NULL, " ");
    } while (temporaryToken != NULL);
    token = strtok( NULL, ";" );
} while(token != NULL);

Вероятно, вы имели в виду следующее:

if (scanf(" %[^\n]", line) > 0)
{
    token = strtok(line, ";");
    do
    {
        line2 = token;
        temporaryToken = strtok(line2, " ");
        do
        {
            //divide the line2 by spaces into command and args, not the question here]
            temporaryToken = strtok(NULL, " ");
        } while (temporaryToken != NULL);
        token = strtok(NULL, ";");
    } while (token != NULL);
}

Предполагая, что это то, что вы намеревались, у вас все еще есть проблема, заключающаяся в том, что один strtok() работает на line, а второй работает на line2. Проблема в том, что цикл на line2 полностью разрушает интерпретацию line. Вы не можете использовать вложенные циклы с strtok().

Если вы должны использовать что-то вроде strtok(), найдите либо POSIX < a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/strtok_r.html" rel="nofollow noreferrer">strtok_r() или strtok_s() (но обратите внимание, что версия strtok_s() в приложении K стандарта C11 отличается — см. Используете ли вы «безопасные» функции TR 24731?).

if (scanf(" %[^\n]", line) > 0)
{
    char *end1;
    token = strtok_r(line, ";", &end1);
    do
    {
        char *end2;
        line2 = token;
        temporaryToken = strtok_r(line2, " ", &end2);
        do
        {
            //divide the line2 by spaces into command and args, not the question here]
            temporaryToken = strtok_r(NULL, " ", &end2);
        } while (temporaryToken != NULL);
        token = strtok_r(NULL, ";", &end1);
    } while (token != NULL);
}

О комментариях

Пока вы используете strtok() или один из его родственников, входная строка будет изменена, и если у вас есть несколько разделителей, вы не сможете определить, какой разделитель присутствовал. Вы можете работать с копией строки и выполнять сравнения (обычно на основе смещений от начала строки).

В пределах использования strtok_r() приведенное выше решение «работает». Вот тестовая программа для демонстрации:

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

int main(void)
{
    char line[1024];

    if (scanf(" %[^\n]", line) > 0)
    {
        char *end1;
        char *token;
        printf("Input: <<%s>>\n", line);
        token = strtok_r(line, ";", &end1);
        do
        {
            char *end2;
            char *line2 = token;
            char *temporaryToken;
            printf("Token1: <<%s>>\n", token);
            temporaryToken = strtok_r(line2, " ", &end2);
            do
            {
                printf("Token2: <<%s>>\n", temporaryToken);
                //divide the line2 by spaces into command and args, not the question here]
                temporaryToken = strtok_r(NULL, " ", &end2);
            } while (temporaryToken != NULL);
            token = strtok_r(NULL, ";", &end1);
        } while (token != NULL);
    }

    return 0;
}

Пример ввода и вывода:

$ ./strtok-demo
ls -la; mkdir lololol; ls -la
Input: <<ls -la; mkdir lololol; ls -la>>
Token1: <<ls -la>>
Token2: <<ls>>
Token2: <<-la>>
Token1: << mkdir lololol>>
Token2: <<mkdir>>
Token2: <<lololol>>
Token1: << ls -la>>
Token2: <<ls>>
Token2: <<-la>>
$

Альтернатива с использованием strcspn() и strspn()

Если вы не хотите разрушать исходную строку, вы должны использовать другие функции, кроме семейства strtok(). Подходят функции strcspn() и strspn(); они являются частью стандарта C (C89 и более поздние версии), хотя и гораздо менее известны, чем некоторые другие функции. Но они идеально подходят для этой задачи.

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

static char *substrdup(const char *src, size_t len);

int main(void)
{
    char line[1024];

    if (scanf(" %[^\n]", line) > 0)
    {
        char *start1 = line;
        size_t len1;
        printf("Input: <<%s>>\n", line);
        while ((len1 = strcspn(start1, ";")) != 0)
        {
            char *copy = substrdup(start1, len1);
            char *start2 = copy;
            size_t len2;
            printf("Token1: %zd <<%.*s>>\n", len1, (int)len1, start1);
            printf("Copy: <<%s>>\n", copy);
            start2 += strspn(start2, " ");      // Skip leading white space
            while ((len2 = strcspn(start2, " ")) != 0)
            {
                printf("Token2: %zd <<%.*s>>\n", len2, (int)len2, start2);
                start2 += len2;
                start2 += strspn(start2, " ");
            }
            free(copy);
            start1 += len1;
            start1 += strspn(start1, ";");
        }
        printf("Check: <<%s>>\n", line);
    }

    return 0;
}

#include <assert.h>

static char *substrdup(const char *src, size_t len)
{
    char *copy = malloc(len+1);
    assert(copy != 0);              // Apalling error handling strategy
    memmove(copy, src, len);
    copy[len] = '\0';
    return(copy);
}

Пример ввода и вывода:

$ strcspn-demo
ls -la; mkdir lololol; ls -la
Input: <<ls -la; mkdir lololol; ls -la>>
Token1: 140734970342872 <<>>
Copy: <<ls -la>>
Token2: 2 <<ls>>
Token2: 3 <<-la>>
Copy: << mkdir lololol>>
Token2: 5 <<mkdir>>
Token2: 7 <<lololol>>
Copy: << ls -la>>
Token2: 2 <<ls>>
Token2: 3 <<-la>>
Check: <<ls -la; mkdir lololol; ls -la>>
$

Этот код восходит к более удобному циклу while, вместо того, чтобы использовать циклы do-while, что является преимуществом.

person Jonathan Leffler    schedule 11.08.2012
comment
Должен ли я тогда беспокоиться об управлении &end1 и &end2? это единственное, чего я не знал о strtok_r - person Sam P; 11.08.2012
comment
ТАКЖЕ: я добавил вашу реализацию strtok_r, но она не сработала - все еще оставалась проблема с изменением токена. Любые идеи? - person Sam P; 11.08.2012
comment
Обратите внимание, что как только вы дойдете до поддержки строковых аргументов в кавычках, любой метод, основанный на strtok, перестанет быть подходящим. Впрочем, это дело будущего, а не срочная проблема. (Вы можете использовать метод на основе strcspn, если строки вашего класса символов содержат все соответствующие специальные символы (например, одинарную кавычку, двойную кавычку, обратную кавычку, доллар, обратную косую черту), а также основные пробельные символы (код, вероятно, должен принять табуляцию в качестве разделителя), и вы смотрите на то, что отметило конец, чтобы решить, что делать дальше. Код выше не должен был беспокоиться обо всем этом. - person Jonathan Leffler; 19.08.2012

strtok изменяет исходную строку. Если вы хотите смешивать такие вызовы, вам нужно либо сделать копию, либо использовать strtok_r.

person Karl Bielefeldt    schedule 11.08.2012
comment
Как мне это сделать? Я полный новичок в C. Буду ли я создавать другую строку с именем tokenizedLine и создавать временные токены на ее основе? - person Sam P; 11.08.2012