Рекомендуемый способ пакетной передачи строк 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 2

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
----

Таким образом, решение остается переносимым, и производительность лучше, чем с временными файлами.