LINUX.ORG.RU
решено ФорумAdmin

[сумасшедшая задача][5 млн файлов]Достать построчно в отдельные файлы, переименовать и сменить расширение


0

2

Есть файл 5 млн. строк.

Нужно достать отдельно каждую строчку(одну), создать файл для каждой одной строчки в отдельный файл который будет с таким же названием как и содержание строчки и поставить ему расширение *db3


Я когда делал, подумал что 5 млн. файлов это не так много же и займет от силы пару часов, поэтому даже не заморачивался и подумал сделать по отдельность так:

#!/bin/bash

if [ -z "$1" ]; then
    echo "Нужно указать на файл"
fi

L=1
M=$(wc -l "$1" | egrep -o '^[0-9]+')
N=$((M / L))
if [[ $((N*L)) -lt $M ]]; then
    tail -n$((M - N*L)) "$1" > "$1$((N+1))";
fi

for ((i=0; i<N; i++)); do
    head -n$((i*L + L)) "$1" | tail -n$L > "$1$i";
done
И на создание 988576 ушло полтора дня(и загрузка одного ядра все это время на 70%), а потом закончилось место, и вот я толком не могу понять какой их них последний, вернее догадываюсь(что это db.txt988586), но боюсь ошибиться, вот вывод ls
Не пропадать же работе, эти только переименую, а уже потом уже удалю из файла со всеми строчками все до последние и пойду сразу с переименованием и сменой расширения(это тоже переименование по сути) Ну вот а потом я уже думал поменять названия с расширением файлов:
#!/bin/bash
for f in *; do
    d="$(head -1 "$f" | awk '{ print }').html"
    if [ ! -f "$d" ]; then
        mv "$f" "$d"
    else
        echo "Файл '$d' уже есть! Пропущен '$f'"
    fi
done
Короче основной вопрос это что-то что будет быстрее всего делать это:

Нужно достать отдельно каждую строчку(одну), создать файл для каждой одной строчки в отдельный файл который будет с таким же названием как и содержание строчки и поставить ему расширение *db3


Учитывая что файлов 5 млн где-то выйдет, awk print если сделать в первом скрипте я так понимаю будет еще медленней.
Потом вопрос, стоит ли переименовывать уже готовые файлы около миллиона, или проще заново пройтись если найдется что-нить быстрее?
Ну и еще я немного сомневаюсь, какой файл последний в выводе ls.

Перемещено maxcom из talks

Первая глупость - это работать над всеми 5 млн.

Алгоритмы, какими бы они ни были нужно было отработать на 2-3 сотнях. Выбрав быстрейший приступать к делу.

Вот тут, очень разумно пишут про split:

http://www.askdavetaylor.com/how_can_i_quickly_create_millions_of_tiny_files_...

Он умеет бить файлы, -l 1 будет делать это построчно.

Проверь, какая у него скорость и отпишись.

zgen ★★★★★
()

Нужно достать отдельно каждую строчку(одну), создать файл для каждой одной строчки в отдельный файл который будет с таким же названием как и содержание строчки и поставить ему расширение *db3

$ cat 111.txt 
as as
df df
zx zx
cv cv
$ awk '{printf "" >$0".db3"}' 111.txt 
$ ls -1
111.txt
as as.db3
cv cv.db3
df df.db3
zx zx.db3
uzbl
()

У тебя именно место закончилось? Посмотри df -i

Интересно, как быстро отработает такой код:

with open("list.txt") as f:
    for line in f:
        with open(line, 'w') as file:
            file.write(line)
?

power
()

C#

foreach (string s in File.ReadAllLines(fileName, Encoding.Default))
            File.WriteAllText(Path.Combine(outDirectory, s + ".db3"), s);

// NightmareZ

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

awk '{printf «» >$0".db3"}' 111.txt

Оно во внутрь же тоже самое не запишет. Да похоже нужно еще таки фильтровать названия.

split

zgen Пробую, пока больше всего похоже на то что надо

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

что-то вроде

sed -n 's/.*/cat >"&.db3"/e' 
вам поможет. (если конечно в строках гарантированно нет спецсимволов вроде $,! и т.д.)

drBatty ★★
()

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

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

anonymous_sama

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

ИМХО быстрее будет переделать 1М файлов, чем проверять, есть они или нет.

drBatty ★★
()

И файлы лучше всего пихать в древовидную структуру, к примеру на основе номера. Так будет гораздо быстрее, чем все 5млн в одной директории. Посмотри, к примеру, структуру кэша сквида.

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

Оно во внутрь же тоже самое не запишет

Тогда так:

$ cat 111.txt 
<>:$1()@#*&
<>:$1()@#*&1
<>:$1()@#*&2
<>:$1()@#*&4
$ awk '{print $0>$0".db3"}' 111.txt
$ ls -1
111.txt
<>:$1()@#*&1.db3
<>:$1()@#*&2.db3
<>:$1()@#*&4.db3
<>:$1()@#*&.db3

Да похоже нужно еще таки фильтровать названия

Какая фс? Posix исключает использование в имени файла символа '\0' и '/' (по понятным причинам); все остальные — валидны.

Я бы делал это на рамдиске или ext2; причём переписал бы на с, но сейчас лениво ;)

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

Ну ммм примерно так, но впрочем я уже представляю что делать дальше:
awk: (FILENAME=db.txt FNR=54) fatal: can't redirect to `03081975|Blalbla 2005: RULE Torrkölich-LIK 24/7, 2:a shimato|948962244|1|0|789201ntHaRn8WKF6rFYSGsDHNH.db3' (Нет такого файла или каталога)
Что awk, что sed суть то одна. Нужен фильтрующий regexp

anonymous_sama ★★★★★
() автор топика
-- base
import Prelude (IO, Monad(..), ($), (++))

import System.IO (writeFile)
import System.Environment (getArgs)

-- enumerator
import Data.Enumerator (($=), ($$), run_, consume)
import Data.Enumerator.List (mapM_)
import Data.Enumerator.Text (enumFile, lines)

-- text
import Data.Text (unpack)

main :: IO ()
main = do
    (file:_) <- getArgs
    run_ $ enumFile file $= lines $$ mapM_ $ \x -> writeFile (unpack x ++ ".db3") ""

Исключительно интереса ради, попробуй так.

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

Попробуйте так:

awk '{a=$0; gsub(«/», "", a); print $0>a".db3"}' 111.txt
Regexp вообще говоря, будет очень медленно; здесь уместен только аналог memchr(, '/', ).

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

Regexp вообще говоря, будет очень медленно; здесь уместен только аналог memchr(, '/', ).

Просто пропустить через tr

tr -d'/' input.file | awk '{print $0>$0".db3"}'

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

Ему нужно, чтобы содержимое файла содержало исходную немодифицированную строку; вариант с предфильтрацией не подходит.

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

uzbl

Regexp вообще говоря, будет очень медленно; здесь уместен только аналог memchr(, '/', ).

в sed разницы не будет.

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

пойдёт? (не проверил)

За 55 минут 161666 файлов с

awk '{a=$0; gsub(«/», "", a); print $0>a".db3"}' 111.txt


Как думаешь стоит через sed таки?

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

Похоже сама судьба протестировать, снова кончилось место.

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

anonymous_sama

Как думаешь стоит через sed таки?

без понятия, тебе виднее. Обычно sed быстрее gawk, и почти всегда значительно быстрее bash. Но тут возможны варианты (изменение каталога с 5М файлами само по себе долго, и выигрыш от sed может быть просто незаметен. Это НЕ проверяется в тестах).

Похоже сама судьба протестировать, снова кончилось место.

ну это уже ваши проблемы. Ещё могут inode кончится (см df -i)

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

я уже тестировал. Sed быстрее. Вот только, 5М файлов это примерно 500Мб размера каталог (если имя файла в среднем ~100 байтов). Создание файла - полное переписывание каталога. Сколько у вас записывается на диск 500Мб? Насколько сек. минимум. Потому, разница между bash и sed будет попросту не заметна. Только вначале, на первых 10К файлов. Займёт это всё как минимум 2 мес, если 500Мб у вас пишется 1 сек. ИЧСХ, CPU вам не поможет, разве что SSD.

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

Можно tmpfs использовать, если оперативки хватит

Толк будет только при больших файлах побольше или в если в несколько потоков.
---
Да проверил в моем случае разницы sed vs awk нет
За 55 минут 161666 файлов с awk и 162873 за 57 минут с sed
Ну конечно тут очень специфичная задача.

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

Проверил на tmpfs. 360000 файлов — 2 ГБ ушло :) Примерно такая же скорость (46 секунд против 37 секунд на носителе).

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

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

Ну я почти так уже и делаю. Разделил файл по 500000 и в разных каталогах.

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

sdio

Серьезно? В любой ФС?

в EXT*. А что, есть альтернативы? Ну попробуй FAT & NTFS - они загнуться намного раньше. Я проверял.

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

adzeitor

Можно tmpfs использовать, если оперативки хватит... А потом уже скопировать на носитель.

можно попробовать...

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

anonymous_sama

Ну конечно тут очень специфичная задача.

нет. Такое часто встречается... Кстати, можно ускорить разбив файлы на группы например по CRC32, и создав M каталогов. Обычно оптимально M делать примерно sqrt(N).

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

adzeitor

Проверил на tmpfs. 360000 файлов — 2 ГБ ушло :) Примерно такая же скорость (46 секунд против 37 секунд на носителе).

дык кеширование... Не будет работать на 5М файлах (точнее будет, но намного менее эффективно).

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

Написал тест для проверки на сколько директорий оптимально разбивать. Вот что получилось на 200000 строк:

3> millionfiles:start_bench().

5000: 83 and 61 seconds 

10000: 89 and 57 seconds 

20000: 91 and 63 seconds 

30000: 93 and 61 seconds 

40000: 104 and 58 seconds

50000: 109 and 59 seconds 

60000: 102 and 62 seconds 

70000: 99 and 68 seconds 

80000: 103 and 71 seconds 

90000: 95 and 69 seconds 

100000: 109 and 57 seconds 

110000: 109 and 55 seconds 

120000: 108 and 67 seconds

130000: 117 and 64 seconds 

Первое число - количество файлов в каждой директории, далее время первого и второго прохода.

Не успел большие значения проверить, но завтра выложу, если надо.

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

бинарник бы ему сделал, ТС так и пойдёт себе haskell окружение ставить, разбираться с кабалом и ставить нужные пакеты :)

PS. могу скинуть для amd64 с динамическими зависимостями от:

libgmp.so.10
libm.so.6
libc.so.6

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

adzeitor

Первое число - количество файлов в каждой директории, далее время первого и второго прохода.

а считать время на 1 файл я за вас должен?

adzeitor

Не успел большие значения проверить

именно они и интересуют. Но это не важно, я уже проверял - примерно квадратный корень получается, и в теории и на практике. Я брал несколько цифр из CRC32 - например 4 цифры дают ровно 10000 каталогов. Ну или CRC32 можно брать 16иричное, тогда 2 цифры дают 256 каталогов, а 4 - 64К каталогов. Время создания файла растёт примерно линейно в зависимости от числа файлов в каталоге, потому каталогов должно быть столько-же, сколько файлов в них. Это приводит нас к формуле M=sqrt(N). Тогда у нас M каталогов, в каждом из которых M файлов. Т.е. надо около 2236 каталогов. Оптимум гладкий, потому проще 2048.

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

нафига crc-то кладите по очереди да и всё

кстати чисто для интереса можно попробовать:

import Prelude hiding (lines) 
import System.IO (writeFile) 
import System.Environment (getArgs) 
import Data.Enumerator (($=), ($$), run_, consume) 
import qualified Data.Enumerator.List as EL 
import Data.Enumerator.Text (enumFile, lines) 
import System.Directory (createDirectory) 
import System.FilePath.Posix ((</>)) 
import Control.Monad (when) -- text 
import Data.Text (unpack) 
main :: IO () 
main = do 
    (file:xs) <- getArgs 
    case xs of 
         (k:_) -> 
             run_ $ enumFile file $= lines $= nodir $= dir (read k) $$ sink 
         _ -> run_ $ enumFile file $= lines $= nodir $$ sink 
    where 
        nodir = EL.map unpack 
        sink = EL.mapM_ $ \x -> flip writeFile "" $ x ++ ".db3" 
        dir k = flip EL.mapAccumM (0,0) $ \(i,d) x -> do 
            let st@(i',d') = if i==0 then (k,d+1) else (i-1,d) 
            when (i==0) $ createDirectory $show $ d+1 
            return ((i',d'),(show d') x) 

можно задать файл и кол-во файлов в каталоге, по превышению создаётся новый и пишется туда

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

qnikst

нафига crc-то

мне ещё надо было проверить, есть такой файл или нет. Все подкаталоги просматривать было долго.

drBatty ★★
()

А нет ли проблемы в скорости работы с древом файлов у самой ФС? Попробуйте Reiserfs. Однако, как уже подметили, Ваш алгоритм был, мягко говоря, не оптимизированным.

ktulhu666 ☆☆☆
()
Ответ на: комментарий от qnikst

qnikst

а, ну тоже не проблема плюс полстроки в программу

если вы будете кидать в каталоги файлы в беспорядке, то это проблема - придётся проверить ВСЕ файлы.

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

я видимо не по русски выражаюсь, повторю, для того, чтобы распихивать файлы в каталоги по crc нужно добавить в вышепреведённую программу лишь пару строк, что тут не ясного?

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

qnikst

я видимо не по русски выражаюсь

угу. поиск файла - это тоже полстроки. Вот только тормозить сильно будет. (без crc или чего-то подобного).

qnikst

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

теперь понятно.

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

56к файлов прочитанных из files, куда названия были нагенерены из /dev/urandom, учитывая алгоритм при увеличении числа файлов, время будет увеличиваться линейно, а потребляемые ресурсы будут постоянными. т.е. на твои 5 миллионов файлов уйдёт час, если FS-ка тупить не будет.

Код тестировался на ReiserFS.

	Command being timed: "./test files"
	User time (seconds): 3.25
	System time (seconds): 2.86
	Percent of CPU this job got: 14%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:43.24
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 31072
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 0
	Minor (reclaiming a frame) page faults: 9962
	Voluntary context switches: 4675
	Involuntary context switches: 497
	Swaps: 0
	File system inputs: 1000
	File system outputs: 146920
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 4096
	Exit status: 0
qnikst ★★★★★
()
Ответ на: комментарий от VladimirMalyk

qnikst

56к файлов прочитанных из files, куда названия были нагенерены из /dev/urandom, учитывая алгоритм при увеличении числа файлов, время будет увеличиваться линейно, а потребляемые ресурсы будут постоянными. т.е. на твои 5 миллионов файлов уйдёт час, если FS-ка тупить не будет.

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

VladimirMalyk

ФС таким не нагружают. для такого БД используют.

да... но по условию надо файлы.

drBatty ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.