2009-09-11 2 views
37

Дано: Один большой файл текстовых данных (например, формат CSV) со специальной линией (например, именами полей).Как разбить файл и сохранить первую строку в каждой части?

Требуется: Эквивалент Coreutils split -l команды, но с дополнительным требованием, чтобы строка заголовка из исходного файла появится в начале каждого из полученных кусков.

Я предполагаю, что какая-то смесь split и head будет делать трюк?

+8

Это кажется разумным, что кто-то следует добавить, что в качестве встроенного в особенности 'split' , не так ли? –

+1

Вероятно, самый большой фактор * против *, который становится встроенным, заключается в том, что вы обычно восстанавливаете разделенный файл, выполняя 'cat a b c> rebructed'. Посторонние строки в файле означают, что обычный подход к восстановлению не воспроизводит исходный файл. –

+2

Это то, что нужно для предстоящей (* not *) "' unsplit -remove-header' "утилиты! Но серьезно, 'split', если бы он имел параметр« повторить заголовок », должен по умолчанию по-прежнему выполнять свое текущее поведение. Если бы вы действительно этого хотели, вы использовали бы только заголовок. –

ответ

32

Это сценарий robhruska по очистке немного:

tail -n +2 file.txt | split -l 4 - split_ 
for file in split_* 
do 
    head -n 1 file.txt > tmp_file 
    cat $file >> tmp_file 
    mv -f tmp_file $file 
done 

Я удалил wc, cutls, и echo в тех местах, где они не нужны. Я изменил некоторые имена файлов, чтобы сделать их немного более значимыми. Я разбил его на несколько строк, чтобы упростить чтение.

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

Редактировать

Использование GNU split это можно сделать:

split_filter() { { head -n 1 file.txt; cat; } > "$FILE"; }; export -f split_filter; tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_ 

Разразившийся для читаемости:

split_filter() { { head -n 1 file.txt; cat; } > "$FILE"; } 
export -f split_filter 
tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_ 

Когда --filter указано, split запускает команду (а функция в этом случае, которая должна быть экспортирована) для каждого выходного файла и устанавливает v допустимый FILE, в среде команды, к имени файла.

Скрипт или функция фильтра могут выполнять любые манипуляции, необходимые для содержимого вывода или даже имени файла. Примером последнего может быть вывод в фиксированное имя файла в каталоге переменных: например, > "$FILE/data.dat".

+0

Это, безусловно, будет работать. Я просто надеялся на какой-то гладкий однострочный шрифт, например 'for $ part in (split -l 1000 myfile); cat <(head -n1 myfile) $ part> myfile. $ part; done' – Arkady

+0

Это не может работать, потому что 'split', по необходимости, не выводится на' stdout'. –

+0

'split' * может * выводить * имена * файлов на stdout, хотя (пока мы обсуждаем, что' split' * ought * делать :-) – Arkady

4

Я новичок, когда дело доходит до Баш-фу, но я смог придумать это чудовище с двумя командами. Я уверен, что есть более элегантные решения.

$> tail -n +2 file.txt | split -l 4 
$> for file in `ls xa*`; do echo "`head -1 file.txt`" > tmp; cat $file >> tmp; mv -f tmp $file; done 

Это предполагает, что ваш входной файл file.txt, вы не используете prefix аргумент split, и вы работаете в директории, которая не имеет каких-либо других файлов, которые начинаются с невыполнением split «s xa* формат вывода. Кроме того, замените «4» на желаемый размер разделенной линии.

1

Я никогда не был уверен в правилах копирования скриптов прямо с сайтов других людей, но у Geekology есть хороший сценарий, чтобы делать то, что вы хотите, с несколькими комментариями, подтверждающими, что он работает. Обязательно сделайте tail-n+2 как отмечено в комментарии у основания.

2

Это более надежная версия Скрипт Дениса Уильямсона. Сценарий создает много временных файлов, и было бы позором, если бы они оставались лежащими, если пробег был неполным. Итак, давайте добавим захват сигнала (см. http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html, а затем http://tldp.org/LDP/abs/html/debugging.html) и удалите наши временные файлы; это наилучшая практика.

trap 'rm split_* tmp_file ; exit 13' SIGINT SIGTERM SIGQUIT 
tail -n +2 file.txt | split -l 4 - split_ 
for file in split_* 
do 
    head -n 1 file.txt > tmp_file 
    cat $file >> tmp_file 
    mv -f tmp_file $file 
done 

Замените «13» на любой код возврата. О, и вы, вероятно, должны использовать mktemp в любом случае (как некоторые из них уже предложили), поэтому продолжайте и удалите «tmp_file» из rm в линии ловушки. См. Страницу управления сигналами для получения большего количества сигналов.

8

Вы можно использовать [мг] AWK:

awk 'NR==1{ 
     header=$0; 
     count=1; 
     print header > "x_" count; 
     next 
    } 

    !((NR-1) % 100){ 
     count++; 
     print header > "x_" count; 
    } 
    { 
     print $0 > "x_" count 
    }' file 

100 это число строк каждого ломтика Он не требует временных файлов и могут быть помещены в одной строке

+0

Upvoting для обучения чему-то новому, но если я собираюсь написать небольшой скрипт, я мог бы также сделать это в Perl или Python :-) – Arkady

5

Вы можете использовать новый.. - функциональность фильтра в GNU coreutils split> = 8.13 (2011):

tail -n +2 FILE.in | 
split -l 50 - --filter='sh -c "{ head -n1 FILE.in; cat; } > $FILE"' 
+1

Мне нравится версия с одним слоем. Чтобы сделать его более общим для bash, я сделал: 'tail -n +2 FILE.in | split -d -lines 50 - --filter = 'bash -c "{head -n1 $ {FILE%. *}; cat;}> $ FILE"' FILE.in.x' – KullDox

1

мне понравились версия AWK Марко, принятая от этого упрощенного однострочника, где вы можете легко определить разделительные фракции в качестве зернистого, как вы хотите:

awk 'NR==1{print $0 > FILENAME ".split1"; print $0 > FILENAME ".split2";} NR>1{if (NR % 10 > 5) print $0 >> FILENAME ".split1"; else print $0 >> FILENAME ".split2"}' file 
+0

Мне нравится это решение, однако он ограничен только двумя разделенными файлами. – Bas

+0

Если вам нравится, для него есть функция upvote;) Его можно легко настроить для большего количества файлов, но да, это не так гибко, как split -l – DreamFlasher

+0

«один лайнер» ... pshh – Pandem1c

1

мне очень понравились Робы и Деннис версия, настолько, что я хотел их улучшить.

Вот моя версия:

in_file=$1 
awk '{if (NR!=1) {print}}' $in_file | split -d -a 5 -l 100000 - $in_file"_" # Get all lines except the first, split into 100,000 line chunks 
for file in $in_file"_"* 
do 
    tmp_file=$(mktemp $in_file.XXXXXX) # Create a safer temp file 
    head -n 1 $in_file | cat - $file > $tmp_file # Get header from main file, cat that header with split file contents to temp file 
    mv -f $tmp_file $file # Overwrite non-header containing file with header-containing file 
done 

Отличие:

  1. in_file является файл аргументом вы хотите разделить сохранение заголовков
  2. Использования awk вместо tail благодаря awk, имеющим более высокой производительности
  3. разбит на 100 000 файлов строк вместо 4
  4. Сплит имя файла будет введено имя файла добавляется символ подчеркивания и цифры (до 99999 - от «-d -a 5» разделенного аргумента)
  5. Использование Mktemp безопасно обрабатывать временные файлы
  6. Использование одного head | cat линии вместо двух линий
0

Использование GNU Parallel:

parallel -a bigfile.csv --header : --pipepart 'cat > {#}' 

Если вам необходимо выполнить команду на каждой из частей, то GNU Parallel может помочь сделать это, тоже:

parallel -a bigfile.csv --header : --pipepart my_program_reading_from_stdin 
parallel -a bigfile.csv --header : --pipepart --fifo my_program_reading_from_fifo {} 
parallel -a bigfile.csv --header : --pipepart --cat my_program_reading_from_a_file {} 

Если вы хотите разделить на 2 части на ядро ​​процессора (например,24 сердечников = 48 размера равных частей):

parallel --block -2 -a bigfile.csv --header : --pipepart my_program_reading_from_stdin 

Если вы хотите разбить на 10 МБ блоков:

parallel --block 10M -a bigfile.csv --header : --pipepart my_program_reading_from_stdin 
Смежные вопросы