Как разбить файл и сохранить первую строку в каждой части?

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

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

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

Ответы

Ответ 1

Это скрипт Робруски, немного исправленный:

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" например.

Ответ 2

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

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

Ответ 3

Вы можете использовать [мг] 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

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

$> 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" на желаемый размер разделяемой линии.

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Здесь моя версия:

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

Отличия:

  • in_file - это аргумент файла, который вы хотите разделить на поддерживающие заголовки.
  • Используйте awk вместо tail из-за awk с лучшей производительностью
  • разбивается на 100 000 строк, а не на 4
  • Имя разделенного файла будет содержать имя файла, добавленное с помощью подчеркивания и цифр (до 99999 - из аргумента split -d-a 5).
  • Используйте mktemp для безопасного обращения с временными файлами
  • Используйте одиночную строку head | cat вместо двух строк

Ответ 10

Используйте 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

Ответ 11

Ниже представлен 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'.