【问题标题】:Is there a cross-platform Java method to remove filename special chars?是否有跨平台的 Java 方法来删​​除文件名特殊字符?
【发布时间】:2010-11-12 09:50:54
【问题描述】:

我正在制作一个跨平台应用程序,它可以根据在线检索的数据重命名文件。我想清理从当前平台的 Web API 获取的字符串。

我知道不同的平台有不同的文件名要求,所以我想知道是否有跨平台的方法来做到这一点?

编辑:在 Windows 平台上不能有问号“?”在文件名中,而在 Linux 中,您可以。文件名可能包含此类字符,我希望支持这些字符的平台保留它们,否则,将它们删除。

另外,我更喜欢不需要第三方库的标准 Java 解决方案。

【问题讨论】:

  • Ben,你能提供一些例子吗?
  • 在我的问题中添加了问号注释。

标签: java cross-platform filesystems filenames


【解决方案1】:

正如其他地方所建议的,这通常不是您想要做的。通常最好使用 File.createTempFile() 等安全方法创建临时文件。

您不应该使用白名单来执行此操作,而只能保留“好”字符。如果文件仅由中文字符组成,那么您将删除其中的所有内容。由于这个原因,我们不能使用白名单,我们必须使用黑名单。

Linux 几乎允许任何可能是真正痛苦的事情。我只想将 Linux 限制在与 Windows 相同的列表中,这样您以后就不用担心了。

在 Windows 上使用这个 C# sn-p 我生成了一个在 Windows 上无效的字符列表。此列表中的字符数比您想象的要多得多 (41),因此我不建议您尝试创建自己的列表。

        foreach (char c in new string(Path.GetInvalidFileNameChars()))
        {
            Console.Write((int)c);
            Console.Write(",");
        }

这是一个“清理”文件名的简单 Java 类。

public class FileNameCleaner {
final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47};
static {
    Arrays.sort(illegalChars);
}
public static String cleanFileName(String badFileName) {
    StringBuilder cleanName = new StringBuilder();
    for (int i = 0; i < badFileName.length(); i++) {
        int c = (int)badFileName.charAt(i);
        if (Arrays.binarySearch(illegalChars, c) < 0) {
            cleanName.append((char)c);
        }
    }
    return cleanName.toString();
}
}

编辑: 正如斯蒂芬建议的那样,您可能还应该验证这些文件访问仅发生在您允许的目录中。

以下答案包含示例代码,用于在 Java 中建立自定义安全上下文,然后在该“沙箱”中执行代码。

How do you create a secure JEXL (scripting) sandbox?

【讨论】:

  • 不错的 java 例子,但为什么不包括正斜杠 (47)?
  • 不知道为什么它不在列表中。我们实际上只是在生产代码中遇到了这个问题。我已将答案固定为 47。谢谢。
  • 必须对非法字符数组进行排序,binarySearch 才能正常工作。请添加Arrays.sort(illegalChars)或将数组更改为“{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19、20、21、22、23、24、25、26、27、28、29、30、31、34、42、47、58、60、62、63、92、124}"
  • 你的解决方案使用charAt()...基本上你不应该使用charAt。将其视为已弃用。原因是charAt 无法处理Basic Multilingual Plane 之外的Unicode 代码点,因为它是一个16 位值。相反,请使用返回整数的codePointAt()。此外,这消除了您当前正在执行的强制转换为 int 的需要。
  • 请记住,length() 返回字符数,因此如果您使用codePointAt,则需要使用codePointCount()badFileName.codePointCount(0, badFileName.length());
【解决方案2】:

或者只是这样做:

String filename = "A20/B22b#öA\\BC#Ä$%ld_ma.la.xps";
String sane = filename.replaceAll("[^a-zA-Z0-9\\._]+", "_");

结果:A20_B22b_A_BC_ld_ma.la.xps

解释:

[a-zA-Z0-9\\._] 匹配从 a-z 小写或大写字母、数字、点和下划线

[^a-zA-Z0-9\\._] 是相反的。即所有不匹配第一个表达式的字符

[^a-zA-Z0-9\\._]+ 是与第一个表达式不匹配的字符序列

所以每个不包含 a-z、0-9 或 . _ 将被替换。

【讨论】:

  • 这适用于仅使用英文字母的文件名。如果文件仅由中文字符组成,那么您将删除其中的所有内容。因此,我们不能在字符串上使用白名单来去除坏字符,我们必须使用黑名单。
  • 看看这里:stackoverflow.com/questions/9576384/… 如果你使用 Java 7,它应该可以工作
  • @Dirk Downvoted 因为正则表达式不是这里的解决方案。如果文件名是多种语言怎么办?
  • 视实际需求而定。如果白名单字符就足够了,那么这个解决方案的可读性就更高了。
  • 要在文件名中保留非拉丁字符,您可以使用 unicode 标志(从 Java 1.7 开始),如下所示:String sane = filename.replaceAll("(?U)[^\\w\\._]+", "_") ;
【解决方案3】:

这是基于Sarel Botha 接受的答案,只要您没有遇到Basic Multilingual Plane 之外的任何字符,它就可以正常工作。如果您需要完整的 Unicode 支持(谁不需要呢?)请改用这个 Unicode 安全的代码:

public class FileNameCleaner {
  final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47};

  static {
    Arrays.sort(illegalChars);
  }

  public static String cleanFileName(String badFileName) {
    StringBuilder cleanName = new StringBuilder();
    int len = badFileName.codePointCount(0, badFileName.length());
    for (int i=0; i<len; i++) {
      int c = badFileName.codePointAt(i);
      if (Arrays.binarySearch(illegalChars, c) < 0) {
        cleanName.appendCodePoint(c);
      }
    }
    return cleanName.toString();
  }
}

这里的主要变化:

  • 使用codePointCount i.c.w。 length 而不仅仅是 length
  • 使用codePointAt 而不是charAt
  • 使用appendCodePoint 而不是append
  • 无需将chars 转换为ints。事实上,你永远不应该处理chars,因为它们基本上被 BMP 之外的任何东西破坏了。

【讨论】:

  • 您可以使用标准函数并使用字符 - 您只需跳过代理对字符后面的字符。此外,字符不需要强制转换为数字类型 - 它们是设计为数字的。
  • 我已经阅读了最佳答案和这个答案,而且这个似乎更仔细地考虑了......但是我找不到任何情况下这个代码正确执行而另一个没有。什么输入显示了差异?
【解决方案4】:

这是我使用的代码:

public static String sanitizeName( String name ) {
    if( null == name ) {
        return "";
    }

    if( SystemUtils.IS_OS_LINUX ) {
        return name.replaceAll( "[\u0000/]+", "" ).trim();
    }

    return name.replaceAll( "[\u0000-\u001f<>:\"/\\\\|?*\u007f]+", "" ).trim();
}

SystemUtils 来自Apache commons-lang3

【讨论】:

  • 没有 SystemUtils: if( File.separatorChar=='/') { return name.replaceAll( "/+", "" ).trim(); }
  • 文件名中是否允许使用 \u0000?
【解决方案5】:

有一个非常好的内置 Java 解决方案 - Character.isXxx()

试试Character.isJavaIdentifierPart(c):

String name = "name.é+!@#$%^&*(){}][/=?+-_\\|;:`~!'\",<>";
StringBuilder filename = new StringBuilder();

for (char c : name.toCharArray()) {
  if (c=='.' || Character.isJavaIdentifierPart(c)) {
    filename.append(c);
  }
}

结果是“name.é$_”。

【讨论】:

  • 好的,所以这是一种保守的方式,不能完全满足原始问题(跨平台),但对我有用:)
  • 它确实删除了对文件名有效的连字符(至少在 Windows 中)但它确实完成了这项工作,无论如何我认为 Apache Commons FilenameUtils 应该结合跨平台的方式来完成这项工作
  • 它也删除了“@”,这在 Windows 中再次有效。
【解决方案6】:

您的问题并不清楚,但由于您计划接受来自网络表单的路径名(?),您可能应该阻止重命名某些事物的尝试;例如“C:\程序文件”。这意味着您需要规范化路径名以消除“。”和“..”,然后再进行访问检查。

鉴于此,我不会尝试删除非法字符。相反,我会使用“new File(str).getCanonicalFile()”来生成规范路径,接下来检查它们是否满足您的沙盒限制,最后使用“File.exists()”、“File.isFile()”等来检查源和目标是否符合犹太教规定,并且不是同一个文件系统对象。我会通过尝试执行操作并捕获异常来处理非法字符。

【讨论】:

    【解决方案7】:

    Paths.get(...) 抛出非法字符位置的详细异常。

    public static String removeInvalidChars(final String fileName)
    {
      try
      {
        Paths.get(fileName);
        return fileName;
      }
      catch (final InvalidPathException e)
      {
        if (e.getInput() != null && e.getInput().length() > 0 && e.getIndex() >= 0)
        {
          final StringBuilder stringBuilder = new StringBuilder(e.getInput());
          stringBuilder.deleteCharAt(e.getIndex());
          return removeInvalidChars(stringBuilder.toString());
        }
        throw e;
      }
    }
    

    【讨论】:

    • 哎哟。聪明,但如果您需要快速解决方案(尝试/捕获和递归),请不要使用它。此外,如果您接受来自网络的用户输入,请不要忘记修剪输入;否则,发布一个 1Mb 长且包含无效字符的文件名肯定会使您的服务器溢出;)
    【解决方案8】:

    如果你想使用更多像[A-Za-z0-9],那么检查MS Naming Conventions,并且不要忘记过滤掉“......整数表示在1到31范围内的字符, ...”,就像 Aaron Digulla 的例子一样。代码例如对于这些字符,来自 David Carboni 是不够的。

    包含保留字符列表的摘录:

    使用当前代码页中的任何字符作为名称,包括 Unicode 字符和扩展字符集 (128–255) 中的字符,但以下字符除外:

    以下保留字符:

    • &lt;(小于)
    • &gt;(大于)
    • :(冒号)
    • "(双引号)
    • /(正斜杠)
    • \(反斜杠)
    • |(竖条或竖管)
    • ?(问号)
    • *(星号)
    • 整数值零,有时称为 ASCII NUL 字符。
    • 整数表示在 1 到 31 范围内的字符,允许使用这些字符的备用数据流除外。有关文件流的更多信息,请参阅文件流。
    • 目标文件系统不允许的任何其他字符。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-04
      • 2014-05-27
      • 2016-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多