Манипулиране на низове с помощта на strtok/sscanf в C

Опитвам се да разделя следния низ на три отделни променливи, т.е. a, b и c.:

"   mov/1/1/1,0 STR{7}, r7" 

всеки трябва да държи различен сегмент от низа, напр.

a = "mov/1/1/1,0"
b = "STR{7}"
c = "r7"

Може да има интервал или табулатор между всяка команда; това прави тази кодова част по-сложна.

Опитах се да използвам strtok за манипулиране на низове, но не се получи.

char command[50] = "    mov/1/1/1,0 STR{7}, r7";
char a[10], b[10], c[10];
char * ptr = strtok(command, "\t");
strcpy(a, ptr);
ptr = strtok(NULL, "\t");
strcpy(b, ptr);
ptr = strtok(NULL, ", ");
strcpy(c, ptr);

но това наистина обърква нещата, тъй като променливите a, b и c задържат повече стойности, отколкото би трябвало, което води програмата до срив.

Въвеждането може да варира от:

"   mov/1/1/1,0 STR{7}, r7"
"jsr /0,0            PRTSTR"
"mov/1/1/0,0         STRADD{5}, LASTCHAR {r3} "

в който стойностите на a,b и c се променят в различна част от дадения низ.

Казаха ми, че е по-безопасно да използвам sscanf за този вид маниери, отколкото strtok, но не съм сигурен защо и как може да ми помогне.

Ще се радвам да чуя вашето мнение!


person Ron Nuni    schedule 17.08.2013    source източник
comment
mov/1/1/0,0 и LASTCHAR {r3} Би било по-дълго от девет знака (a[10],c[10]).   -  person BLUEPIXY    schedule 18.08.2013
comment
Казвате, че искате нещата да бъдат разделени с интервал или също с разделител, но след това използвате strtok само с "\t" като разделител. Може би просто искате " \t" (интервали или раздели, както казвате), въпреки че това ще приеме всяка последователност от един или повече интервали или раздели като разделител. Ако искате две интервали да означават „празно“ поле, това няма да работи.   -  person Chris Dodd    schedule 18.08.2013
comment
Вашият примерен низ "jsr /0,0 PRTSTR" хвърля гаечен ключ в работата; има значима запетая във второто поле, докато в другите два примерни низа запетаята във второто поле не е значима. Ако трябва да премахнете крайните запетаи, можете да го направите след разделянето на базата на пространство.   -  person Jonathan Leffler    schedule 18.08.2013


Отговори (3)


Това трябва да свърши работа:

sscanf(command, "%s,%s,%s", &a, &b, &c)

От страницата за ръководство на scanf, %s изяжда белите интервали, били те интервали или раздели:

s : Съвпада с поредица от знаци, които не са празни интервали; следващият указател трябва да бъде указател към масив от знаци, който е достатъчно дълъг, за да побере входната последователност и крайния нулев байт ('\0'), който се добавя автоматично. Входящият низ спира на бяло пространство или на максималната ширина на полето, което от двете настъпи първо.

person ydache    schedule 17.08.2013
comment
Това включва крайната запетая във втория низ, което не изглежда да е желаната цел на OP. Може би актуализирайте спецификацията на вашия формат, за да отчетете това. - person WhozCraig; 18.08.2013
comment
Благодаря, свърши работа в определени случаи, но в други може да има раздели или интервали между всяка фраза, което кара sscanf да не я чете правилно. също така във втория низ оставя запетая в края на низа, която не трябва да е част от него - person Ron Nuni; 18.08.2013
comment
Момчета, интервалите между всеки низ може да се променят в различни команди, варират от един интервал до табулатори, това наистина прави нещата по-трудни.. p.s. Току-що добавих няколко примера за дадените стойности - person Ron Nuni; 18.08.2013
comment
@RonNuni Ако търсите универсален спецификатор на sscanf формат за всички тези формати, може да имате работа за себе си. Относно това, което този конкретен отговор се опитваше да предаде, опитайте "%s %[^,], %s", но аз сериозно бих обмислил най-стабилния подход. - person WhozCraig; 18.08.2013
comment
@WhozCraig, благодаря, това всъщност работи в повечето случаи, но фактът, че количеството бели интервали варира при всяко изпълнение, е това, което ми създава трудности, защото променливите a, b и c не се инициализират правилно - person Ron Nuni; 18.08.2013
comment
@RonNuni Тогава изберете нещо, което ви доближава и до трите променливи, и след това бъркайте с всяка от тях. С всичките ви потенциални формати, не мисля, че съществува сребърен куршум. Може да се наложи да анализирате това по трудния начин (понякога просто трябва да се направи). - person WhozCraig; 18.08.2013
comment
@WhozCraig, да, беше прав за това, че трябваше да създам свои собствени функции и да се справям сам с повечето „събития“. Само един малък въпрос, как мога да взема малко парче от низа и да инициализирам променлива с него. например от низа: mov/1/1/0,0 STRADD{5}, LASTCHAR {r3} ... как мога да инициализирам променлива a с mov? (бягам, докато стигна /)? - person Ron Nuni; 18.08.2013

Както може би знаете, че можете да използвате sscanf() по същия начин като scanf(), разликата е, че sscanf сканира от низ, докато scanf от стандартен вход.
В този проблем можете да укажете scanf с набор от знаци, които да "винаги пропускате" “, както е направено в тази връзка.
Тъй като вие имат различен набор от ограничения за сканиране на всичките три низа, можете да посочите, като използвате %*[^...], тези ограничения преди всяко %s вътре в sscanf().

person Don't You Worry Child    schedule 17.08.2013

Имам резерви относно използването на strtok(), но този код, който го използва, изглежда прави това, от което се нуждаете. Както отбелязах в коментар, примерният низ "jsr /0,0 PRTSTR" хвърля гаечен ключ в работата; има значима запетая във второто поле, докато в другите два примерни низа запетаята във второто поле не е значима. Ако трябва да премахнете крайните запетаи, можете да го направите след разделянето на базата на интервали — както е показано в този код. Вторият цикъл тества функцията zap_trailing_commas(), за да се увери, че се държи при изродени случаи, прекъсвайки завършващите запетаи, но не препълвайки началото на буфера или нещо ужасно.

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

static void zap_trailing_commas(char *str)
{
    size_t len = strlen(str);
    while (len-- > 0 && str[len] == ',')
        str[len] = '\0';
}

static void splitter(char *command)
{
    char a[20], b[20], c[20];
    char *ptr = strtok(command, " \t");
    strcpy(a, ptr);
    zap_trailing_commas(a);
    ptr = strtok(NULL, " \t");
    strcpy(b, ptr);
    zap_trailing_commas(b);
    ptr = strtok(NULL, " \t");
    strcpy(c, ptr);
    zap_trailing_commas(c);
    printf("<<%s>> <<%s>> <<%s>>\n", a, b, c);
}

int main(void)
{
    char data[][50] =
    {
        "   mov/1/1/1,0 STR{7}, r7",
        "jsr /0,0            PRTSTR",
        "mov/1/1/0,0         STRADD{5}, LASTCHAR {r3} ",
    };

    for (size_t i = 0; i < sizeof(data)/sizeof(data[0]); i++)
        splitter(data[i]);

    char commas[][10] = { "X,,,", "X,,", "X,", "X" };
    for (size_t i = 0; i < sizeof(commas)/sizeof(commas[0]); i++)
    {
        printf("<<%s>> ", commas[i]);
        zap_trailing_commas(&commas[i][1]);
        printf("<<%s>>\n", commas[i]);
    }

    return 0;
}

Примерен резултат:

<<mov/1/1/1,0>> <<STR{7}>> <<r7>>
<<jsr>> <</0,0>> <<PRTSTR>>
<<mov/1/1/0,0>> <<STRADD{5}>> <<LASTCHAR>>
<<X,,,>> <<X>>
<<X,,>> <<X>>
<<X,>> <<X>>
<<X>> <<X>>

Тествах и вариант със запетаи на мястото на X и това остави единствената запетая сама.

person Jonathan Leffler    schedule 18.08.2013