Проблема с функцией strtok в C: она возвращает только один токен

#c #shell #loops #environment-variables #strtok

Вопрос:

Может кто-нибудь, пожалуйста, помочь мне понять, почему мой код не присваивает следующий элемент strtok , который функция возвращает в мой индекс? Он прикрепляет первое возвращение strtok , но не любое из следующих. Так что мой цикл while запускается только один раз. Я не уверен, что происходит, большая помощь признательна. Спасибо!

   #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int ac, char **av, char **env)
    {
        char **token;
        const char *deli = ":";
        char *path = "PATH=";
        char *hold;
        int i, j, k = 0, inputSize = 100;
    
        int count;
    
        token = malloc(inputSize * sizeof(char));
            if(token == NULL)
            {
                exit;
            }
    
            for (count = 0; count < inputSize; count  )
            {
                token[count] = malloc(sizeof(char) * (inputSize));
    
                if (token[count] == NULL)
                {
                    for (count -= 1; count >= 0; count--)
                    {
                        free(token[count]);
                    }
                    free(token);
    
                    return (0);
                }
        }
    //this loop gets PATH
        for (i = 0; env[i] != NULL; i  )
        {
            for (j = 0; j < 5; j  )
            {
            if (path[j] != env[i][j])
                    break;
            }
            if (j == 5)
                break;
        }
    
        strtok(env[i], deli);
    
        hold = strtok(env[i], deli);
    
        while (hold != NULL)
        {
            token[k] = (char*)hold;
            printf("%sn", token[k]);
            k  ;
            hold = strtok(NULL, deli);
        }
        return (0);
    }
 

Комментарии:

1. Обратите внимание, что это и есть exit(); , и нет exit; . Последние ничего не делают.

2. Для strtok данного буфера вы передаете адрес буфера при первом вызове и передаете NULL для остальных вызовов/токенов

3. Вы должны удалить первую инструкцию strtok(), эта функция изменит содержимое env_i, и следующий вызов этой функции не будет работать должным образом.

4. спасибо, ребята! удаление первого звонка помогло

Ответ №1:

У вас есть несколько проблем, вызывающих проблемы. При использовании strtok() первого вызова используется сам указатель, например strtok (p, delims); , в то время как все последующие вызовы используются NULL вместо указателя, например strtok (NULL, delim); .

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

Вы также усложняете поиск строки среды ПУТИ, а затем удаляете "PATH=" ее часть, чтобы оставшуюся часть можно было разделить на отдельные компоненты пути. Простой способ подойти к этому-использовать strncmp() для поиска префикса "PATH=" , а затем перейти к первым пяти символам в строке.

Вы можете сделать это просто с PREFIX помощью определенных как "PATH=" и prefixlen = strlen(PREFIX); как:

     char path[PATH_MAX] = "",               /* storage for copy of environment string */
        *p = path,                          /* pointer - general use */
        **tokens = NULL;                    /* pointer to pointer for path components */
    ...
    for (int i = 0; env[i]; i  )            /* find PREFIX in environment */
        if (strncmp (env[i], PREFIX, prefixlen) == 0) {
            strcpy (p, env[i]   prefixlen); /* copy path from env to path */
            break;
        }
 

Если вы инициализируете хранилище для path пустой строки или всего нуля, вы можете подтвердить PATH , что переменная среды была найдена, проверив, содержит ли ваша копия строку, например

     if (!*p) {  /* if PREFIX not found */
        fprintf (stderr, "error: '%s' not found in environment.n", PREFIX);
        return 1;
    }
 

Когда вы используете strtok() для разделения компонентов пути path для доступа через указатель на указатель char , вы должны выделить указатель для каждого компонента (и дополнительный указатель, если вы хотите предоставить стража NULL в качестве последнего указателя, отмечающего конец). Хотя обычно вы хотели бы выделить блок указателей с относительным количеством компонентов пути (менее 100), вы можете просто realloc добавить указатель для каждого найденного токена. Когда вы realloc , вы всегда используете временный указатель, чтобы предотвратить перезапись исходного указателя в NULL случае realloc сбоя.

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

     /* loop moving ep (end-pointer) to next DELIM in p or last token */
    for (p = strtok (p, DELIM); p; p = strtok (NULL, DELIM)) {
        size_t  len = strlen(p);            /* length of token */
        /* realloc pointers using temporary pointer
         * adding two more than index to set sentinel NULL as last pointer
         * (sentinel NULL is optional, just add 1 if not wanted)
         */
        void *tmp = realloc (tokens, (ndx   2) * sizeof *tokens);
        if (!tmp) {                                 /* validate realloc */
            perror ("realloc-tokens");
            break;
        }
        tokens = tmp;                               /* assign new block to tokens */
        tokens[ndx   1] = NULL;                     /* set sentinel NULL */
        if (!(tokens[ndx] = malloc (len   1))) {    /* allocate/validate string storage */
            perror ("malloc-tokens[ndx]");
            tokens[ndx] = NULL;
            break;
        }
        memcpy (tokens[ndx  ], p, len   1);         /* copy path component */
    }
 

Изложив все это в коротком примере, вы могли бы сделать:

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

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

#define PREFIX "PATH="  /* constants for PREFIX and DELIM */
#define DELIM ":n"

int main (int argc, char **argv, char **env)
{
    char path[PATH_MAX] = "",               /* storage for copy of environment string */
        *p = path,                          /* pointer - general use */
        **tokens = NULL;                    /* pointer to pointer for path components */
        
    size_t  prefixlen = strlen (PREFIX),    /* prefix length */
            ndx = 0;                        /* path tokens index */
    
    for (int i = 0; env[i]; i  )            /* find PREFIX in environment */
        if (strncmp (env[i], PREFIX, prefixlen) == 0) {
            strcpy (p, env[i]   prefixlen); /* copy path from env to path */
            break;
        }
    
    if (!*p) {  /* if PREFIX not found */
        fprintf (stderr, "error: '%s' not found in environment.n", PREFIX);
        return 1;
    }
    
    puts ("nfull path:n");    /* show full path to create tokens from */
    puts (p);
    
    /* loop moving ep (end-pointer) to next DELIM in p or last token */
    for (p = strtok (p, DELIM); p; p = strtok (NULL, DELIM)) {
        size_t  len = strlen(p);            /* length of token */
        /* realloc pointers using temporary pointer
         * adding two more than index to set sentinel NULL as last pointer
         * (sentinel NULL is optional, just add 1 if not wanted)
         */
        void *tmp = realloc (tokens, (ndx   2) * sizeof *tokens);
        if (!tmp) {                                 /* validate realloc */
            perror ("realloc-tokens");
            break;
        }
        tokens = tmp;                               /* assign new block to tokens */
        tokens[ndx   1] = NULL;                     /* set sentinel NULL */
        if (!(tokens[ndx] = malloc (len   1))) {    /* allocate/validate string storage */
            perror ("malloc-tokens[ndx]");
            tokens[ndx] = NULL;
            break;
        }
        memcpy (tokens[ndx  ], p, len   1);         /* copy path component */
    }
    
    puts ("npath components:n");                  /* output separated tokens */
    for (size_t i = 0; tokens amp;amp; tokens[i]; i  ) {  /* loop until sentinel NULL */
        puts (tokens[i]);
        free (tokens[i]);                           /* free string storage */
    }
    free (tokens);                                  /* free pointers */
    
    (void)argc;     /* cast to prevent -Wunused warnings */
    (void)argv;
}
 

(примечание: for() условие цикла tokens amp;amp; tokens[i] будет обрабатывать случай, когда начальное распределение с realloc() возвратами NULL позволяет избежать разыменования tokens[0] . Вы можете предоставить отдельный if (!tokens) { perror ("initial allocation failed"); return 1; } над выходным контуром, если хотите)

Пример Использования/Вывода

Компиляция и запуск программы приведут к выходу, аналогичному:

 $ ./bin/path_tokens

full path:

/opt/kde3/bin:/home/david/bin:/usr/local/bin:/usr/bin:/bin:/usr/lib/qt3/bin:/sbin:/usr/sbin:/usr/local/sbin:/opt/gcc-arm-none-eabi/bin

path components:

/opt/kde3/bin
/home/david/bin
/usr/local/bin
/usr/bin
/bin
/usr/lib/qt3/bin
/sbin
/usr/sbin
/usr/local/sbin
/opt/gcc-arm-none-eabi/bin
 

Использование памяти/Проверка Ошибок

В любом написанном вами коде, который динамически выделяет память, у вас есть 2 обязанности в отношении любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, чтобы (2) его можно было освободить, когда он больше не нужен.

Крайне важно использовать программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или выполнить запись за пределами/за пределами выделенного блока, попытаться прочитать или выполнить условный переход на неинициализированном значении и, наконец, подтвердить, что вы освободили всю выделенную память.

Для Linux valgrind это нормальный выбор. Для каждой платформы существуют аналогичные средства проверки памяти. Все они просты в использовании, просто запустите через них свою программу.

 $ valgrind ./bin/path_tokens
==22643== Memcheck, a memory error detector
==22643== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==22643== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==22643== Command: ./bin/path_tokens
==22643==

full path:

/opt/kde3/bin:/home/david/bin:/usr/local/bin:/usr/bin:/bin:/usr/lib/qt3/bin:/sbin:/usr/sbin:/usr/local/sbin:/opt/gcc-arm-none-eabi/bin

path components:

/opt/kde3/bin
/home/david/bin
/usr/local/bin
/usr/bin
/bin
/usr/lib/qt3/bin
/sbin
/usr/sbin
/usr/local/sbin
/opt/gcc-arm-none-eabi/bin
==22643==
==22643== HEAP SUMMARY:
==22643==     in use at exit: 0 bytes in 0 blocks
==22643==   total heap usage: 21 allocs, 21 frees, 1,679 bytes allocated
==22643==
==22643== All heap blocks were freed -- no leaks are possible
==22643==
==22643== For counts of detected and suppressed errors, rerun with: -v
==22643== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
 

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок в памяти нет.

Изменения Для работы В Windows и Linux

Since windows uses the ';' (semi-colon) for the path separator and prefixes the environment string with "Path=" , while Linux uses ':' (colon) for the separator and "PATH=" as the prefix, with a simple preprocessor #if .. #else .. #endif , you can modify the program to work on both Widows and Linux. Simply replace the defines for PREFIX and DELIM with:

 #if defined (_WIN64) || defined (_WIN32)
#define PREFIX "Path="  /* constants for PREFIX and DELIM on windows */
#define DELIM ";rn"
#else
#define PREFIX "PATH="  /* constants for PREFIX and DELIM on Linux */
#define DELIM ":n"
#endif
 

Пример использования/вывода в Windows

Полный путь был опущен, так как он будет прокручиваться на тысячу с лишним символов:

 >binpath_tokens_win.exe

full path:

<snipped -- too long>

path components:

C:Program Files (x86)Microsoft Visual Studio2017CommunityVCToolsMSVC14.16.27023binHostX86x86
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7IDEVCVCPackages
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7IDECommonExtensionsMicrosoftTestWindow
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7IDECommonExtensionsMicrosoftTeamFoundationTeam Explorer
C:Program Files (x86)Microsoft Visual Studio2017CommunityMSBuild15.0binRoslyn
C:Program Files (x86)Microsoft Visual Studio2017CommunityTeam ToolsPerformance Tools
C:Program Files (x86)Microsoft Visual StudioSharedCommonVSPerfCollectionTools
C:Program Files (x86)Microsoft SDKsWindowsv10.0AbinNETFX 4.6.1 Tools
C:Program Files (x86)Windows Kits10bin10.0.17763.0x86
C:Program Files (x86)Windows Kits10binx86
C:Program Files (x86)Microsoft Visual Studio2017Community\MSBuild15.0bin
C:WindowsMicrosoft.NETFrameworkv4.0.30319
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7IDE
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7Tools
C:Program FilesImageMagick-7.0.10-Q16
C:Program Files (x86)Common FilesOracleJavajavapath
C:ProgramDataOracleJavajavapath
C:Program FilesImageMagick-7.0.3-Q16
C:WindowsSystem32
C:Windows
C:WindowsSystem32wbem
C:WindowsSystem32WindowsPowerShellv1.0
C:Program Files (x86)PDFtkbin
C:Program Files (x86)PDFtk Serverbin
C:Program Files (x86)GNUGnuPGpub
C:Program Files (x86)Windows Kits10Windows Performance Toolkit
C:WindowsSystem32OpenSSH
C:Program FilesGitcmd
C:Program FilesPuTTY
C:Program Files (x86)Gpg4win..GnuPGbin
C:WINDOWSsystem32
C:WINDOWS
C:WINDOWSSystem32Wbem
C:WINDOWSSystem32WindowsPowerShellv1.0
C:WINDOWSSystem32OpenSSH
C:UsersdavidAppDataLocalMicrosoftWindowsApps
c:MinGWbin
c:MinGWmsys1.0bin
c:gtk2bin
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7IDECommonExtensionsMicrosoftCMakeCMakebin
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7IDECommonExtensionsMicrosoftCMakeNinja
 

(Microsoft, конечно, не стесняется размера пути….)

Есть несколько других вариантов, кроме strtok() того, чтобы не изменять исходную строку. Вы можете использовать strpbrk() аналогично тому, как вы strtok() вручную перемещаете указатель начала и конца в скобки и копируете каждый компонент. Вы можете использовать strchr() для определения местоположения каждого разделителя по существу одинаковым образом. Если вам нравится работать с индексами больше, чем с указателями, вы можете использовать strcspn() их для определения количества символов между каждым разделителем. Конечно, вы также можете просто использовать цикл, работающий вниз, а затем строку, вручную определяющую местоположение каждого разделителя. Как вы это сделаете, полностью зависит от вас.

Посмотрите все и дайте мне знать, если у вас будут еще вопросы.

Комментарии:

1. ух ты!! Это было потрясающе , большое вам спасибо за подробный ответ!!! Я обязательно перечитаю это еще несколько раз. Очень подробный ответ. Спасибо.

2. Пожалуйста. Удачи вам с кодированием. Дайте мне знать, если вам понадобится дополнительная помощь.