LINUX.ORG.RU

Запутался то ли с выводом строки в Си, то ли даже просто с массивом и указателями

 


0

3

Что не так в этом куске кода, никак не пойму. Короткая программка должна тупо вырезать любые xml-теги из входного файла. Просто убирая все теговое, начиная с символа скобка влево и заканчивая скобка вправо.

Если символы не убирать, а заменять, то все нормально, алгоритм работает и на выходе тот же текст, но с забитыми символами на месте тегов. Если убирать (не копированием из одного буфера в другой), то возникают глюки. Часть символов почему-то не копируется.

Проблема тупо в косяке с указателями и длиной буфера? Или с функцией fputs, хотя ее замена на write ничего не дает. Чистка выходного буфера memset тоже ничего не меняет.

Программа открывает файл, читает его функцией read в входной буфер, затем в функции copybuf вырезает теги и копирует то, что без них. Вырезание тегов сделано тупо автоматом с флагом-глобальной переменной tagstatus. Если 0 - символы копируем, если 1 - не копируем, пока не встретим закрывающую угловую скобку.

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define BSIZE	64

int copybuf(int bufsize, char* input, char* output);
int tagstatus;

int main(int argc, char** argv)
{
  /* set buffer */
  ssize_t readlen; // bytes
  char inputbuffer[BSIZE+1];
  char outputbuffer[BSIZE+1];
  
  /* Open file */
  int f = open (argv[1],O_NOATIME|O_RDONLY);
  if (f<0)
  {
     fprintf(stderr, "buf: Cannot open file '%s'\n",argv[1]);
     return 1;
  }

  /* read file */
  tagstatus=0;
  while ((readlen = read (f, inputbuffer, BSIZE)) > 0)
  {
       int len=copybuf(readlen,inputbuffer,outputbuffer);      
       if (len>0)
       {
         outputbuffer[len]=0;
         fputs (outputbuffer, stdout);       
       }     
  }

  if (readlen < 0)
  {
    fprintf (stderr, "buf: Cannot read file\n");
    close(f);
    return 1;
  }

  close(f);
  return 0;
}

/* copybuf function */
int copybuf(int bufsize, char* input, char* output)
{
  /* return count of really copied bytes */
  int len=0;
  for (int i=0; i<bufsize; i++)
  {  
     if (tagstatus==0) //outside a tag, main text
     {
        if (input[i]!='<') //no start tag
         {
           output[i]=input[i];len++;
           continue;
         }
         else 
         {
           tagstatus=1;//new tag start            
           continue;
         }
     }
     else //inside a tag
     {
         if (input[i]=='>') //end tag
         {
           tagstatus=0;//close tag
           continue;
         }         
     }
  }
  return len;
}
★★★★★

if (input[i]!='<') //no start tag

Вот это вот безотносительно остального кода звучит как «если не выключенно включить» - такие отрицательные условия это лишнее усложнение семантики на пустом месте. Вообще на мой вкус от стиля изложения - попахивает, даже не знаю чем но отторжение вызывает.

По-теме, не проверял но по идее:

outputbuffer[len]=0;
fputs (outputbuffer, stdout);

это лишнее, просто запиши выходной буфер write’ом.

pon4ik ★★★★★
()

Вот тут косяк output[i]=input[i];len++;

text<tag>text у тебя превращается в textXXXXXtext где X - это мусорная память, которая у тебя была в строке 2 до того как ты начал писать. Тебе нужны разные индексы для двух строк.

Aswed ★★★★★
()
Последнее исправление: Aswed (всего исправлений: 1)
Ответ на: комментарий от Aswed

Вот тут косяк output[i]=input[i];len++;

Тебе нужны разные индексы для двух строк.

Ничего не нужно:

output[len]=input[i];len++;
anonymous
()

Уберите вообще output из параметра copybuf, сделайте его внутренним, и работайте нормально с указателем:

size_t copybuf(ssize_t bufsize, char* buf)
{
    char *output = buf, *o0 = buf;
    ...
        if (*buf != '<') 
            *output++ = *buf;
    ....
    return output - o0;
}
Тогда не будет дополнительного буфера, возврат из функции будет правильное количество символов в выходном буфере. Кстати, если у вас на входе bla-bla<tag xxx>foo, то на выходе склеится bla-blafoo.

vodz ★★★★★
()
Ответ на: комментарий от Aswed

Вот тут косяк

Хренак, посыпаю голову пеплом. Вот что значит глаз замылился и элементарного с индексами не увидел.

Проверял как fputs работает, пытался менять на write, даже на всякий случай memset чистил буфер.

praseodim ★★★★★
() автор топика
Ответ на: комментарий от vodz

Тогда не будет дополнительного буфера, возврат из функции будет правильное количество символов в выходном буфере.

Кстати, да. Предыдущий код еще и для наглядности. Но функцию тогда лучше назвать не copybuf, а как-нибудь вроде cleanbuf.

Кстати, если у вас на входе bla-bla<tag xxx>foo, то на выходе склеится bla-blafoo.

Ну да, это же просто пример, реальная программа посложнее.

praseodim ★★★★★
() автор топика

Чёт у тебя всё сложно как то

Держи ::)

#include <stdio.h>

enum errors{OK=0,FILED_ARGS=-1,FILED_OPEN_INPUT=-2,FILED_OPEN_OUTPUT=-3};

int main(int argc, char *argv[])
{
    if(argc < 3)
    {return FILED_ARGS;}

    FILE *in, *out = NULL;

    if(!(in=fopen(argv[1],"r")))
    {return FILED_OPEN_INPUT;}

    if(!(out=fopen(argv[2],"w")))
    {return FILED_OPEN_OUTPUT;}

    for(int stat = 0, ch=1; (ch=getc(in)) != EOF; )
    {
        switch(ch) 
        {
            case '<':   stat = 1; break;
            case '>':   stat = 0; break;
            default : (!stat)? fprintf(out,"%c",ch):0; break;
        }
    }
    fclose(in);
    fclose(out);
    return OK;
}
LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Можно и так. С буфером скорость выше. Хотя и теоретически, не сравнивал на практике =)

К тому же конечный результат несколько сложнее, чем просто убранные все угловые скобки. Он для fb2-файлов, там наверное буду оставлять только то, что между body находится, игнорируя остальное.

Еще не решил, что со сносками в тексте делать, физически они обычно в конце файла после основной части body.

praseodim ★★★★★
() автор топика
Последнее исправление: praseodim (всего исправлений: 2)
Ответ на: комментарий от dsxl

Смотря с какой стороны проще

dron@gnu:~/Рабочий-Стол$ apt source perl; cd ./perl-5.30.3; wc -l ./*/*.c | grep "итого"
...
бла бла бла
...
  41356 итого
dron@gnu:~/Рабочий-Стол/perl-5.30.3$ 

А у него походу либа, или это часть внутреннего кода.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от praseodim

скорость выше

Тебе терабайты тегов нужно вырезать что ли? Небось однострочник на перле отработал бы даже быстрее, чем ты свою программу закончил бы отлаживать.

dsxl
()
Ответ на: комментарий от LINUX-ORG-RU

При чём тут количество строк в исходниках перла и почему не учтены ещё исходники gcc, ядра и всего остального?

dsxl
()
Ответ на: комментарий от LINUX-ORG-RU

А у него походу либа, или это часть внутреннего кода.

Где-то так, но скорее просто Си захотелось вспомнить.

praseodim ★★★★★
() автор топика
Ответ на: комментарий от dsxl

Тебе терабайты тегов нужно вырезать что ли?

Не терабайты, но десятки гигабайт и десятки тысяч файлов - да. Их еще раззиповывать надо будет, т.к., обычно это не одиночные *.fb2, а архивы *.fb2.zip, думаю тоже совместить, смотрел сторонние либы в буфер последовательно раззиповывают.

praseodim ★★★★★
() автор топика
Последнее исправление: praseodim (всего исправлений: 2)
Ответ на: комментарий от praseodim

Ну разница есть, да. Если файл влезет полностью в память

SHELL=bash
all:
	GET "https://www.linux.org.ru/forum/development/15799228" > prein.xml;
	echo "" > in.xml;
	for (( i = 0; i < 15000; i++ )) do cat prein.xml >> in.xml; done;
	du -h ./in.xml;
	gcc xml.c -O3 -DF2F -o app_1;
	gcc xml.c -O3 -DF2B -o app_2;
	time ./app_1 in.xml out.txt ; rm out.txt;
	time ./app_2 in.xml out.txt
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
enum errors{OK=0,FILED_ARGS=-1,FILED_OPEN_INPUT=-2,FILED_OPEN_OUTPUT=-3};

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

    if(argc < 3)
    {return FILED_ARGS;}

    FILE *in, *out = NULL;

    if(!(in=fopen(argv[1],"r")))
    {return FILED_OPEN_INPUT;}

    if(!(out=fopen(argv[2],"w")))
    {return FILED_OPEN_OUTPUT;}

#if defined(F2F)
    for(int stat = 0, ch=1; (ch=getc(in)) != EOF; )
    {
        switch(ch) 
        {
            case '<':   stat = 1; break;
            case '>':   stat = 0; break;
            default : (!stat)? fprintf(out,"%c",ch):0; break;
        }
    }
#endif

#if defined(F2B)
    fseek(in,SEEK_CUR,SEEK_END);
    size_t size = ftell(in);
    fseek(in,SEEK_CUR,SEEK_SET);

    char * buffer = malloc(sizeof(char)*size);
    assert(buffer);
    memset(buffer,'\0',size);

    int bcount = 0;
    for(int stat = 0, ch=1; (ch=getc(in)) != EOF ; )
    {
        switch(ch) 
        {
            case '<':   stat = 1; break;
            case '>':   stat = 0; break;
            default : (!stat)? buffer[bcount++]=ch:0; break;
        }
    }

    fprintf(out, "%s\n",buffer);
    free(buffer);
#endif

    fclose(in);
    fclose(out);
    return OK;
}
dron@gnu:~/Рабочий-Стол$ make
GET "https://www.linux.org.ru/forum/development/15799228" > prein.xml;
echo "" > in.xml;
for (( i = 0; i < 15000; i++ )) do cat prein.xml >> in.xml; done;
du -h ./in.xml;
638M	./in.xml
gcc xml.c -O3 -DF2F -o app_1;
gcc xml.c -O3 -DF2B -o app_2;
time ./app_1 in.xml out.txt ; rm out.txt;

real	0m13,659s
user	0m4,161s
sys	0m0,635s
time ./app_2 in.xml out.txt

real	0m5,385s
user	0m3,648s
sys	0m0,593s
dron@gnu:~/Рабочий-Стол$ 

Хотя если не влезет можно взять размер в 10 метров. И его записывать, выгружать, затирать и так по кругу. Хотя можно не затирать, а лишь на последней итерации заполнения в конец счётчика заполения \0 втюхать. Со статичным размером буфера чёт лень было делать тест =) Хотя нахер я его вообще делал тоже непонятно ну да ладно :D

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от praseodim

десятки гигабайт и десятки тысяч файлов - да. Их еще раззиповывать надо будет

Это не так и много. Да и диск всё равно будет узким местом.

Я бы просто написал бы портянку на баше, которая сначала раззиповывает все файлы на диск, а затем удаляет теги. И пока оно там работает я бы спокойно занимался своими делами.

Но это если бы у меня были другие дела, а не «просто Си захотелось вспомнить.»

dsxl
()
Ответ на: комментарий от dsxl

А вспомнить язык это что плохо? Может ему скоро чё патчить надо будет или самоделку какую делать когда платы приедут. Вот и себе дело полезное сделает в виде автоматизации и язык вспомнит. И тут не важно какой собственно.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от dsxl

Я бы просто написал бы портянку на баше

Для большей части да. Для критичных мест с нюансами и особенностями оправданно себе утилитку написать чисто для себя и конкретного случая. Ничё такого в этом нет.

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от anonymous

вот что бывает, когда не используют rust…

Адепты такие бестолковые. Только отталкивают от языка, потому что возникает ощущение что тем же растом интересуются в основном не очень грамотные, но социально активные особи.

anonymous
()
Ответ на: комментарий от anonymous

Адепты такие бестолковые. Только отталкивают от языка, потому что возникает ощущение что тем же растом интересуются в основном не очень грамотные, но социально активные особи.

Такой вот дух времени. Сверхинициативные неучи массой давят опытных и скромных.

anonymous
()
Ответ на: комментарий от anonymous

вручную парсить xml на C в 2020 году…

Почему бы и нет, если полноценный парсинг не требуется, а нужно для определенного вида файлов быстро выдрать из них текст?

Нет, понятно, что по-энтерпрайзному надо написать xslt-файл для трансформации в plain text, который будет вызываться каким-нибудь там саблотроном или еще-чем нибудь правильным, хоть libxslt каким.

Хотя, наверное, ошибаюсь. Совсем по фен-шую в 2020-м году будет использовать какой-нибудь JQuery в Electron-е для парсинга XML. Верно, да?

К тому же, как уже сказал, мне и вообще захотелось с Си размяться. Уже вижу, что не зря.

praseodim ★★★★★
() автор топика
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

int main(int argc, char * argv[]) {
  // Ты начал использовать сишное api, молодец.
  int fd = open(argv[1], O_RDONLY);
  struct stat st;
  fstat(fd, &st);
  //используй его до конца
  const char * mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0), * end = mem + st.st_size;
  
  // писать на сишке - придумывать максимально узкие решения
  
  // твою задачу можно свести к: (start ...) '<' ... '>', ..., end
  // какой-то текст, потом тег, который нужно скипнуть. Далее опять какой-то текст.
  // цикл готовый.
  
  // перва часть - это поиск '<', базовый паттерн для memchr
  // memchr(start, '<') -> null|ptr
  
  // start ... end | start ... ptr -> start ... (ptr ?: end)
  //write(start, (ptr ?: end) - start);
  
  // есть записано по end - файл закончен, признак end - (ptr == null)
  
  //if(!ptr) break;
  
  //далее осталось только скипнуть тег.
  // опять же, базовый паттерн для memchr
  // memchr(ptr, '>') -> null|ptr
  
  // признак коцна файла null | ((ptr + 1) == end)
  
  // во всех остальных случаях - ptr + 1 - это новый start
  
  
  do {
    //никогда не используй логику дерьма для поиска флагов.
    const char * sp = memchr(mem, '<', end - mem);
    
    write(STDOUT_FILENO, mem, (sp ?: end) - mem);
    if(!sp) break;
    
    const char * ep = memchr(sp, '>', end - sp);
    if(!ep | ((ep + 1) == end)) break;
    
    mem = ep + 1;
  } while(1);
}


anonymous
()
Ответ на: комментарий от pon4ik

такие отрицательные условия это лишнее усложнение семантики на пустом месте.

Нет, очевидно. Это «всё исключая X». Никакого усложнения нет, базовая операция применяющаяся везде.

anonymous
()

Не занимайся ерундой.

cat file.xml | sed 's/<[^<>]*>//g'

cherry_boy
()
Ответ на: комментарий от LINUX-ORG-RU

как можно вспомнить то, что ты и не знал

anonymous
()
Ответ на: комментарий от praseodim

А что в расте нельзя забыть использовать разные индексы для массивов в подобном случае?

В расте есть нормальные строки, пердолинг с индексами не нужен. /тхреад

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

Тот редчайший случай, когда я поддержку «царя» - это что за строки такие в расте расчудесные, показать можешь?

anonymous
()
Ответ на: комментарий от Virtuos86

Ну ты какую-то ерунду пока городишь. Топикстартер конкретно обозначил проблему - он неправильно делал индексацию по выходной строке. Очередной малоразбирающийся адепт раста выдал, что на расте такой проблемы нет. Ты его поддержал. Ну так и покажи нам тут грешным, как в расте можно было избежать этой проблемы и тогда мы поверим. А так это просто очередной бессмысленный вброс про якобы божественный раст. От которых уже подташнивает.

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

anonymous
()
Ответ на: комментарий от anonymous

Топикстартер конкретно обозначил проблему - он неправильно делал индексацию по выходной строке.

Ерунду горожу я, по-твоему, а фигню несешь ты. Ну какие в Си могут быть строки? ТС занимается «массивным» байтоёбством. В расте тоже так можно делать, так даже нужно делать — если пишешь либу с максимальной производительностью, но в отличие от божественной сишечки в расте уже написано определенное количество кода в стдлиб, которое позволяет считать, что в языке есть строки, а не тыкать пальцем в любой массив байтов и заниматься самообманом, называя это «строкой».
А тот анонимус, конечно, просто набросил. По крайней мере, очень похоже на типовой лулз, тем более в треде предложили уже несколько вариантов реально более юзабельных. Полагаю, он такой же адепт раста, как и ты.

Virtuos86 ★★★★★
()
Ответ на: комментарий от hobbit

Ну в общем да. Но не для чтения, а разных экспериментов с текстом.

praseodim ★★★★★
() автор топика
Ответ на: комментарий от anonymous

mmap - хороший вариант, тоже думал про него. Где-то припоминаю однажды мерялись скоростями и open (fopen) проиграл почти в два раза.

//никогда не используй логику дерьма для поиска флагов.

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

В целом, спасибо. Проще и изящнее у тебя вышло.

praseodim ★★★★★
() автор топика
Ответ на: комментарий от praseodim

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

То будешь заново писать. Царь пишет в духе олимпиады. Поставил рекорд по скорости, а дальше что-нибудь поменялось, и код в мусорку.

anonymous
()
Ответ на: комментарий от praseodim

А что в расте нельзя забыть использовать разные индексы для массивов в подобном случае?

Это же раст. В нем ничего забыть нельзя, особенно этот шикарный синтаксис.

anonymous
()
Ответ на: комментарий от praseodim

Где-то припоминаю однажды мерялись скоростями и open (fopen) проиграл почти в два раза.

Это не важно. Даже если бы не проиграл - mmap используется для того, чтобы не пердолится с нелепыми буферами и прочим дерьмом. Это маздайским рабам нужно страдать, а у людей есть нормальное api. Оно удобнее и быстрее.

И обусловлено это тем, что мы как люди можем пользоваться благами прогресса. А рабы должны страдать.

Поэтому зачем прикручивать какое-то убожество с буферами, если всё это уже есть на уровне mmu. mmap так же как и read обращается к пейджкешу. Только если read обращается произвольными кусками(пейджкеш же всё равно работает со страницами и читает их) и копирует данные из пейджкеша в юзерспейс. mmap же читает по 4k(в базе) и страницы эти не копирует, а прямиком отображает в юзерпейс. Это в том случае, если ты используешь только PROT_READ, если используешь ещё и PROT_WRITE - страны будут копироваться.

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

Поэтому всё, буферизация у тебя есть. Минимальный расход памяти - есть.

Руками писать что-то не имеет смысла, используя мусорные read/write. Нужны нормальные api, но там уже сложно и никакого убого пейджкеша.

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

Ну да, очевидно, что если memchr не применим - его использовать не нужно. Но в тех случаях когда применим - нужно. Просто потому, что он проще и быстрее.

Но опять же, все подобные «советы» работают только в базовых случаях. Когда какой-то особенный - нужно разбираться с ним отдельно, да и реализующий подобные случаи уже знает(зачастую) что делает.

anonymous
()
Ответ на: комментарий от anonymous

То будешь заново писать.

В этом и смысл сишки.

Царь пишет в духе олимпиады.

Нет, Олимпиады - это битвы бездарных инвалидов.

Поставил рекорд по скорости

Никаких рекордов там нет и быть не может. Запарта бездарна по определению и вся её отрыжка так же.

Рекорды ставятся в реальных задачах, соревнуясь с лучшими. А не в дошколятское бездарной херне соревнуясь с бездарными инвалидами.

а дальше что-нибудь поменялось, и код в мусорку.

Ты обгадился - это раз. Два - это свойство любого кода. Но api-обезьяне этого не понять. Обезьяна не пишет код - она жрёт говно и конфигурирует api используя те пару трюков, которым его обучил хозяин.

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

anonymous
()

ТС, а как оно с XML-ем в котором в тексте < или > есть справляется? Короче я за обмазывание тестами.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от praseodim

Уже написано. Я твою задачу решал в прошлом году, есть специальная вещь для этого https://zlovolsky.wordpress.com/2013/01/11/конвертация-fb2-в-txt-в-gnulinux/ почитай, вроде как лучше всего варит на практике. Распаковка ясно через unzip, короче на баше все эти сотни гигабайт обрабатываются меньше, чем за час с учётом распаковки, которая отжирает больше всего времени.

ТС, а не расскажешь что именно ты хочешь в конце сделать? Ну или какого плана задачу решить? А то может я её и решил уже? Тогда кину тебе кодец, мне не слишком жалко.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 2)
Ответ на: комментарий от peregrine

Естестественно сглючит. Но оно и не для этого нужно. В нормальном художественном тексте вероятность появления < > минимальна.

praseodim ★★★★★
() автор топика
Ответ на: комментарий от peregrine

ТС, а не расскажешь что именно ты хочешь в конце сделать? Ну или какого плана задачу решить? А то может я её и решил уже? Тогда кину тебе кодец, мне не слишком жалко.

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

Ну такой себе проект и чтобы интересно было и Си припомнить в итоге на реальной задаче. И хотя и не терабайты крутить надо, но в общем о скорости тоже есть повод позаботиться.

Вон у Мошкова одно время была фича выдачи похожести произведения на произведение кого-то еще. Сейчас то ли прикрыл, то ли в глаза не бросается.

praseodim ★★★★★
() автор топика
Последнее исправление: praseodim (всего исправлений: 4)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.