如何在Unix命令行中递归解压目录及其子目录中的归档文件?

98
unzip命令没有递归解压存档文件的选项。
如果我有以下目录结构和存档文件:
/Mother/Loving.zip
/Scurvy/Sea Dogs.zip
/Scurvy/Cures/Limes.zip
并且我想将所有存档文件解压缩到与每个存档文件名称相同的目录中:
/Mother/Loving/1.txt
/Mother/Loving.zip
/Scurvy/Sea Dogs/2.txt
/Scurvy/Sea Dogs.zip
/Scurvy/Cures/Limes/3.txt
/Scurvy/Cures/Limes.zip
我要发出哪些命令?
重要的是,这不能因文件名中包含空格而中断。

长文件路径超过260个字符怎么办?请参考:https://superuser.com/a/1263234/439537 - Andrew
11个回答

172

如果你想将文件提取到各自的文件夹中,可以尝试这个:

find . -name "*.zip" | while read filename; do unzip -o -d "`dirname "$filename"`" "$filename"; done;

一个适用于可以处理高I/O的系统的多进程版本:

find . -name "*.zip" | xargs -P 5 -I fileName sh -c 'unzip -o -d "$(dirname "fileName")/$(basename -s .zip "fileName")" "fileName"'

2
这是正确的做法,因为它保留了完整的目录结构! - kynan
12
注意:这并不会按照归档文件的同名目录来提取文件,与请求不符。 - emlai
5
我不知道xargs有多核支持! - chuckrector
3
由于某种原因,第一个示例对我和 emlai 一样存在问题,所有解压缩的文件都会放在 zip 文件所在的目录中。然而,第二个示例,即多线程方法,不仅速度更快,而且还将每个 zip 文件的文件放在以该 zip 文件命名的目录中。完美! - MoTLD
1
一个小细节: 如果成功解压缩了zip文件,可以在最后一个逗号之前立即添加&& rm“fileName”来删除zip文件,例如find . -name "*.zip" | xargs -P 5 -I fileName sh -c 'unzip -o -d "$(dirname "fileName")/$(basename -s .zip "fileName")" "fileName" && rm "fileName"' - MoTLD
显示剩余2条评论

61

一个能够正确处理包括换行符在内的所有文件名,并将其提取到与文件位于同一位置但不包含扩展名的目录中的解决方案:

find . -iname '*.zip' -exec sh -c 'unzip -o -d "${0%.*}" "$0"' '{}' ';'

请注意,通过使用 -o 添加文件类型,例如 .jar,您可以轻松地使其处理更多类型的文件:

find . '(' -iname '*.zip' -o -iname '*.jar' ')' -exec ...

到目前为止,我找到的唯一有效的解决方案。 - kostja
到目前为止,这是唯一按要求工作的解决方案。 - Rob
2
建议更改为“find . -iname...”,以便解压缩“.ZIP”和“.zip”文件。 - Luke Rehmann
这个似乎真的获取了所有的zip文件。xargs解决方案由于我不理解的原因错过了一些文件。 - SamBobb

40

以下是一个解决方案,它可以将所有的 zip 文件提取到 工作目录,并且使用了 find 命令和 while 循环:

find . -name "*.zip" | while read filename; do unzip -o -d "`basename -s .zip "$filename"`" "$filename"; done;

9
请注意:此提取将所有zip文件解压缩到“工作目录”!而您要求相对解压缩。请参见Vivek的回答 - Jossef Harush Kadouri
嗯,据我所知,它似乎没有将它们全部提取到工作目录中。我正在使用来自OS X El Capitan 10.11.6的UnZip 5.52。结果似乎与Vivek的相同。尽管如此,我还是接受了他的答案,因为它还展示了如何利用多个核心! - chuckrector

5
这是我们想要的完美结果:
解压文件:
find . -name "*.zip" | xargs -P 5 -I FILENAME sh -c 'unzip -o -d "$(dirname "FILENAME")" "FILENAME"'

以上命令不会创建重复的目录。

删除所有zip文件:

find . -depth -name '*.zip' -exec rm {} \;

4
你可以在单个命令行中使用 find 命令和 -exec 标志来完成这项任务。
find . -name "*.zip" -exec unzip {} \;

3
这将所有内容解压到当前目录,而不是相对于每个子目录。它也不会将内容解压缩到与每个归档文件同名的目录中。 - chuckrector
我假设正确使用“-d”选项留给读者自己练习。该读者需要注意,“-exec”选项在命令中只允许使用一次 {},可以通过调用“sh”并将 {} 分配给变量来解决此问题。 - Steve Jessop
还要注意,-execdir 有时比 -exec 更可取。在这种情况下,我认为这并不重要。 - Steve Jessop
我的查找工具(GNU findutils 4.4.0)允许我多次使用 {}...cloud@thunder:~/tmp/files$ find . -exec echo {} {} ; . . ./a ./a ./b ./b ./c ./c ./d ./d ./e ./e ./f ./f ./g ./g - Sam Reynolds
请查看我的答案,其中使用了-exec包括-d。请注意,在调用sh时要小心,确保不会执行像"; rm -rf /;这样的恶意文件名。 - robinst

2

是否需要像使用-r标志的gunzip一样的东西?

递归地遍历目录结构。如果命令行上指定的任何文件名是目录,则gzip将进入该目录并压缩其中找到的所有文件(或在gunzip的情况下解压缩它们)。

http://www.computerhope.com/unix/gzip.htm


2
他特别在谈论zip文件,而不是gzip文件。 - dvorak

2

如果您正在使用cygwin,basename命令的语法会略有不同。

find . -name "*.zip" | while read filename; do unzip -o -d "`basename "$filename" .zip`" "$filename"; done;

1

我知道这篇文章很老,但是当我在谷歌上搜索类似问题的解决方案时,它是谷歌上排名靠前的结果之一,所以我会在这里发布我所做的事情。我的情况略有不同,因为我基本上只想完全解压一个JAR文件,以及其中包含的所有JAR文件,所以我编写了以下bash函数:

function explode {
    local target="$1"
    echo "Exploding $target."
    if [ -f "$target" ] ; then
        explodeFile "$target"
    elif [ -d "$target" ] ; then
        while [ "$(find "$target" -type f -regextype posix-egrep -iregex ".*\.(zip|jar|ear|war|sar)")" != "" ] ; do
            find "$target" -type f -regextype posix-egrep -iregex ".*\.(zip|jar|ear|war|sar)" -exec bash -c 'source "<file-where-this-function-is-stored>" ; explode "{}"' \;
        done
    else
        echo "Could not find $target."
    fi
}

function explodeFile {
    local target="$1"
    echo "Exploding file $target."
    mv "$target" "$target.tmp"
    unzip -q "$target.tmp" -d "$target"
    rm "$target.tmp"
}

请注意<file-where-this-function-is-stored>,如果您将其存储在非交互式 shell 不读取的文件中,则需要此文件。如果您将函数存储在非交互式 shell(例如.bashrc)加载的文件中,则可以删除整个source语句。希望这会对某些人有所帮助。
一个小警告- explodeFile还会删除压缩文件,当然您可以通过注释掉最后一行来更改它。

0

您还可以循环遍历每个zip文件,创建每个文件夹并解压缩zip文件。

for zipfile in *.zip; do
  mkdir "${zipfile%.*}"
  unzip "$zipfile" -d "${zipfile%.*}"
done

0
另一个有趣的解决方案是:
DESTINY=[Give the output that you intend]

# Don't forget to change from .ZIP to .zip.
# In my case the files were in .ZIP.
# The echo were for debug purpose.

find . -name "*.ZIP" | while read filename; do
ADDRESS=$filename
#echo "Address: $ADDRESS"
BASENAME=`basename $filename .ZIP`
#echo "Basename: $BASENAME"
unzip -d "$DESTINY$BASENAME" "$ADDRESS";
done;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接