Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Basically, i have a set of directories that are constantly being filled with .rar's, and i need to be able to extract them in place with automatic deletion of the left over .(rar|rXX) files. How would i go about this?

Note: I can't delete all of them once done, they have to be deleted as the script completes one rar set.

Example Directory Structure:

/
/folder1/
        /file1.rar
        /file1.r00
        /file1.r01
/folder2/
        /sub.folder1/
                    /file2.part001.rar (contains a directory "file2")
                    /file2.part002.rar
                    /file2.part003.rar
        /sub.folder2/
                    /file3.rar
                    /file3.r00
                    /file3.r01

Expected Result:

/
/folder1/
        /file1.ext
/folder2/
        /sub.folder1/
                    /file2/
                          /file2.ext
        /sub.folder2/
                    /file3.ext
share|improve this question
1  
What have you tried so far? –  Oliver Salzburg Jun 3 '12 at 20:41
    
i used: find ./software-foo*/ -type f -name '*01.rar' -execdir unrar x {} \ but it's missing the deletion and filled my hard drive. –  Jharwood Jun 3 '12 at 20:43
    
Don't put the solution into the question please. Answer your own question as soon as you can (in about 7 hours) — this way, you can even accept it later on! –  slhck Jun 3 '12 at 21:42
    
" I can't delete all of them once done, they have to be deleted as the script completes one rar set." - Why exactly is this the case?" –  Ramhound Jun 4 '12 at 11:06
    
Disk would fill before it reached the end of processing –  Jharwood Jun 6 '12 at 8:04
add comment

2 Answers

up vote 2 down vote accepted

Variant for Bash:

#!/bin/bash
if [ ! -d "${1}" ]; then
    printf '%s: error: mandatory argument is not a valid directory.' "${0##*/}" 1>&2
    exit 1
fi

shopt -s extglob
for i in "${1}"/**/*.rar; do
    cd -- "${i%/*}"
    file=${i##*/}
    unrar e -- "${file}" && rm -- @(${file}|${file%.rar}.r[[:digit:]][[:digit:]])
    cd - >/dev/null
done

RAR archives can also "legally" be named e.g. myarchive.part001.rar and so on, and that would have to be handled separately in some way if those kinds of archives exist. A pragmatic way of solving this is to replace

unrar e -- "${file}" && rm -- @(${file}|${file%.rar}.r[[:digit:]][[:digit:]])

with

unrar e -o- -- "${file}"
if [ $? -eq 0 -o $? -eq 10 ]; then
    rm -- @(${file}|${file%.rar}.r[[:digit:]][[:digit:]])
fi

but be wary of that this will clean out RAR files even if error code 10 ("No files to extract") is given, and is thus not generally recommended in its unmodified form.


All in all, it might be better to simply have the material stored in the archived form and instead use on-the-fly decompression or something similar when you want to use it. There might be other benefits to keeping the archived format.

share|improve this answer
    
+1, but why not use for f in !(*.part[0-9][0-9][2-9].rar|!(*.rar)); do? That will catch non-partXXX.rar files and part001.rar files only and spare you the error prone exit code check. –  kopischke Jun 4 '12 at 9:01
1  
@kopischke: That would make that part better, but be at least just as error prone in other ways You assume .part###.rar, but they could just as well be .part##.rar or just part#.rar, and there are even more allowed naming schemes (archive.001 -> archive.002, etc.), and the suffix length depends on the number of archives (int(log10(#parts))+1, really) (i.e. it could also be .r101 which is not currently matched), so a general solution is more complex. This will still remain "just a quick hack", and the length one wants to go to handle special cases is up to the questioner. –  Daniel Andersson Jun 4 '12 at 10:00
    
The variable numeric part can be easily matched by nesting patterns: for f in !(*.part*([0-9])[2-9].*|!(*.rar)); do, but you are right that other legal naming schemes would need a more complex pattern. I’m still thinking finding a matching glob would probably be safer than exit code checking (better to miss some archives than delete stuff that shouldn’t be IMO) – but that decision is up to OP, agreed. –  kopischke Jun 4 '12 at 10:12
    
@kopischke: I did not know about the + operator in Bash extglob, thanks for the example. –  Daniel Andersson Jun 4 '12 at 10:15
    
Yup, extended globs, when nested, can be surprisingly powerful. I edited the comment to use the *() pattern (0 or more, instead of 1 or more – now also matches .part#.rar), BTW. And .r101 is matched by the glob (first digit is in the 0-9 range). –  kopischke Jun 4 '12 at 10:16
show 3 more comments

I'm writing this off the top of my head, so it's untested code, but it should get you on the right track. It basically walks the tree you point it to, looking for .rar files. Upon finding one, it will uncompress it in place and delete the original archive unless unrar returns nonzero. When hitting a folder, the function will just call itself, causing it to be recursive.

#!/bin/bash

[[ ! -d "$1" ]] && echo "Please point me at a directory!" && exit 1

function recursively_extract_all_rars_in() {
    local x=`pwd` f
    cd "$1"


    for f in (*); do
        [[ -d "$f" ]] && recursively_extract_all_rars_in "$f"
        [[ -f "$f" ]] && [[ "$f" =~ "*.rar" ]] && unrar e "$f" && rm ${f%%.*}.r??
    done
    cd "$x"
}

recursively_extract_all_rars_in "$1"

The function name is completely arbitrary. I like to have them read like proper english when invoked with their arguments. [[ -d /path ]] returns true if the path exists and is a directory. -f does the corresponding for files. [[ "string" =~ "pattern"]] is a bashism that allows for pattern matching in strings. It works mostly just like glob patterns.

The line local x=pwd f might be cryptic, but it just defines two local variables: one called x, to hold the pwd, and one called f, uninitialized (it's initialized in the for loop below, I just declare it here so it's local).

Storing the pwd and returning to it if your function uses cd is a Good Thing (tm).

Please note that using the output of ls programatically is generally Bad Mojo, and you should avoid it like the pest, in favour of find. If any of your file names contains a space, using ls in your script will screw up big time. You have been warned.

ZSH

I'm not sure you can do the same thing in Bash, but in ZSH, I'd put the following somewhere in .zshrc

function recursive_unrar() {
    for f in **/*.rar; do
        local cwd=`pwd` fbn=${f##*/}
        cd "${f%/*}"
        unrar e "$fbn"
        rm "${fbn%%.*}.r{01..99} $fbn"
        cd "$cwd"
    done
}

And then just call it from inside the corresponding folder.

share|improve this answer
    
This will delete the first rar of each set from what i can see, how can i get it to delete all from the directory? –  Jharwood Jun 3 '12 at 22:16
    
/usr/bin/unrarall: line 12: conditional binary operator expected /usr/bin/unrarall: line 12: syntax error near ~=' /usr/bin/unrarall: line 12: [[ -f "$f" ]] && [[ "$f" ~= "*.rar" ]] && print $x' -- What? :s –  Jharwood Jun 3 '12 at 22:22
    
replace ls with find $1 and fix the pattern matching, and i think it'll be fine. anyone know how to match all .r00 .r01 .r02 etc etc? –  Jharwood Jun 3 '12 at 22:55
    
The bash pattern matching operator is =~, not ~=, that causes the error. Also, for the reasons already given n the answer, do not use he output from ls – use a glob instead, i.e. ` for f in *; do`. –  kopischke Jun 3 '12 at 22:56
    
replace "$f" in the rm command with ${f%%.*}.r?? to have it remove all the rar parts too. –  mkaito Jun 3 '12 at 22:57
show 7 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.