业务场景
在node脚本中解压常见格式压缩包并读取所有文件,存在压缩包嵌套压缩包的情况
linux系统版本
uname -v
#168-Ubuntu SMP Wed Jan 16 21:00:45 UTC 2019
linux解压工具
unzip
解压 test.zip
unzip -d /mnt/test/ test.zip
解压 test.zip 并移动所有文件到第一层
# -o 重名覆盖 -n 重名不覆盖
unzip -j -o -d /mnt/test/ test.zip
7z
下载
sudo apt-get install p7zip-full p7zip-rar
apt-get下载p7zip版本过老,直接下载最新版本源码
wget http://netix.dl.sourceforge.net/project/p7zip/p7zip/16.02/p7zip_16.02_src_all.tar.bz2
这里需要注意的有个小坑,下载p7zip_16.02_x86_linux_bin.tar.bz2,而不是p7zip_16.02_src_all.tar.bz2,否则只有 7za命令生效,7z命令不生效
在Linux上执行下面命令(解压和安装):
sudo tar xjvf p7zip_9.20.1_x86_linux_bin.tar.bz2
cd p7zip_9.20.1
sudo sh install.sh
安装完成后有三个可执行文件,
7z, 7za和7zr。这三个文件是有区别的。
其中7zr与7za属于轻量级,不使用插件,
7zr只用来处理7z格式的压缩包,
7za支持的格式稍多,
而7z则使用插件,支持的格式最多
解压 test.zip
7z x test.zip -o/mnt/test
解压 test.zip 并移动所有文件到第一层
# -aou自动重命名重名文件 7z特有
7z e test.zip -aou -o/mnt/test/
unar
sudo apt-get install unar
解压 test.zip
# -r 当解压的文件已存在时,始终重命名文件
unar test.zip -o /mnt/test/ -r
Ubuntu解压zip包文件名乱码
原因
本质问题还是zip格式的缺陷,没有字段标志出文件名的编码格式。ZIP在压缩与解压缩的时候默认使用了系统的本地编码,如windows中文环境下的编码多为gbk,gb2312,日文环境下是JIS,linux默认编码为UTF8等;那么在不同系统环境下,只要压缩与解压缩的编码不一致,就会出现乱码。
unzip方案
# 先试试
unzip -O GB18030 file.zip -d directory
# 不行再试试 GBK也可写成CP936,这是gbk编码在windows里的别称
unzip -O GBK file.zip -d directory
# 再不行继续试试
unzip -O GB2312 file.zip -d directory
这么试的原因是,编码技术的演进方向为:GB2312 ⇒ GBK(=CP936) ⇒ GB18030,最新的一般能兼容旧的编码技术,遇到不兼容的情况再用旧的编码去尝试。如果无法使用 -O 参数,参考以下链接打补丁。
缺参数,则给unzip打补丁 参考 github.com/ikohara/dpk… ;按步骤给unzip打补丁,打完即可使用-O参数了。
7z
安装convmv
sudo apt-get install convmv
用7z和convmv两个命令完成解压缩任务
LANG=C 7z e test.zip -aou -o/mnt/test/
convmv -f GBK -t utf8 --notest -r /mnt/test/
第一条命令用于解压缩,而LANG=C表示以US-ASCII这样的编码输出文件名,如果没有这个语言设置,它同样会输出乱码,只不过是UTF8格式的乱码(convmv会忽略这样的乱码)。
第二条命令是将GBK编码的文件名转化为UTF8编码,-r表示递归访问目录,即对当前目录中所有文件进行转换。
unar方案
这个工具会自动检测文件的编码,也可以通过-e来指定
最终选择解压方案
| 方案 | 是否支持多格式 | 能否解决跨平台乱码 | 自动重命名重名文件 |
|---|---|---|---|
| unzip | 否 | 能 | 否 |
| 7z | 是 | 实测7z和convmv结合方案有各种问题,依旧会乱码 | 是 |
| unar | 是 | 完美解决乱码 | 只能重命名解压文件夹 |
一开始我采用7z,因为它的自动重命名功能和移动所有文件到第一层,解压嵌套压缩包和读取起来最方便,后来发现它乱码解决实在是有各种问题,换为unar方案,配合node循环读取所有目录下文件
注:后来在实际使用中发现unar在linux平台的一个坑,解压采用RAR 5时会报错
Failed! (File is not fully supported)
但实际上文件成功解压了,只是报错会导致程序循环解压时停止运行,我在mac环境用unar解压该rar压缩包并没有报错,查了下,看起来像是unar的陈年老坑:
换成unar和7z配合方案,unar解压zip解决乱码问题,7z解压rar和7z格式压缩包,好了,现在我们可以在linux解压来自其他所有平台的各种格式压缩包了,然后使用node调用它们
node调用工具解压
import fs from 'fs';
import fse from 'fs-extra';
import child_process from 'child_process';
import path from 'path';
const exec = child_process.exec;
解压函数extractsArchive
/**
* 命令行解压压缩包
* @param zipPath 压缩包文件路径
* @param unZipPath 解压目录路径
*/
async function extractsArchive(zipPath, unZipPath){
if (/\.(rar|7z)$/.test(zipPath)){
await execPromise(`7z x '${zipPath}' -o'${unZipPath}'`);
}
if (/\.(zip)$/.test(zipPath)){
await execPromise(`unar '${zipPath}' -o '${unZipPath}' -r`);
}
}
function execPromise(cmd, options){
return new Promise((resolve, reject) => {
//maxBuffer改为100M
exec(cmd, { maxBuffer: 100 * 1024 * 1024 }, (err, stdout, stderr) => {
console.log(`${cmd}执行`);
if (err && stderr){
return reject(err || stderr);
}
resolve(stdout);
});
});
};
解压目录下所有压缩包unAllZip
/**
* 解压目录下所有压缩包(压缩包解压目录为压缩包当前所在路径)
* @param unZipPath
* @returns {Array[string]} 返回目录下所有文件路径
*/
async function unAllZip(unZipPath){
let fileNames = [];
//如果路径不存在
if(!fs.existsSync(unZipPath)){
return fileNames;
}
const files = fs.readdirSync(unZipPath);
for(let name of files){
//脏文件
if(name === '.DS_Store'){
continue;
}
//获取文件完整路径
const pathName = path.join(unZipPath, name);
//如果是压缩包解压文件
if (/\.(rar|zip|7z)$/.test(name)) {
const childFileNames = await unChildZip(pathName);
fileNames = fileNames.concat(childFileNames);
continue;
}
//读取文件类型
const stats = await fs.statSync(pathName);
//如果是文件夹遍历获取
if (stats.isDirectory()) {
const childFileNames = await unAllZip(pathName);
fileNames = fileNames.concat(childFileNames);
continue;
}
//正常文件
fileNames.push(pathName);
}
return fileNames;
}
//解压子压缩包
async function unChildZip(unZipPath){
const childPath = splitFileName(unZipPath);
await extractsArchive(unZipPath, childPath);
fse.removeSync(unZipPath);
const fileNames = await unAllZip(childPath);
return fileNames;
}
使用
const zipPath = '/mnt/test.zip';
const { dir, name } = path.parse(zipPath);
const unZipPath = path.join(dir, name);
fse.removeSync(unZipPath);
await extractsArchive(zipPath, unZipPath);
const fileNames = await unAllZip(unZipPath);
其他
7z命令行语法
Ubuntu解压缩与文件(名/内容)乱码解决方案