Поєднання великої кількості файлів


15

У мене є ± 10 000 файлів ( res.1- res.10000), всі складаються з одного стовпця та рівного числа рядків. Те, що я хочу, по суті, просте; об'єднайте всі файли в стовпці в новий файл final.res. Я спробував використовувати:

paste res.*

Однак (хоча це , здається, працює для невеликого підмножини результуючих файлів, це дає наступне повідомлення про помилку , коли виконується на всій множині: Too many open files.

Має бути "простий" спосіб зробити це, але, на жаль, я абсолютно новачок у Unix. Спасибі заздалегідь!

PS: Щоб дати вам уявлення про те, як виглядає (один із моїх) файлів даних:

0.5
0.5
0.03825
0.5
10211.0457
10227.8469
-5102.5228
0.0742
3.0944
...

Ви спробували скористатися --serialпараметром із pasteкомандою?
шивами

@shivams paste --serialне з’єднує файли, що відповідають колонці ...
Стівен Кітт

@StephenKitt Зачекайте. Я трохи розгублений. Чи означає він, що у вихідному файлі йому потрібен інший стовпець для даних кожного файлу? Або всі дані в одному стовпчику?
шивам

@Stephen Kitt shivams Використання paste -sсправді працює, але окремі файли результатів вставляють рядок замість стовпців. Однак це щось, що я можу вирішити. Спасибі!
килимки

@shivams Я хочу інший стовпчик для даних кожного файлу у вихідному файлі
матчі

Відповіді:


17

Якщо у вас є кореневі дозволи на цій машині, ви можете тимчасово збільшити "максимальну кількість відкритих дескрипторів файлів":

ulimit -Hn 10240 # The hard limit
ulimit -Sn 10240 # The soft limit

І потім

paste res.* >final.res

Після цього ви можете повернути його до початкових значень.


Друге рішення , якщо ви не можете змінити обмеження:

for f in res.*; do cat final.res | paste - $f >temp; cp temp final.res; done; rm temp

Він викликає pasteкожен файл один раз, і в кінці є величезний файл з усіма стовпцями (це займає його хвилину).

Редагувати : Марне використання кота ... Ні !

Як зазначалося в коментарях, використання catтут ( cat final.res | paste - $f >temp) не є марним. При першому запуску циклу файл final.resще не існує. pasteТоді б не вдалося, і файл ніколи не заповнюється і не створюється. З моїм рішенням catне вдається вперше з No such file or directoryі pasteчитає зі stdin лише порожній файл, але він продовжується. Помилку можна ігнорувати.


Спасибі! Будь-яка ідея, як я можу перевірити, що таке початкові значення?
мати

Тільки ulimit -Snдля м'якої межі та ulimit -Hnдля жорсткої межі
хаос

Дякую, це частково працює. Однак, для іншого набору файлів , які я отримую наступне повідомлення про помилку: -bash: /usr/bin/paste: Argument list too long. Ідеї, як це вирішити? Вибачте, що турбуєте вас, хлопці.
мати

@mats здається, що ваше ядро ​​не дозволяє більше аргументів, ви можете перевірити це getconf ARG_MAX, ви можете збільшити це значення лише під час перекомпіляції ядра. Ви можете спробувати моє друге рішення?
хаос

2
Замість використання catкожного разу через цикл, ви можете почати зі створення порожнього final.resфайлу. Це, мабуть, гарна ідея в будь-якому випадку, якщо ви вже є final.resфайл.
Бармар

10

Якщо відповідь на хаос не застосовується (оскільки у вас немає необхідних дозволів), ви можете згрупувати pasteвиклики наступним чином:

ls -1 res.* | split -l 1000 -d - lists
for list in lists*; do paste $(cat $list) > merge${list##lists}; done
paste merge* > final.res

У цьому списку перераховані файли 1000 в той час , в файли з іменами lists00, і lists01т.д., а потім вставляє відповідні res.файли у файли з ім'ям merge00, і merge01т.д., і , нарешті , об'єднує всі отримані частково об'єднані файли.

Як згадує хаос, ви можете збільшити кількість файлів, які використовуються відразу; обмеження - це значення, яке задається ulimit -nмінусом, однак багато файлів, які ви вже відкрили, так що ви скажете

ls -1 res.* | split -l $(($(ulimit -n)-10)) -d - lists

використовувати межу мінус десять.

Якщо ваша версія splitне підтримує -d, ви можете її видалити: все, що вона робить, - splitце використовувати числові суфікси. За замовчуванням суфікси буде aa, і abт.д. , а не 01, і 02т.д.

Якщо стільки файлів ls -1 res.*не виходить ("список аргументів занадто довгий"), ви можете замінити його, findщоб уникнути цієї помилки:

find . -maxdepth 1 -type f -name res.\* | split -l 1000 -d - lists

(Як вказував don_crissti , -1це не повинно бути необхідним при lsвиведенні трубопроводів ; але я залишаю це для обробки випадків, коли lsвони відчужені -C.)


4

Спробуйте виконати це таким чином:

ls res.*|xargs paste >final.res

Ви також можете розділити партію на частини і спробувати щось на кшталт:

paste `echo res.{1..100}` >final.100
paste `echo res.{101..200}` >final.200
...

і в кінці комбінуйте остаточні файли

paste final.* >final.res

@ Ромео Нінов Це дає ту саму помилку, яку я зазначив у своєму первинному запитанні:Too many open files
мати

@mats, у такому випадку ви вирішили розділити партію на частини. Відредагую мою відповідь, щоб дати вам ідею
Ромео Нінов

Право, @StephenKitt, я редагую свою відповідь
Ромео Нінов,

Щоб уникнути тимчасових файлів, спробуйте створити final.x00be pipe - або як названі FIFO, або неявно, використовуючи підстановку процесу (якщо ваша оболонка підтримує це, наприклад, bash). Це не приємно писати від руки, але цілком може підійти до файлу.
Toby Speight

4
i=0
{ paste res.? res.?? res.???
while paste ./res."$((i+=1))"[0-9][0-9][0-9]
do :; done; } >outfile

Я не думаю, що це так складно, як усе - ви вже зробили важку роботу, замовивши імена файлів. Просто не відкривайте їх усі одночасно, це все.

Інший спосіб:

pst()      if   shift "$1"
           then paste "$@"
           fi
set ./res.*
while  [ -n "${1024}" ] ||
     ! paste "$@"
do     pst "$(($#-1023))" "$@"
       shift 1024
done >outfile

... але я думаю, що це робить їх назад ... Це може працювати краще:

i=0;  echo 'while paste \'
until [ "$((i+=1))" -gt 1023 ] &&
      printf '%s\n' '"${1024}"' \
      do\ shift\ 1024 done
do    echo '"${'"$i"'-/dev/null}" \'
done | sh -s -- ./res.* >outfile

І ось ще один спосіб:

tar --no-recursion -c ./ |
{ printf \\0; tr -s \\0; }    |
cut -d '' -f-2,13              |
tr '\0\n' '\n\t' >outfile

Це дозволяє tarзібрати всі файли в поточний відмінений для вас розмір, аналізує всі метадані заголовка, крім назви файлу, та перетворює всі рядки у всіх файлах на вкладки. Хоча він покладається на те, що вхід є фактичними текстовими файлами - це означає, що кожен закінчується в новому рядку, і у файлах немає нульових байтів. Про - і це також залежить від імен файлів , самі будучи новим рядком вільної (хоча це може бути оброблено робастний з GNU tar«s --xformваріантом) . Враховуючи ці умови, він повинен зробити дуже коротку роботу з будь-якої кількості файлів - і tarзробить майже все це.

Результат - це набір ліній, які мають вигляд:

./fname1
C1\tC2\tC3...
./fname2
C1\tC2\t...

І так далі.

Я перевірив це, спершу створивши 5 тестових файлів. Мені зараз не дуже хотілося створювати 10000 файлів, тому я просто побільшав для кожного - а також переконався, що довжина файлів відрізняється значною мірою. Це важливо при тестуванні tarсценаріїв, оскільки tarбуде блокувати введення фіксованої довжини - якщо ви не спробуєте принаймні кілька різних довжин, ви ніколи не дізнаєтесь, чи будете ви реально обробляти лише один.

У будь-якому випадку для тестових файлів я зробив:

for f in 1 2 3 4 5; do : >./"$f"
seq "${f}000" | tee -a [12345] >>"$f"
done

ls згодом повідомили:

ls -sh [12345]
68K 1 68K 2 56K 3 44K 4 24K 5

... тоді я побіг ...

tar --no-recursion -c ./ |
{ printf \\0; tr -s \\0; }|
cut -d '' -f-2,13          |
tr '\0\n' '\n\t' | cut -f-25

... просто показати лише перші 25 полів з обмеженими вкладками на рядок (адже кожен файл - це один рядок - їх дуже багато ) ...

Вихід був:

./1
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./2
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./3
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./4
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25
./5
1    2    3    4    5    6    7    8    9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25

4

Враховуючи кількість файлів, розміри рядків тощо, я думаю, що це перевершить типові розміри інструментів (awk, sed, paste, * тощо)

Я створив би для цього невелику програму, вона не мала б відкрити ні 10 000 файлів, ні довжину рядка в сотні тисяч (10 000 файлів з 10 (максимальний розмір рядка в прикладі)). Потрібно лише ~ 10000 масивів цілих чисел, щоб зберігати кількість байтів, прочитаних з кожного файлу. Недоліком є ​​те, що він має лише один дескриптор файлу, він повторно використовується для кожного файлу, для кожного рядка, і це може бути повільним.

Визначення FILESта ROWSповинні бути змінені на фактичні точні значення. Вихід направляється на стандартний вихід.

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

#define FILES 10000 /* number of files */
#define ROWS 500    /* number of rows  */

int main() {
   int positions[FILES + 1];
   FILE *file;
   int r, f;
   char filename[100];
   size_t linesize = 100;
   char *line = (char *) malloc(linesize * sizeof(char));

   for (f = 1; f <= FILES; positions[f++] = 0); /* sets the initial positions to zero */

   for (r = 1; r <= ROWS; ++r) {
      for (f = 1; f <= FILES; ++f) {
         sprintf(filename, "res.%d", f);                  /* creates the name of the current file */
         file = fopen(filename, "r");                     /* opens the current file */
         fseek(file, positions[f], SEEK_SET);             /* set position from the saved one */
         positions[f] += getline(&line, &linesize, file); /* reads line and saves the new position */
         line[strlen(line) - 1] = 0;                      /* removes the newline */
         printf("%s ", line);                             /* prints in the standard ouput, and a single space */
         fclose(file);                                    /* closes the current file */
      }
      printf("\n");  /* after getting the line from each file, prints a new line to standard output */
   }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.