escaping - 如何从 Tcl 执行 POSIX shell 转义

标签 escaping tcl filenames sh

有没有办法在 Tcl 中对字符串执行 POSIX shell 转义?

背景:

我在 Tcl 列表中有一个任意文件名的列表。我需要扩展该列表以粘贴到一个 shell 片段中,该片段稍后将通过执行“sh -c”由任意 POSIX shell(bash、dash、posh 等)执行。

下面是一个说明问题的示例:

#!/usr/bin/tclsh

set targets {with\ spaces has"stray'quotes has{brackets} $not_a_variable \[escaped_braces\] (not_a_subshell) weird\ \{|#^$(}

set shell_fragment {
  something
  some_command $targets
  something else
}

puts [subst $shell_fragment]

上面的输出是经过 Tcl 转义的名称:

  something
  some_command with\ spaces has"stray'quotes has{brackets} $not_a_variable \[escaped_braces\] (not_a_subshell) weird\ \{|#^$(
  something else

然而,我需要它看起来像这样才能正常工作(POSIX shell 转义):

  something
  some_command with\ spaces has\"stray\'quotes has{brackets} \$not_a_variable [escaped_braces] \(not_a_subshell\) weird\ {\|\#^\$\(
  something else

想法:

以下是我可以想象的一些方法来解决这个问题,但我并不真正想做:

  • 在 Bash 中,printf 的 %q 格式化程序可以完成我想要的操作。我可以对每个文件名执行一次 bash 调用来利用此功能,但这 1) 是一个相当大的背部,2) 引入了对 bash 的依赖,这是我不希望这样做的。

    <
  • 根据 POSIX shell 转义规则自行实现 shell 转义。这显然是可行的,但我不想重新发明轮子。我找到了一种“简单”的方法,通过垃圾邮件引用来做到这一点,但这使得调试变得很糟糕,并且大大减少了可用的命令行长度:

“坏”方法的示例:

proc posix_escape_via_bash {name} {
  return [exec bash -c {printf %q "$0"} $name]
}

proc posix_escape_via_spamming_quotes {name} {
  set escaped {}
  foreach char [split $name {}] {
    switch $char {
      '       {lappend escaped {\'}}
      default {lappend escaped '$char'}
    }
  }
  return [join $escaped {}]
}

再说一次:有没有一种方法可以在 Tcl 中对字符串执行 POSIX shell 转义?如果有的话,我最乐意使用“标准”方法来执行此操作,但我'我也很高兴使用非标准 Tcl 库,甚至是从 C 执行此操作的方法,因此我可以从 Tcl 调用它。

最佳答案

做到这一点的关键是使用 string mapregsub .

使用 string map转换一组字符

您所要做的就是为您想要转义的内容提供正确的映射。

对于您的具体情况,您似乎想要引用的唯一字符是 ' , " , $ , ( , ) , < , >| 。让我们添加; , *?也是(我猜你不想要杂散的语句分隔符或通配符)。这非常简单,但我们将迭代地生成映射,而不是使用文字:

set mappedChars {'"$()<>|&!;*?}    ;#'# Just to deal with SO's formatting...
set escaping {}
foreach c $mappedChars { lappend escaping $c "\\$c" }

这件事你只需要做一次。完成后,应用 map 就很容易了:

set escapedTargets [string map $escaping $targets]

我将让您找出将其与 subst 的使用合并的最佳方法。 .

使用regsub转换一组字符

另一种方法是使用regsub-all选项。仅当您在所有替换情况下执行完全相同类型的转义时,这才真正有效。

# This puts a backslash in front of all non-alphanumerics
set escapedTargets [regsub -all {[^[:alnum:]]} $targets {\\&}]
# This _particular_ case has an almost-equivalent-good-enough that's shorter
set escapedTargets [regsub -all {\W} $targets {\\&}]

复杂之处在于为所有问题案例确定正确的表征正则表达式,这就是为什么人们经常说使用正则表达式将一个问题变成两个问题......

<小时/>

讨论/替代方法

上面的映射并没有涵盖所有 POSIX shell 元字符 - 特别是,它不处理反斜杠本身或空格(这样做会给你带来问题,因为你似乎想要获取多个单词)并且它还应该处理这些:{}[]~ ——正则表达式可能有点了,在完全无辜的东西前面加上了反斜杠。事实上,某些用途(例如变量名)比上述任何一种方法都需要更加小心,因为它们有一些根本无法使用的东西。

根本问题是 shell 实际上有非常复杂的语法,有很多交互规则。如果您可以编写代码而不需要运行 shell,您可能会发现事情更加可靠(模数 Tcl 的 exec 和管道 open 有它们自己的奇怪问题,这些问题源于尝试太多就像外壳一样)。这是否适合您取决于您​​在问题中没有告诉我们的其他正在发生的事情。

关于escaping - 如何从 Tcl 执行 POSIX shell 转义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11130818/

相关文章:

cmake - 如何在自定义 CMake 命令中包含文字双引号?

java - DOMDocument getNodeValue() 返回 null(包含输出转义字符串)

regex - Tcl 中的模式匹配

r - 从完整路径中仅提取文件名之前的文件夹名称

php - 以编程方式确定最大文件名长度

file - 更改文件名

Python:字典值中的八进制转义字符\033 在打印语句中转换为 UTF-8 字符

javascript - Angular 2 HostListener 按键检测转义键?

user-interface - 如何为 bash 脚本制作 GUI?

tcl - 从字符串中删除前缀子字符串