Loops aninhados, se condições e trabalhos em segundo plano no bash

0

Eu tenho uma função de extração de bash que estou tentando paralelizar. Seu trabalho é encontrar e extrair arquivos aninhados. Idealmente, quero que a avaliação e todas as suas ações sejam enviadas para segundo plano. O problema é que os itens de ação da avaliação if precisam ser feitos em ordem, por isso não posso simplesmente adicionar um "&". para os comandos no if. Existe uma maneira de encapsular toda a avaliação if em um único job em background e ter os comandos executados em ordem?

Aqui está a função de trabalho atual:

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 
}

Além disso, estou curioso para saber se há alguma maneira de executar a extração sem excluir os arquivos de origem. Eu atualmente faço isso para evitar um loop infinito. Por enquanto, a função é confiável o suficiente para não perder nenhum dado, mas eu gostaria de evitar deletar qualquer coisa por segurança.

    
por MrDrMcCoy 05.08.2014 / 18:12

2 respostas

1

Graças aos conselhos de @Useless e @Orion, agora eu venci a função até a submissão. Agora, ele gera todas as extrações em segundo plano, não exclui mais os arquivos de origem e é 25% mais rápido para mim do que seu antecessor. @Gilles observou que a paralelização não é para todos, já que é um pouco caro para armazenamento. Porém, foi melhor para mim e, se você achar que pode usar esse script, eu o forneço abaixo:

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
}
    
por 06.08.2014 / 04:51
2

Por que você executa o mesmo comando find várias vezes, duas vezes para cada extensão? Você pode apenas gerar um único comando de localização que só irá percorrer a árvore de diretórios uma vez:

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

Agora, você não precisa de loops aninhados, e certamente não precisa do while que causou seu problema de loop infinito.

Em segundo lugar, seu código é quebrado para nomes de arquivos com espaços. Você pode corrigir isso adicionando

IFS=''

(para parar for z in ... dividindo a saída no espaço em branco).

Por fim, se você colocar um & no final de cada uma das suas if/elif filiais, elas serão executadas em paralelo.

BTW, quais são os echo "$z" | rev que deveriam realizar? Você estava, de alguma forma, recebendo nomes de arquivos com várias linhas?

    
por 05.08.2014 / 18:41

Tags