Рекомендуемый способ пакетной передачи строк stdin в другую повторяющуюся команду, например xargs, но через stdin, а не аргументы?
У меня есть скрипт импорта данных, который читает строки и добавляет их в базу данных, пока все хорошо. К сожалению, что-то в скрипте (или его среда выполнения, или библиотека базы данных, или что-то еще) является утечкой памяти, поэтому большие объемы импорта используют монотонно увеличивающуюся основную память, что приводит к медленной подкачке, а затем к смерти процесса с исчерпанной памятью. Разбиение импорта на несколько запусков-это обходной путь; я делал это с помощью split
, а затем выполнял циклическое выполнение сценария импорта на каждом фрагменте.
Но Я бы предпочел пропустить создание разделенных файлов, и это похоже на то, что это должно быть 1-liner. На самом деле, кажется, должен быть эквивалент xargs
, который передает строки в указанную команду на stdin, а не в качестве аргументов. Если бы эта гипотетическая команда была xlines
, то я ожидал бы, что следующий сценарий будет выполняться myimport
для каждой партии до 50 000 строк в giantfile.txt:
cat giantfile.txt | xlines -L 50000 myimport
Я пропустил xlines
-подобную возможность под каким-то другим именем или скрытую в какой-то другой команде варианты? Или можно xlines
сделать в нескольких строках сценария BASH?
3 ответа:
Используйте GNU Parallel-available здесь.
Вам понадобится опция
--pipe
, а также опция--block
(которая принимает размер байта, а не количество строк).Что-то вроде:
cat giantfile.txt | parallel -j 8 --pipe --block 4000000 myimport
(это выбор размера блока в 50 000 строк * 80 байт = 4000000, который также может быть сокращен
Если вы не хотите, чтобы задания выполнялись параллельно, измените4m
здесь.)8
на1
. Или вы можете оставить его совсем, и он будет работать одно задание на ядро процессора.Вы также можете избежать
cat
, запустивparallel ... < giantfile.txt
Сохраните следующий код как test.sh сценарий.
#!/bin/bash tempFile=/tmp/yourtempfile.temp rm -f tempFile > /dev/null 2>&1 declare -i cnt=0 while read line do cnt=$(($cnt+1)) if [[ $cnt < $1 || $cnt == $1 ]]; then echo $line >> tempFile else echo $line >> tempFile cat tempFile | myimport rm -f tempFile > /dev/null 2>&1 cnt=$((0)) fi done < $2 exit 0
Затем запустите
./test.sh 500000 giantfile.txt
. Я использую tempFile для сохранения указанного количества строк,а затем использую ваш скрипт импорта, занимающийся этим. Надеюсь, это поможет.
Мой подход, без установки
parallel
, и без записи временных файлов:#!/bin/bash [ ! -f "$1" ] && echo "missing file." && exit 1 command="$(which cat)" # just as example, insert your command here totalSize="$(wc -l $1 | cut -f 1 -d ' ')" chunkSize=3 # just for the demo, set to 50000 in your version offset=1 while [ $[ $totalSize + 1 ] -gt $offset ]; do tail -n +$offset $1 | head -n $chunkSize | $command let "offset = $offset + $chunkSize" echo "----" done
Тест:
seq 1000 1010 > testfile.txt ./splitter.sh testfile.txt
Вывод:
1000 1001 1002 ---- 1003 1004 1005 ---- 1006 1007 1008 ---- 1009 1010 ----
Таким образом, решение остается переносимым, и производительность лучше, чем с временными файлами.