1

I'm looking for the pure command-line counterpart to ark -ba <path> (on KDE), or file-roller -h <path> (on GNOME/Unity).

Unfortunately, both ark and file-roller require X to be running. I'm aware that it is relatively simple to write a tool that detects archives based on their file extension, and then runs the appropriate program:

#!/bin/bash
if [[ -f "$1" ]] ; then
    case $1 in
        *.tar.bz2) tar xjvf $1 ;;
        *.tar.gz) tar xzvf $1 ;;
        *.bz2) bunzip2 $1 ;;
        *.rar) rar x $1 ;;
        *.gz) gunzip $1 ;;
        *.tar) tar xf $1 ;;
        *.tbz2) tar xjvf $1 ;;
        *.tgz) tar xzvf $1 ;;
        *.zip) unzip $1 ;;
        *.Z) uncompress $1 ;;
        *.7z) 7z x $1 ;;
        *) echo "'$1' cannot be extracted with this utility" ;;
    esac
else
    echo "path '$1' does not exist or is not a file"
fi

However, that doesn't take care of subdirectory detection (and in fact, many extraction programs do not even supply such an option).

So might there be a program that does exactly that?

5
  • The question is perfectly on topic and welcome to stay here. If you don't get a good answer, you can always flag it for migration to Unix & Linux.
    – terdon
    Dec 9, 2012 at 4:51
  • 4
    What do you mean by "subdirectory detection"?
    – terdon
    Dec 9, 2012 at 4:53
  • I am also unclear what you mean with subdirectory detection. Do you want to avoid extracting any subdirectories and dump all files in the current directory? Do you want to Avoid that and make sure it end up in a directory (without more subdirs in that dir), etc etc. --- Also, in the example: Why verbose on compressed tarballs but not on regular ones? And why #!/bin/bash rather than the more modern #!/usr/bin/env bash ?
    – Hennes
    Dec 9, 2012 at 5:18
  • 1
    Both ark and file-roller are able to automatically detect whether or whether not the archive stores the file in a subdirectory - i.e, (as virtual path) "somearchive.zip/somesubdirectory/whatever.html", compared to (again, virtual path) "somearchive.zip/whatever.html". In the latter case, both ark and file-roller would create a directory named "somearchive" (or "somearchive-<number>" if the directory already exists in $PWD). That's what I mean with "subdirectory detection". Dec 9, 2012 at 5:33
  • 1
    You could look into atool. The aunpack command should be able to do what you need. Also see here.
    – xuhdev
    Feb 20, 2014 at 7:59

3 Answers 3

4

What you describe as "subdirectory detection" should happen by default. In this example with GNU tar:

$ tree
.
├── dir1
│   └── file4
├── dir2
│   ├── file5
│   └── file6
├── file1
├── file2
└── file3

Archive:

$ tar cvf all.tar *
$ mkdir new_dir
$ mv all.tar new_dir
$ cd new_dir
$ tar xvf all.tar
$ tree
.
├── all.tar
├── dir1
│   └── file4
├── dir2
│   ├── file5
│   └── file6
├── file1
├── file2
└── file3

If you are using an archive program that does not keep the directory structure when creating an archive (are you sure about this by the way? I don't know of any that don't do this), then the information is lost. There is no way to recreate the directory structure unless it has been saved in the archive itself, in which case it should be recreated upon archive extraction by default.


If you want to mimic the behavior of ark -a:

  -a, --autosubfolder            Archive contents will be read, and if detected
                                 to not be a single folder archive, a subfolder
                                 with the name of the archive will be created.

You could create a wrapper script that extracts the archive to a temp directory, then if the temp dir contains just one other directory, move that directory into your current working directory and delete the tmp dir and, if there are multiple files/dirs in the temp dir, rename it to the name of the archive. Something like this:

#!/usr/bin/env bash

for file in "$@"
do
    ## Get the file's extension
    ext=${file##*.}
    ## Special case for compressed tar files. They sometimes
    ## have extensions like tar.bz2 or tar.gz etc.
    if [[ "$(basename "$file" ."$ext")" =~ \.tar$ ]]; then
                if [[ "$ext" = "gz" ]]; then
                        ext="tgz"
                elif [[ "$ext" = "bz2" ]]; then
                        ext="tbz"
                fi
        fi


        ## Create the temp dir
        tmpDir=$(mktemp -d XXXXXX);
    case $ext in
        7z)
            7z -o "$tmpDir" e "$file" 
            ;;
        tar)
            tar xf "$file" -C "$tmpDir" 
            ;;
                tbz)
                        tar xjf "$file" -C "$tmpDir" 
                        ;;

                tgz)
                        tar xzf "$file" -C "$tmpDir" 
                        ;;

        rar)
                        unrar e "$file" "$tmpDir"
            ;;
        zip)
            unzip "$file" -d "$tmpDir"
            ;;
        *)
            echo "Unknown extension: '$ext', skipping..."
            ;;
    esac

        ## Get the tmp dir's structure
        tmpContents=( "$tmpDir"/* )
        c=1
        ## If the tmpDir contains only one item and that is a directory
        if [[ ${#tmpContents[@]} = 1 ]] && [[ -d "${tmpContents[0]}" ]]
        then
                ## Move that directory to the current working directory
                ## and delete the tmpDir, renaming it if a file/directory with
                ## the same name already exists.
                dirName=${tmpContents[0]##*/}
                [[ -e "$dirName" ]] && dirName="$dirName.$c"
                while [[ -e "$dirName" ]]; do
                        ((c++))
                        dirName="${dirName/.*}.$c"
                done
                mv "${tmpContents[0]}" "$dirName"
        else
                ## If the tmpDir contains anything more than a single directory,
                ## rename thye tmpDir to the name of the archive sans extension.
                ## If a file/dir of that name already exists, add a counter.
                dirName="${file##*/}"     ## strip path
                dirName="${dirName%%.*}"  ## strip extension(s)
                [[ -e "$dirName" ]] && dirName="$dirName.$c"
                while [[ -e "$dirName" ]]; do
                        ((c++))
                        dirName="${dirName/.*}.$c"
                done
                mv "$tmpDir" "$dirName"
        fi
printf "Archive '%s' extracted to %s\n" "$file" "$dirName" >&2
done
6
  • No worries, I'll delete my comment. It was uncalled for, I agree. Nevertheless, I appreciate your effort. My apologies. Dec 9, 2012 at 22:50
  • Fair enough, deleting mine too, sorry I couldn't be of more help.
    – terdon
    Dec 9, 2012 at 23:42
  • @terdon You might've misunderstood the question, OP probably has to deal with archives they didn't create themselves. Sometimes people create archives with several files/dirs in its root. What ark -ba does in this case is it creates a subdirectory with the same name as the archive sans extensions and unpacks inside this subdir. I too would welome a CLI program that would do this...
    – kralyk
    May 4, 2016 at 9:03
  • @kralyk since the OP has accepted this answer, it presumably answers their question. Have a look at the comments under the question where I ask for clarification of what they mean "subdirectory detection". What you seem to be asking for is different. You might want to ask a new question, either here or on Unix & Linux.
    – terdon
    May 4, 2016 at 9:10
  • @terdon I belive I'm on the same page as OP - ark -a is what this is about. Check out ark --help to see what ark -a does.
    – kralyk
    May 4, 2016 at 9:13
2

aunpack from atool does this by default.

Usage: aunpack <archive file>

Available from most linux distro repos.

0
1

Here is an update on terdon on strike's answer. This fixes some problems that it had with files that have version numbers in them, such as "VSCode-3.2.1.tar.gz" etc. As well as adds support for .tar.xz archives, and combines all the compressed tar options, since the -j and -z, etc, tar options are really only for archive creation, not extraction (it will auto detect). Also, the temp dir wasn't being removed after successful extraction in some cases, so I fixed that also.

#!/usr/bin/env bash

# https://superuser.com/questions/516873/commandline-program-to-extract-archives-with-automatic-subdirectry-detection

for file in "$@"
do
    ## Get the file's extension
    ext=${file##*.}
    ## Special case for compressed tar files. They sometimes
    ## have extensions like tar.bz2 or tar.gz etc.
    if [[ "$(basename "$file" ."$ext")" =~ \.tar$ ]]; then
        if [[ "$ext" =~ ^(gz|bz2|xz|Z)$ ]]; then
            ext="tar"
        fi
    fi

    ## Create the temp dir
    tmpDir=$(mktemp -d XXXXXX);
    case $ext in
        7z)
            7z -o "$tmpDir" e "$file"
            ;;
        tar)
            tar -xf "$file" -C "$tmpDir"
            ;;
        rar)
            unrar e "$file" "$tmpDir"
            ;;
        zip)
            unzip "$file" -d "$tmpDir"
            ;;
        *)
            echo "Unknown extension: '$ext', skipping..."
            ;;
    esac

    ## Get the tmp dir's structure
    tmpContents=( "$tmpDir"/* )
    c=1
    ## If the tmpDir contains only one item and that is a directory
    if [[ ${#tmpContents[@]} = 1 ]] && [[ -d "${tmpContents[0]}" ]]
    then
        ## Move that directory to the current working directory,
        ## renaming it if a file/directory with the same name exists.
        ## Then delete the tmpDir.
        dirName=${tmpContents[0]##*/}
        [[ -e "$dirName" ]] && dirName="$dirName.$c"
        while [[ -e "$dirName" ]]; do
            ((c++))
            dirName="${dirName%.*}.$c"  ## increment dirName.1 counter
        done
        mv "${tmpContents[0]}" "$dirName"
        rm -r "$tmpDir"
    else
        ## If the tmpDir contains anything more than a single directory,
        ## rename the tmpDir to the name of the archive, without extension.
        ## If a file/dir of that name already exists, add a counter.
        dirName="${file##*/}"      ## strip path
        dirName="${dirName%.*}"    ## strip extension
        dirName="${dirName%.tar}"  ## strip remaining tar extension
        [[ -e "$dirName" ]] && dirName="$dirName.$c"
        while [[ -e "$dirName" ]]; do
            ((c++))
            dirName="${dirName%.*}.$c"  ## increment dirName.1 counter
        done
        mv "$tmpDir" "$dirName"
    fi

printf "Archive '%s' extracted to %s\n" "$file" "$dirName" >&2
done

This is apparently how aunpack works anyway (minus the dirName incrementer we use), so if you don't want to install that, just add this to your $HOME/.local/bin/ or elsewhere, named unpack/untar, or something, chmod +x.

Note, this solution depends on you also having installed 7zip, unrar, unzip, and tar, of course. (Or whichever of those filetypes you'll be using). If you won't be working with rar files, then unrar isn't necessary, etc.

Note 2: A terminal program called unar apparently also exists that comes with some linux distros: "Unarchiver for a variety of file formats". It seems to behave in this way as well, but with a warning/option to rename/override/skip/quit, when it encounters a folder name that already exists during extraction. And it also seems to work with all of the above file types.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .