Вложенные циклы, если условия и фоновые задания в bash

У меня есть функция извлечения bash, которую я пытаюсь распараллелить. Его задача – найти и извлечь вложенные архивы. В идеале, я хочу, чтобы оценка if и все ее действия были отправлены на задний план. Уловка состоит в том, что элементы действия оценки if необходимо выполнить по порядку, поэтому я не могу просто добавить «&». к командам в if. Есть ли способ инкапсулировать всю оценку if в одно фоновое задание и выполнить команды в порядке?

Вот текущая рабочая функция:

extract () { IFS=$'\n' trap exit SIGINT SIGTERM for ext in zip rar tar.gz tar.bz2 tbz tgz 7z tar; do while [ "`find . -type f -iname "*.$ext" | wc -l`" -gt 0 ]; do for z in `find . -type f -iname "*.$ext"`; do if [ ! -d "`echo "$z" | rev | cut -c$(expr ${#ext} + 2)- | rev`" ]; then echo "Extracting `basename "$z"` ..." mkdir -p `echo "$z" | rev | cut -c$(expr ${#ext} + 2)- | rev` if [[ "$z" =~ ^.*\.7z$ ]]; then 7z x "$z" -o"`echo "$z" | rev | cut -c$(expr ${#ext} + 2)- | rev`" > /dev/null elif [[ "$z" =~ ^.*\.zip$ ]]; then unzip -uoLq "$z" -d `echo "$z" | rev | cut -c$(expr ${#ext} + 2)- | rev` 2>&1 | grep -ive warning elif [[ "$z" =~ ^.*\.tar\.xz$ ]] || [[ "$z" =~ ^.*\.tar\.gz$ ]] || [[ "$z" =~ ^.*\.tar\.bz2$ ]] || [[ "$z" =~ ^.*\.tgz$ ]] || [[ "$z" =~ ^.*\.tbz$ ]] || [[ "$z" =~ ^.*\.tar$ ]] ; then tar -xaf "$z" -C `echo "$z" | rev | cut -c$(expr ${#ext} + 2)- | rev` elif [[ "$z" =~ ^.*\.rar$ ]]; then unrar x -y -o+ "$z" `echo "$z" | rev | cut -c$(expr ${#ext} + 2)- | rev` fi rm -f "$z" else echo "Omitting `basename "$z"`, directory with that name already exists."; rm -f "$z" fi done done done } 

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

Почему вы запускаете ту же команду find несколько раз, дважды для каждого расширения? Вы можете просто создать единую команду find, которая будет перемещаться только по дереву каталогов один раз:

 EXT_REGEX='.*(zip|rar|tar.gz|tar.bz2|tbz|tgz|7z|tar)$' find . -regextype posix-egrep -iregex $EXT_REGEX - EXT_REGEX='.*(zip|rar|tar.gz|tar.bz2|tbz|tgz|7z|tar)$' find . -regextype posix-egrep -iregex $EXT_REGEX 

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

Во-вторых, ваш код разбит на имена файлов с пробелами. Вы можете исправить это, добавив

 IFS='' 

(для остановки for z in ... разбиение выходного файла на пробелы).

Наконец, если вы нажмете & в конце каждой из ветвей if/elif , они будут работать параллельно.

BTW, каковы все echo "$z" | rev echo "$z" | rev должен был выполнить? Вы каким-то образом получили многострочные имена файлов?

Благодаря советам @Useless и @Orion, я теперь избил функцию в подчинение. Теперь он генерирует все выделения в фоновом режиме, больше не удаляет исходные файлы и более чем на 25% быстрее для меня, чем его предшественник. @ Gilles отметил, что распараллеливание не для всех, так как это довольно дорогостоящее хранение. Это было лучше для меня, хотя, и если вы обнаружите, что можете использовать этот скрипт, я предоставлю его ниже:

 extract () { # Extracts all archives and any nested archives of specified directory into a new child directory named after the archive. IFS=$'\n' trap "rm $skipfiles ; exit" SIGINT SIGTERM shopt -s nocasematch # Allows case-insensitive regex matching echo -e "\n=====Extracting files=====" skipfiles=`mktemp` ; echo -e '\e' > $skipfiles # This creates a temp file to keep track of files that are already processed. Because of how it is read by grep, it needs an initial search string to omit from the found files. I opted for a literal escape character because who would name a file with that? while [ "`find "$1/" -type f -regextype posix-egrep -iregex '^.*\.(tar\.gz|tar\.bz2|tar\.xz|tar|tbz|tgz|zip|rar|7z)$' | grep -ivf $skipfiles | wc -l`" -gt 0 ]; do #The while loop ensures that nested archives will be extracted. Its find operation needs to be separate from the find for the for loop below because it will change. for z in `find "$1/" -type f -regextype posix-egrep -iregex '^.*\.(tar\.gz|tar\.bz2|tar\.xz|tar|tbz|tgz|zip|rar|7z)$' | grep -ivf $skipfiles`; do destdir=`echo "$z" | sed -r 's/\.(tar\.gz|tar\.bz2|tar\.xz|tar|tbz|tgz|zip|rar|7z)$//'` # This removes the extension from the source filename so we can extract the files to a new directory named after the archive. if [ ! -d "$destdir" ]; then echo "Extracting `basename $z` into `basename $destdir` ..." mkdir -p "$destdir" if [[ "$z" =~ ^.*\.7z$ ]]; then 7z x "$z" -o"$destdir" > /dev/null & elif [[ "$z" =~ ^.*\.rar$ ]]; then unrar x -y -o+ "$z" "$destdir" & elif [[ "$z" =~ ^.*\.zip$ ]]; then unzip -uoLq "$z" -d "$destdir" 2>/dev/null & elif [[ "$z" =~ ^.*\.(tar\.gz|tar\.bz2|tar\.xz|tar|tbz|tgz)$ ]] ; then tar -xaf "$z" -C "$destdir" & fi echo `basename "$z"` >> $skipfiles # This adds the name of the extracted file to the omission list for the next pass. else echo "Omitting `basename $z`, directory with that name already exists."; echo `basename "$z"` >> $skipfiles # Same as last line fi done wait # This will wait for all files in this pass to finish extracting before the next one. done rm "$skipfiles" # Removes temporary file }