首页 > 解决方案 > 压缩文件——路径名中的撇号

问题描述

我无法压缩文件夹名称中带有撇号的任何文件。例如

zip -rmT "./I've Been Everywhere (Today)/PDFs.zip" "./PDFDirectory"

或者

zip -rmT ./I\'ve\ Been\ Everywhere\ \(Today\)/PDFs.zip ./PDFDirectory

我尝试了各种引用、转义和使用变量而不是输入字符串的方式,但仍然得到

sh: 1: Syntax error: "(" unexpected

test of I've Been Everywhere (Today)/PDFs.zip FAILED

这发生在命令行和 bash 脚本中。从路径中删除撇号/单引号可以清除错误。Zip 无法处理路径名中的撇号还是我遗漏了什么?

更新 09/22/21 1443 EDT

Marco 是正确的,它是导致错误的 -T (--test)。由于我需要该测试,因此我目前正在通过 cd 进入文件夹并在压缩完成后 cd 回解决该问题(YUCK!)。

标签: bashpathzipescaping

解决方案


似乎-T( --test) 选项不能很好地处理'文件路径中的单引号。如果您在没有它的情况下运行命令,它应该可以正常工作,但您将失去完整性检查。更好的解决方法是在当前目录中创建 zip 文件,然后再将其移动到 I've Been Everywhere (Today)目录中,例如:

$ zip -rmT PDFs.zip PDFDirectory && mv PDFs.zip "I've Been Everywhere (Today)"
  adding: PDFDirectory/ (stored 0%)
  adding: PDFDirectory/bar.pdf (stored 0%)
  adding: PDFDirectory/baz.pdf (stored 0%)
  adding: PDFDirectory/foo.pdf (stored 0%)
test of PDFs.zip OK
$ ls I\'ve\ Been\ Everywhere\ \(Today\)/
PDFs.zip
$

(参见最后使用-b选项的另一种解决方法)


只是为了好玩,我试图查看zip程序源代码以(尝试)了解发生了什么(这里是 github 镜像)。

如前所述,问题仅发生在(我认为)-T对 zip 文件进行完整性检查的选项中。来自man zip

-T
--测试

测试新 zip 文件的完整性。如果检查失败,旧的 zip 文件将保持不变,并且(使用 -m 选项)不会删除任何输入文件。

-TT cmd
--解压缩命令 cmd

当使用 -T 选项时,使用命令 cmd 而不是“unzip -tqq”来测试存档。在 Unix 上,要使用当前目录中的解压缩副本而不是标准系统解压缩,可以使用:

zip archive file1 file2 -T -TT "./unzip -tqq"

在 cmd 中,{} 替换为临时存档的名称,否则将存档的名称附加到命令的末尾。检查返回码是否成功(Unix 上为 0)。

使用该-T选项时,在 zip 文件上zip运行。unzip -tqq我在 中找到了实际执行此操作的一段代码,zip.c函数是:

local void check_zipfile(zipname, zippath)
  char *zipname;
  char *zippath;
  /* Invoke unzip -t on the given zip file */

它很长而且真的很“#ifdef-heavy”,我假设你在一个类似unix的系统上,我只会报告相关部分:

    if ((cmd = malloc(20 + strlen(zipname))) == NULL) {
      ziperr(ZE_MEM, "building command string for testing archive");
    }

    strcpy(cmd, "unzip -t ");
...
    if (!verbose) strcat(cmd, "-qq ");
    if (check_unzip_version("unzip") == 0)
      ZIPERR(ZE_TEST, zipfile);

# ifdef UNIX
    strcat(cmd, "'");    /* accept space or $ in name */
    strcat(cmd, zipname);
    strcat(cmd, "'");
...
  }

  result = system(cmd);
...
  free(cmd);
  cmd = NULL;
  if (result) {
...
    fprintf(mesg, "test of %s FAILED\n", zipfile);
    ziperr(ZE_TEST, "original files unmodified");
  }

该代码基本上构建了一个命令字符串cmd并使用 system()函数运行它。system()是一个 C 库函数,它:

使用 fork(2) 创建一个子进程,该子进程执行 command using execl(3) 中指定的 shell 命令,如下所示:

execl("/bin/sh", "sh", "-c", command, (char *) NULL);

system() 在命令完成后返回。

执行的命令check_zipfile()实际上是:

/bin/sh -c "unzip -t -qq 'I've Been Everywhere (Today)/PDFs.zip'"

注意'文件路径周围的两个添加了这些行:

strcat(cmd, "'");    /* accept space or $ in name */
strcat(cmd, zipname);
strcat(cmd, "'");

如果zipname包含一个'(如您的情况)shell命令将被“破坏”,因为'添加了两个,不要再“包装”完整的文件路径。如果我尝试运行该命令,我会得到相同的错误报告zip,即 shell 语法错误:

$ sh -c "unzip -t -qq 'I've Been Everywhere (Today)/PDFs.zip'"
sh: 1: Syntax error: "(" unexpected
$

所以该-T选项运行格式错误的 shell 命令!

奇怪的是,如果你的文件路径不包括目录路径,即你只传递一个文件名,它可以包含'没有任何问题,例如:

$ zip -rmT "foo's bar.zip" PDFDirectory
updating: PDFDirectory/ (stored 0%)
updating: PDFDirectory/bar.pdf (stored 0%)
updating: PDFDirectory/baz.pdf (stored 0%)
updating: PDFDirectory/foo.pdf (stored 0%)
test of foo's bar.zip OK
$

我认为这是因为zip为“压缩”创建了一个临时文件,并且仅在最后将其重命名为提供的名称。

查看此临时文件如何工作的代码,我能够使用-b( --temp-path) 选项找到该问题的另一种解决方法:

$ zip -rmT -b /tmp "I've Been Everywhere (Today)/PDFs.zip" PDFDirectory
  adding: PDFDirectory/ (stored 0%)
  adding: PDFDirectory/bar.pdf (stored 0%)
  adding: PDFDirectory/baz.pdf (stored 0%)
  adding: PDFDirectory/foo.pdf (stored 0%)
test of I've Been Everywhere (Today)/PDFs.zip OK
$ ls I\'ve\ Been\ Everywhere\ \(Today\)/
PDFs.zip
$

使用-b,zip将执行命令:

/bin/sh -c "unzip -t -qq '/tmp/ziXXXXXX'"

对于-T格式正确的 shell 命令的选项,最后,它将重命名/tmp/ziXXXXXXI've Been Everywhere (Today)/PDFs.zip.


推荐阅读