【问题标题】:Create a shell-escaped POSIX path in macOS在 macOS 中创建一个 shell 转义的 POSIX 路径
【发布时间】:2019-03-27 15:56:31
【问题描述】:

我需要从完整的 POSIX 路径(从根目录开始)创建一个字符串,以便将它可以直接粘贴到像 bash 这样的 Unix shell 中,例如在Terminal.app 中,不需要在路径周围加上引号。

(我实际上并没有将字符串传递给 shell,而是需要它来将其传递给另一个程序。该程序期望路径的格式与您将文件拖入 Terminal.app 时所获得的形式相同.)

为此,我需要通过在字符串中添加反斜杠来转义至少所有空格。还有更多字符。

例如这条路径:

/directory/-as"<>' *+

转义如下:

/directory/-as\"\<\>\'\ \*+

执行该转换的安全算法是什么?我可以转义每一个字符,但这太过分了。

似乎没有框架函数可以做到这一点,所以我需要用字符串操作替换。

为了保守(对于最流行的 shell),在避免明显不必要的转义的同时,应该转义哪些字符集?

【问题讨论】:

  • 如果这是一个快速的问题,应该这样标记。如何在 in bash 中执行此操作的最佳答案是要求 shell 本身为您进行转义:printf '%q\n' "$name"
  • 也就是说,如果您想要一个简短、易读且实用的参考实现,the Python 3.x shlex.quote implementation 在这里做得很好。需要注意的是,它不能保证输出只是 ASCII 可读/可见字符;如果您尝试使用制表符/换行符/BELs/等引用某些内容,您将得到制表符/换行符/BELs/等,只是在引号中,因此符合 POSIX 的 shell 会将它们视为文字。
  • ...当然,您也可以将 shell 作为子进程分叉。 execlp("bash", "bash", "-c", "printf '%q' \"$1\"", "_", myStringVar, NULL) 是它在 C 中的样子(输出写入标准输出并能够从中捕获),这将为您提供仅使用可打印 ASCII 字符的 7 位干净结果(尽管不能保证所有人都支持POSIX shell;$'' 是一种 ksh-and-bash-ism,可能会在稍后的标准中出现,但目前还没有)。
  • 如果你想要万无一失,这也取决于你真正要粘贴的内容。考虑文件名“-rq”。完全合法,不需要任何转义,但粘贴到某些命令行中确实很糟糕(前缀为“./”或“.\\”以确保安全,具体取决于您的操作系统)
  • 如果您要粘贴到 shell 中,您需要担心的是 shell 解析器,而不是您的目标程序,因此 (1) 通配符,(2) 空格 (3) 重定向和进程控制, (4) 引用和转义。即,'“*?();|$ 等,如果您要传递到 argv 数组中的另一个应用程序,则不需要转义,除非它很麻烦。

标签: bash macos cocoa posix core-foundation


【解决方案1】:

作为记录,Terminal.app 在将文件名放入其窗口时会转义以下非控制 ASCII 字符:

空格

!"#$%&'()*,:;?[]`{|}~

而且这些都没有转义:

控制代码(00-1F 和 7F)

字母数字

+-.@^_

下面是执行替换的代码:

NSString* shellPathFromPOSIXPath (NSString *path)
{
    static NSRegularExpression *regex = nil;
    if (!regex) {
        NSString *pattern =
          @"([ !\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\,\\:\\;\\<\\=\\>\\?\\[\\]\\`\\{\\|\\}\\~])";
        regex =
          [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
    }
    NSString *result =
      [regex stringByReplacingMatchesInString:path
                                      options:0
                                        range:NSMakeRange(0, path.length)
                                 withTemplate:@"\\\\$1"];
    return result;
}

【讨论】:

    【解决方案2】:

    最好将整个内容放在单引号中,而不是为单个字符添加反斜杠;那么您需要转义的唯一字符是字符串中存在的单引号。

    Python standard library's implementation 作为示例提供,可以轻松地用任何其他只有基本原语的语言重新实现,内容如下:

    def quote(s):
        """Return a shell-escaped version of the string *s*."""
        if not s:
            return "''"
        if _find_unsafe(s) is None:
            return s
    
        # use single quotes, and put single quotes into double quotes
        # the string $'b is then quoted as '$'"'"'b'
        return "'" + s.replace("'", "'\"'\"'") + "'"
    

    也就是说,一般算法如下:

    • 空字符串变为''(一对文字单引号)。
    • 已知安全的字符串(尽管最安全的做法是根本不尝试为此实现代码路径,尤其是当 shell 经常在未定义的空间中实现自己的语法扩展时)可以裸露/不带引号发出。
    • 否则,在前面加上一个',发出您的输入字符串,将所有's 替换为文字字符串'"'"',然后附加一个最终的'

    就是这样。您不需要转义反斜杠(它们是单引号内的文字)、换行符(同样)或其他任何内容。

    【讨论】:

    • 感谢您的努力。不过,这不是我问的。我真的需要不带引号的字符串,原因我不想在这里讨论。我添加了 objc 和 swift 标签以使其更清楚我不需要 Python 解决方案(除非它会调用 CF 框架)。
    • Python 解决方案的重点是为您提供一种算法,您可以使用您选择的任何语言实现(因为您最初没有标记任何实现语言)。坦率地说,如果您想获得真正正确的答案,您需要了解为什么引号是不可接受的;许多使引号明显中断的情况也会使反斜杠以不明显的方式中断(shell命令行解析器如何解释反斜杠取决于上下文),所以如果你不透露细节,你可能会得到一个答案细微的错误。
    • (还要注意,在 shell 中,“引用”是一个逐个字符的属性;如果它是完全 在引号中?foo' 'bar 解析为与 foo\ bar 完全相同的字符串。
    • 我再次更新了我的问题,现在只是询问需要转义的字符集。我一开始就提出了不同的要求,因为我希望框架中会对此提供支持。
    • 同样,这组字符在 shell 之间是不同的——我已经给了你一个在 POSIX shell 和 bash 中不需要转义但 确实 需要转义的示例对于 ZFS。还有一些字符只需要在启用了可选的、默认关闭的选项(如 extglob)的情况下在 bash 中转义。 安全 事情是绝对引用所有内容(这就是为什么我倾向于鼓励解决阻止您使用引号的问题);如果你试图用尽可能少的字符集逃脱,你就是在玩火。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-16
    • 1970-01-01
    • 2012-11-21
    • 1970-01-01
    • 2019-08-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多