Как разбить файл и сохранить первую строку в каждом из кусочков? - PullRequest
51 голосов
/ 11 сентября 2009

Дано: Один большой файл текстовых данных (например, формат CSV) со «специальной» первой строкой (например, имена полей).

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

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

Ответы [ 12 ]

46 голосов
/ 11 сентября 2009

Это Сценарий Робруски немного очищен:

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, cut, ls и 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 запускает команду (в данном случае функцию, которую необходимо экспортировать) для каждого выходного файла и задает для переменной FILE в среде команды имя файла.

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

12 голосов
/ 08 августа 2014

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

tail -n +2 FILE.in |
split -l 50 - --filter='sh -c "{ head -n1 FILE.in; cat; } > $FILE"'
10 голосов
/ 12 сентября 2009

Вы можете использовать [мг] 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 - количество строк каждого среза. Он не требует временных файлов и может быть помещен в одну строку.

4 голосов
/ 11 сентября 2009

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

$> 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 по умолчанию xa* выходной формат. Также замените «4» на желаемый размер разделенной линии.

3 голосов
/ 30 октября 2018

Это разделит большой CSV на части по 999 строк с заголовком вверху каждой

cat bigFile.csv | parallel --header : --pipe -N999 'cat >file_{#}.csv'

Основано на ответе Оле Танге. (повторно ответ Оле: Вы не можете использовать количество строк с pipepart)

2 голосов
/ 12 сентября 2009

Это более надежная версия сценария Дениса Уильямсона . Сценарий создает много временных файлов, и было бы стыдно, если бы они оставались без дела, если запуск был неполным. Итак, давайте добавим перехват сигнала (см. 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 в строке прерывания. См. Страницу руководства по сигналам для получения дополнительных сигналов для захвата.

1 голос
/ 02 июня 2019

Вдохновленный комментарием @ Arkady к одной строке.

  • MYFILE переменная просто уменьшить шаблон
  • split не показывает имя файла, но опция --additional-suffix позволяет нам легко контролировать то, что ожидать
  • удаление промежуточных файлов через rm $part (предполагается, что нет файлов с таким же суффиксом)

MYFILE=mycsv.csv && for part in $(split -n4 --additional-suffix=foo $MYFILE; ls *foo); do cat <(head -n1 $MYFILE) $part > $MYFILE.$part; rm $part; done

Данные:

-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xaafoo
-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xabfoo
-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xacfoo
-rw-rw-r--  1 ec2-user ec2-user  32040110 Jun  1 23:18 mycsv.csv.xadfoo

и, конечно, head -2 *foo добавлен заголовок.

1 голос
/ 12 ноября 2018

Ниже представлен 4 вкладыш, который можно использовать для сохранения заголовка csv (используя: head, split, find, grep, xargs и sed)


csvheader=`head -1 bigfile.csv`
split -d -l10000 bigfile.csv smallfile_
find .|grep smallfile_ | xargs sed -i "1s/^/$csvheader\n/"
sed -i '1d' smallfile_00

Пояснение:

  • Захватить заголовок в переменную с именем csvheader
  • Разделить большой файл на несколько файлов меньшего размера (с префиксом smallfile _)
  • Найдите всех маленьких файлов и вставьте csvheader в первую строку, используя xargs и sed -i . Обратите внимание, что вам нужно использовать sed в двойных кавычках, чтобы использовать переменные.
  • Первый файл с именем smallfile_00 теперь будет иметь избыточные заголовки в строках 1 и 2 (из исходных данных, а также из вставки заголовка sed в шаге 3). Мы можем удалить избыточный заголовок с помощью команды sed -i '1d'.
1 голос
/ 21 февраля 2018

Использовать 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
1 голос
/ 30 января 2015

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

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

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 строку вместо двух
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...