node 在 linux 解压常见格式压缩包,并读取所有文件

2,167 阅读5分钟

业务场景

在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命令行语法

sevenzip.osdn.jp/chm/cmdline…

Ubuntu解压缩与文件(名/内容)乱码解决方案

godsing.top/2018/01/31/…