Xargs: change working directory to file path before executing

working directoryxargs

I have a large folder of RAR archives. There is a significant hierarchy of folder levels. I want to unRAR the entire collection of archives all at once.

I have the following one-liner, which will work:

find -name "*.rar" -print0 | xargs -0 -n 1 -P 4 unrar x

(Note that we're running four threads in parallel to speed up the operation. 🙂 )

The problem with this command is that xargs is executed in the top level directory for each RAR files. This means that all of the output is being dumped into the top level folder.

Instead, I want the output to exist in the same folder as the RAR archive.

Example:

Top level
 |--FolderA
 |----File1.rar
 |----File2.rar
 |--FolderB
 |----File1.rar
 |----File2.rar
 |----File3.rar
 |--FolderC
 |----File1.rar
 |----File2.rar

Each of the "File1.rar" files contains a file with the same name. Thus extracting them all into the top level folder causes overwrite issues.

To summarize, I want to extract all the RAR files in the above hierarchy. I want the contents of each RAR file to exist in the folder that the RAR file exists in.

It seems to me that the solution is to somehow set the working directory, and then run the unrar command form there. However, since the find command is giving me filenames, not directories, I can't do something like

| xargs -I{} -n 1 -P 4 cd {} \; unrar x {}

Short of writing a Perl or Python script that will wrap around the unrar command and handle splitting the provided path into its parts and executing the command, is there a better way to achieve this?

Best Answer

There exist commands to extract a directory name (dirname) and file name (basename) from a path. So you could do something like

find . -name '*.rar' -print0 | \
xargs -0 -I{} -n1 -P4 /bin/sh -c 'cd "$(dirname {})"; unrar x "$(basename {})"'

AFAIK, xargs doesn't support changing directories, so you would need some intermediary to do that, hence the /bin/sh. You mentioned writing a wrapper around unrar, and that is basically what this is doing, except in one-liner form.

Related Question