MacOS – Unpacking a tarball via terminal like the Finder

macostar

If I have a .tar or .tgz or .tbz on my desktop, and I double click on it, I get the folder that was compressed, neatly decompressed right next to the archive file. Just like you'd expect.

When I try to decompress a tarred item via the tar command, the output I get has the whole full path of the original compressed file.

So for example, if I make a .tbz out of a folder on my desktop called "foo", and then I decompress it via tar -xf foo.tbz ~/Desktop/foo, what I get is:
/Users/[user]/Desktop/foo/Users/[user]/desktop/foo/bar.txt

Is there some magic flag in tar that I'm not seeing? I've googled up 'how-to' after 'how-to' that give you a simple tar command to extract a file, but all the directions I see give me my undesired result with long, unnecessary, super-nested result directors.

Best Answer

The option you are looking for is --strip-components (from man tar):

--strip-components count

(x mode only) Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped.

But it is not 100% equivalent to the way the Finder works: you still need to know the directory depth to strip.

Fortunately, it can be calculated with this command:

while read; do echo -n "$REPLY" | wc -c; done < <(tar tvf <tar archive name> | egrep -v '^d' | sed 's:[^/]::g') | sort -n | uniq | head -n 1

Since you have to replace <tar archive name> with the name of your tar archive, it is not as convenient as simply double clicking it in the Finder, but it can be useful in Bash scripts.

If the tar archive contains several files in different paths, the command above calculates the maximum common directory depth:

tar archive contents          =>      extracted files using --strip-components

dir1/dir2/file1               =>      file1
dir1/dir2/dir3/file2          =>      dir3/file2
dir1/dir2/dir3/dir4/file3     =>      dir3/dir4/file3

For example, if you create a tar file like this:

mkdir -p a/b/c/d/e/f
touch a/b/c/d/e/f/myfile1
touch a/b/c/d/e/myfile2
tar cvf file.tar a

so that:

tar tf file.tar
a/
a/b/
a/b/c/
a/b/c/d/
a/b/c/d/e/
a/b/c/d/e/f/
a/b/c/d/e/myfile1
a/b/c/d/e/f/myfile2

you can extract all files (with the shortest directory depth) as follows:

  1. Calculate maximum common directory depth:

    while read; do echo -n "$REPLY" | wc -c; done < <(tar tvf file.tar | egrep -v '^d' | sed 's:[^/]::g') | sort -n | uniq | head -n 1
    

    Command returns 5.

  2. Run tar passing the result of the previous command to --strip-components:

    tar xvf file.tar --strip-components 5
    x f/
    x myfile1
    x f/myfile2
    

Instead of running two commands, if your shell is bash or zsh, you can use $() to pass the result directly to --strip-components:

tar xvf file.tar --strip-components $(while read; do echo -n "$REPLY" | wc -c; done < <(tar tvf file.tar | egrep -v '^d' | sed 's:[^/]::g') | sort -n | uniq | head -n 1)
x f/
x myfile1
x f/myfile2

This command also properly deals with the case of a tar archive without a directory hierarchy:

touch myfile3
tar cvf file2.tar myfile3
tar xvf file2.tar --strip-components $(while read; do echo -n "$REPLY" | wc -c; done < <(tar tvf file2.tar | egrep -v '^d' | sed 's:[^/]::g') | sort -n | uniq | head -n 1)
x myfile3