используя параллель для обработки уникальных входных файлов в уникальные выходные файлы

18

У меня проблема со сценариями оболочки, когда у меня есть каталог, полный входных файлов (каждый файл содержит много входных строк), и мне нужно обрабатывать их индивидуально, перенаправляя каждый из их выходных данных в уникальный файл (иначе, file_1.input needs). быть захваченным в file_1.output и т. д.).

Предварительно , я бы просто перебирал каждый файл в каталоге и выполнял свою команду, выполняя какую-то технику таймера / подсчета, чтобы не перегружать процессоры (предполагая, что каждый процесс имел постоянное время выполнения). Однако я знаю, что это не всегда так, поэтому использование «параллельного» решения кажется лучшим способом получить многопоточность сценария оболочки без написания специального кода.

Хотя я подумал о некоторых способах параллельного запуска для обработки каждого из этих файлов (и позволяющего мне эффективно управлять своими ядрами), все они кажутся хакерскими. У меня есть то, что я считаю довольно простым вариантом использования, поэтому я предпочел бы сохранить его как можно более чистым (и ничто в параллельных примерах, кажется, не выпрыгивает из моей проблемы).

Любая помощь будет оценена!

Пример входного каталога:

> ls -l input_files/
total 13355
location1.txt
location2.txt
location3.txt
location4.txt
location5.txt

Автор сценария:

> cat proces_script.sh
#!/bin/sh

customScript -c 33 -I -file [inputFile] -a -v 55 > [outputFile]

Обновление : после прочтения ответа Оле ниже, я смог собрать недостающие фрагменты для моей собственной параллельной реализации. Хотя его ответ хорош, вот мое дополнительное исследование и заметки, которые я сделал:

Вместо того чтобы запускать весь процесс, я решил начать с команды проверки концепции, чтобы доказать его решение в моей среде. Смотрите мои две разные реализации (и заметки):

find /home/me/input_files -type f -name *.txt | parallel cat /home/me/input_files/{} '>' /home/me/output_files/{.}.out

Использует find (не ls, это может вызвать проблемы), чтобы найти все применимые файлы в моем каталоге входных файлов, а затем перенаправляет их содержимое в отдельный каталог и файл. Моей проблемой сверху было чтение и перенаправление (фактический скрипт был прост), поэтому замена скрипта на cat была хорошим доказательством концепции.

parallel cat '>' /home/me/output_files/{.}.out :::  /home/me/input_files/*

Это второе решение использует парадигму входной переменной параллельного интерфейса для чтения файлов, однако для новичка это было гораздо более запутанным. Для меня использование find и pipe отвечает моим потребностям.

Джонс
источник

Ответы:

27

GNU Parallel предназначен для решения таких задач:

parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output ::: *.input

или:

ls | parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output

Он будет запускать одну работу на ядро ​​процессора.

Вы можете установить GNU Parallel просто:

wget https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem

Посмотрите вступительные видео для GNU Parallel, чтобы узнать больше: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Оле Танге
источник
Отличный ответ (и основные моменты для чтения моего запроса об использовании параллели).
Джонс Джонс
5

Стандартный способ сделать это - настроить очередь и породить любое количество работников, которые знают, как извлечь что-то из очереди и обработать. Вы можете использовать fifo (он же именованный канал) для связи между этими процессами.

Ниже приведен наивный пример для демонстрации концепции.

Простой скрипт очереди:

#!/bin/sh
mkfifo /tmp/location-queue
for i in inputfiles/*; do
  echo $i > /tmp/location-queue
done
rm /tmp/location-queue

И рабочий:

#!/bin/sh
while read file < /tmp/location-queue; do
  process_file "$file"
done

process_file может быть определен где-то в вашем работнике, и он может делать все, что вам нужно.

Когда у вас есть эти две части, вы можете иметь простой монитор, который запускает процесс очереди и любое количество рабочих процессов.

Скрипт монитора:

#!/bin/sh
queue.sh &
num_workers="$1"
i=0
while [ $i < $num_workers ]; do
  worker.sh &
  echo $! >> /tmp/worker.pids
  i=$((i+1))
done
monitor_workers

Там у вас есть это. Если вы действительно делаете это, лучше настроить fifo на мониторе и передать путь как к очереди, так и к рабочим, чтобы они не были связаны и не привязывались к определенному месту для fifo. Я специально настроил это в ответе, чтобы было ясно, что вы используете, когда читаете это.

Шон Дж. Гофф
источник
Как монитор достаточно умен, чтобы приостановить порождение новых рабочих, пока не закончится следующий (иначе, где $ я когда-либо уменьшается)? ---- Отвечая на мое собственное редактирование, рабочие никогда не уходят, они просто обрабатывают файлы, пока вся обработка не будет исчерпана (отсюда и цикл while внутри «процессоров»).
Дж Джонс
Что выполняет строка «monitor_workers» в конце сценария монитора?
Дж Джонс
@JJones - monitor_workersэто как process_file... это функция, которая делает все, что вы хотите. Насчет монитора - ты был прав; он должен сохранить pids своих работников (чтобы он мог послать сигнал уничтожения), и счетчик должен быть увеличен при запуске работника. Я отредактировал ответ, чтобы включить это.
Шон Дж. Гофф
Я действительно ценю вашу работу, но я думаю, что вы должны использовать GNU parallel. Я думаю, что это ваша идея, полностью реализована.
motobói
5

Другой пример:

ls *.txt | parallel 'sort {} > {.}.sorted.txt'

Я нашел другие примеры излишне сложными, когда в большинстве случаев вы, возможно, искали вышеуказанное.

deceleratedcaviar
источник
4

Общедоступным инструментом, который может выполнять распараллеливание, является make. GNU make и несколько других имеют-j возможность выполнять параллельные сборки.

.SUFFIXES: .input .output
.input.output:
        process_one_file <$< >$@.tmp
        mv -f $@.tmp $@

Запустите makeкак это (я предполагаю, что ваши имена файлов не содержат никаких специальных символов, makeэто не хорошо):

make -j 4 $(for x in *.input; do echo ${x%.*}.output; done)
Жиль "ТАК - прекрати быть злым"
источник
imho это самое умное решение :)
h4unt3r
3

Это выполнить ту же команду для большого набора файлов в текущем каталоге:

#!/bin/sh
trap 'worker=`expr $worker - 1`' USR1  # free up a worker
worker=0  # current worker
num_workers=10  # maximum number of workers
for file in *.txt; do
    if [ $worker -lt $num_workers ]; then
        {   customScript -c 33 -I -file $file -a -v 55 > `basename $file .txt`.outtxt 
            kill -USR1 $$ 2>/dev/null  # signal parent that we're free
        } &
        echo $worker/$num_worker $! $file  # feedback to caller
        worker=`expr $worker + 1`
    else
        wait # for a worker to finish
    fi
done

Это запускает customScriptкаждый txtфайл, помещая вывод в outtxtфайлы. Измените как вам нужно. Ключом к тому, чтобы заставить это работать, является обработка сигнала с использованием SIGUSR1, чтобы дочерний процесс мог сообщить родительскому процессу, что это сделано. Использование SIGCHLD не будет работать, так как большинство операторов в сценарии будут генерировать сигналы SIGCHLD для сценария оболочки. Я попытался это заменить вашу команду на sleep 1, программа использовала 0,28 с процессора пользователя и 0,14 с процессора системы; это было только около 400 файлов.

Arcege
источник
Как «подождать» достаточно умно, чтобы взять тот же файл, который в данный момент перебирается, и снова ввести одноуровневое выражение «если»?
J Jones
Это не то, waitчто «достаточно умный»; но он вернется после получения SIGUSR1сигнала. Дочерний / рабочий отправляет SIGUSR1родительскому элементу a , который перехватывается ( trap), и уменьшает $worker( trapпредложение) и ненормально возвращается из wait, позволяя if [ $worker -lt $num_workers ]выполнить предложение.
Arcege
0

Или просто используйте xargs -P, не нужно устанавливать дополнительное программное обеспечение:

find . -type f -print0 | xargs -0 -I'XXX' -P4 -n1 custom_script -input "XXX" -output "XXX.out"

Немного объяснения вариантов:

  • -I'XXX' устанавливает строку, которая будет заменена в шаблоне команды именем файла
  • -P4 будет запускать 4 процесса параллельно
  • -n1 поместит только один файл за исполнение, хотя найдены два XXX
  • -print0и -0работать вместе, позволяя вам иметь специальные символы (например, пробелы) в именах файлов
Петр Чапла
источник