使用自动子目录检测提取档案的命令行程序

使用自动子目录检测提取档案的命令行程序

ark -ba <path>我正在寻找(在 KDE 上)或file-roller -h <path>(在 GNOME/Unity 上)的纯命令行对应物。

不幸的是,ark 和 file-roller 都需要运行 X。我知道编写一个根据文件扩展名检测档案的工具相对简单,然后运行相应的程序:

#!/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

但是,这并没有解决子目录检测的问题(事实上,许多提取程序甚至不提供这样的选项)。

那么是否存在一个可以实现这一目标的程序呢?

答案1

您所描述的“子目录检测”应该默认发生。在此示例中,使用 GNU tar

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

档案:

$ 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

如果您使用的存档程序在创建存档时不保留目录结构(顺便问一下,您确定吗?我不知道有哪个程序不这样做),那么信息就会丢失。除非目录结构已保存在存档本身中,否则无法重新创建目录结构,在这种情况下,默认情况下应在存档提取时重新创建目录结构。


如果你想模仿的行为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.

您可以创建一个包装器脚本,将档案提取到临时目录,然后如果临时目录仅包含另一个目录,则将该目录移动到当前工作目录并删除临时目录,如果临时目录中有多个文件/目录,则将其重命名为档案的名称。 类似这样的内容:

#!/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

答案2

aunpack一个工具默认这样做。

用法:aunpack <archive file>

可从大多数 Linux 发行版存储库获取。

答案3

这是 terdon on strike 的答案的更新。这解决了一些带有版本号的文件的问题,例如“VSCode-3.2.1.tar.gz”等。此外还增加了对 .tar.xz 存档的支持,并结合了所有压缩的 tar 选项,因为 -j 和 -z 等 tar 选项实际上仅用于创建存档,而不是提取(它会自动检测)。此外,在某些情况下,成功提取后不会删除临时目录,所以我也修复了这个问题。

#!/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

无论如何,这显然是如何aunpack工作的(减去我们使用的 dirName 增量器),所以如果你不想安装它,只需将它添加到你的$HOME/.local/bin/或其他地方,名为unpack/ untar,或类似的东西chmod +x

请注意,此解决方案当然还取决于您是否安装了7zip、、和。(或您将使用的任何文件类型)。如果您不使用 rar 文件,则无需 unrar,等等。unrarunziptar

注 2:unar一些 Linux 发行版中似乎还附带一个名为“Unarchiver for a variety of file format”的终端程序。它似乎也以这种方式运行,但当它在提取过程中遇到已存在的文件夹名称时,会发出警告/选择重命名/覆盖/跳过/退出。它似乎也适用于上述所有文件类型。

相关内容