Как отсортировать файлы в команде вставки с 500 файлами CSV - PullRequest
0 голосов
/ 06 января 2019

Мой вопрос похож на Как отсортировать файлы в команде вставки? - что решено.

У меня есть 500 CSV-файлов (данные о суточных осадках) в папке с соглашением об именах chirps_yyyymmdd.csv . Каждый файл имеет только 1 столбец (значение осадков) с 100 000 строк и без заголовка. Я хочу объединить все файлы CSV в один CSV в хронологическом порядке.

Когда я попробовал этот скрипт ls -v file_*.csv | xargs paste -d, только с 100 CSV-файлами, он работал. Но при попытке использовать 500 CSV-файлов я получил эту ошибку: paste: chirps_19890911.csv: Too many open files

Как обработать вышеуказанную ошибку?

Для быстрого решения я могу разделить CSV-файлы на две папки и выполнить процесс, используя приведенный выше скрипт. Но проблема в том, что у меня 100 папок, и в каждой папке 500 csv.

Спасибо

Пример данных и ожидаемый результат: https://www.dropbox.com/s/ndofxuunc1sm292/data.zip?dl=0

Ответы [ 5 ]

0 голосов
/ 07 января 2019

Вы можете попробовать этот лайнер Perl-one. Он будет работать для любого количества файлов, соответствующих * .csv в каталоге

$ ls -1 *csv
file_1.csv
file_2.csv
file_3.csv
$ cat file_1.csv
1
2
3
$ cat file_2.csv
4
5
6
$ cat file_3.csv
7
8
9

$ perl -e  ' BEGIN { while($f=glob("*.csv")) { $i=0;open($FH,"<$f"); while(<$FH>){ chomp;@t=@{$kv{$i}}; push(@t,$_);$kv{$i++}=[@t];}} print join(",",@{$kv{$_}})."\n" for(0..$i) } '                                                                              <
1,4,7
2,5,8
3,6,9

$
0 голосов
/ 07 января 2019

Ошибка исходит от ulimit, от man ulimit :

-n или --file-descriptor-count Максимальное количество открытых файловых дескрипторов

В моей системе ulimit -n возвращает 1024.

К счастью, мы можем вставить вывод пасты, чтобы связать его.

find . -type f -name 'file_*.csv' | 
sort | 
xargs -n$(ulimit -n) sh -c '
     tmp=$(mktemp); 
     paste -d, "$@" >$tmp; 
     echo $tmp
' -- |
xargs sh -c '
     paste -d, "$@"
     rm "$@"
' --
  1. Не анализировать вывод ls
  2. Как только мы перешли от анализа ls к удачному поиску, мы находим все файлы и сортируем их.
  3. первый xargs принимает 1024 файла за раз, создает временный файл, вставляет вывод во временный файл и выводит имя файла временного файла
  4. Второй xargs делает то же самое с временными файлами, но также удаляет все временные файлы
  5. Поскольку число файлов будет 100 * 500 = 500000, что меньше 1024 * 1024, мы можем избежать одного прохода.
  6. Проверено по данным испытаний, созданным с помощью:

    seq 1 2000 |
    xargs -P0 -n1 -t sh -c '
        seq 1 1000 |
        sed "s/^/ $RANDOM/" \
        >"file_$(date --date="-${1}days" +%Y%m%d).csv"
    ' --
    
  7. Проблема, похоже, очень похожа на foldl с максимальным размером чанка, который можно сложить за один проход. По сути, мы хотим, чтобы paste -d, <(paste -d, <(paste -d, <1024 files>) <1023 files>) <rest of files> работал рекурсивно. С небольшим удовольствием я придумал следующее:

func() {
        paste -d, "$@"
}

files=()
tmpfilecreated=0

# read filenames...c
while IFS= read -r line; do

        files+=("$line")

        # if the limit of 1024 files is reached
        if ((${#files[@]} == 1024)); then
                tmp=$(mktemp)

                func "${files[@]}" >"$tmp"

                # remove the last tmp file
                if ((tmpfilecreated)); then
                        rm "${files[0]}"
                fi
                tmpfilecreated=1

                # start with fresh files list
                # with only the tmp file
                files=("$tmp")
        fi
done

func "${files[@]}"

# remember to clear tmp file!
if ((tmpfilecreated)); then
        rm "${files[0]}"
fi

Я думаю, readarray / mapfile может быть быстрее, и в результате получится немного более четкий код:

func() {
        paste -d, "$@"
}

tmp=()
tmpfilecreated=0
while readarray -t -n1023 files && ((${#files[@]})); do
        tmp=("$(mktemp)")

        func "${tmp[@]}" "${files[@]}" >"$tmp"

        if ((tmpfilecreated)); then
                rm "${files[0]}"
        fi
        tmpfilecreated=1
done

func "${tmp[@]}" "${files[@]}"

if ((tmpfilecreated)); then
        rm "${files[0]}"
fi

PS. I want to merge all the csv files into a single csv in chronological order. Разве это не было бы просто cut? Прямо сейчас каждый столбец представляет один день.

0 голосов
/ 06 января 2019

Вы можете сделать это с gawk вот так ...

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

Затем в конце выведите весь массив:

gawk 'BEGINFILE{ ++col }                        # New file, increment column number
               { X[FNR SEP col]=$0; rows=FNR }  # Save datum into array X, indexed by current record number and col
      END      { for(r=1;r<=rows;r++){
                    comma=","
                    for(c=1;c<=col;c++){
                       if(c==col)comma=""
                       printf("%s%s",X[r SEP c],comma)
                    }
                    printf("\n")
                 }
               }' chirps*

SEP - это просто неиспользуемый символ, который разделяет индексы. Я использую gawk, потому что BEGINFILE полезен для увеличения номера столбца.


Сохраните все вышеперечисленное в вашей домашней директории как merge. Затем запустите терминал и, только один раз, сделайте его исполняемым с помощью команды:

chmod +x merge

Теперь перейдите в каталог, в котором находятся ваши щебетания, с помощью команды:

cd subdirectory/where/chirps/are

Теперь вы можете запустить скрипт с:

$HOME/merge

Вывод будет проноситься мимо экрана. Если вы хотите это в файле, используйте:

$HOME/merge > merged.csv
0 голосов
/ 07 января 2019

Если целью является файл с 100 000 строками и 500 столбцами, то что-то вроде этого должно работать:

paste -d, chirps_*.csv > chirps_500_merge.csv

Дополнительный код можно использовать для сортировки входных файлов chirps _... в любом желаемом порядке перед paste ing.

0 голосов
/ 06 января 2019

Сначала создайте один файл без вставки и измените этот файл на oneliner с помощью tr:

cat */chirps_*.csv | tr "\n" "," > long.csv
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...