4

Tcl 内から文字列に対して POSIX シェル エスケープを実行する方法はありますか?

バックグラウンド:

Tcl リストに任意のファイル名のリストがあります。「sh -c」を実行して任意の POSIX シェル (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シェルエスケープ):

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

考え:

私が本当にやりたくないこれを解決することを想像できるいくつかの方法を次に示します。

  • Bashには、私が望むことを行うprintfへの%qフォーマッターがあります。この機能を利用するために、ファイル名ごとに 1 回 bash への呼び出しを実行することもできますが、これは 1) かなり大きな問題であり、2) bash への依存をもたらします。

  • POSIX シェル エスケープ規則に従って、自分自身をエスケープするシェルを実装します。これは明らかに機能しますが、車輪の再発明はしたくありません。引用符をスパムすることでこれを行う「簡単な」方法を見つけましたが、これによりデバッグがひどくなり、使用可能なコマンドラインの長さが大幅に減少します。

それを行う「悪い」方法の例:

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シェルエスケープを実行する方法はありますか? これを行うための「標準的な」方法があればそれが一番嬉しいですが、非標準の Tcl ライブラリ、または C からこれを行う方法さえあれば、Tcl から呼び出すことができます。 .

4

3 に答える 3

2

これを行うための鍵は、string mapまたはを使用することregsubです。

string map文字セットの変換に使用

必要なのは、エスケープしたいものに正しいマッピングを提供することだけです。

あなたが持っている特定のケースでは'、引用したいと思われる唯一の文字は、、、、、、、およびです。、も追加しましょう(文のセパレーターやグロビング文字は不要だと思います)。これは非常に簡単ですが、リテラルを使用するのではなく、反復的にマッピングを生成します。"$()<>|;*?

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

それはあなたが一度だけする必要があることです。これで、マップの適用は簡単になります。

set escapedTargets [string map $escaping $targets]

それをあなたのsubst.

regsub文字セットの変換に使用

もう 1 つの方法は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 {\\&}]

複雑なのは、すべての問題ケースに対して正しい特徴付け正規表現を決定することです。これが、正規表現を使用すると 1 つの問題が 2 つになるとよく言われる理由です…</p>


ディスカッション / 代替アプローチ

上記のマップは、すべての POSIX シェル メタ文字をカバーしているわけではありません — 特に、バックスラッシュ自体または空白を処理しません (複数の単語を取得したいように見えるため、そうすると問題が発生します)。これらも処理する必要があります{}[]~。正規表現は、まったく無害なものの前にバックスラッシュを付けて、おそらく少し鋭すぎます。実際、一部の使用方法 (変数名など) は、単純に使用できないものがあるため、上記のアプローチのいずれよりも多くの注意が必要です。

根本的な問題は、シェルが実際には非常に複雑な構文を持ち、多くの相互作用するルールを持っていることです。シェルを実行する必要がないようにコードを書くことができれば、おそらくはるかに信頼性の高いものになるでしょう (Tclexecとパイプラインopenには、シェルのようにしようとしすぎたために発生する独自の奇妙な問題があるという事実を除けば)。これがあなたに適しているかどうかは、あなたの質問で私たちに言わなかった他のことが起こっていることに依存します.

于 2012-06-21T08:22:12.880 に答える
0

私が言及した「引用スパム」方式のバリエーションを実行することになりましたが、引用する必要がないか、単純な円記号で引用できる特殊なケースのさまざまなクラスの文字です。これはまだ少し熱心ですが、元の素朴なアプローチよりもはるかに優れています。ほとんどの場合、これはbashprintfメソッドと同じ結果になります。

  proc posix_escape {name} {
    foreach char [split $name {}] {
      switch -regexp $char {
        {'}           {append escaped \\'     }
        {[[:alnum:]]} {append escaped $char   }
        {[[:space:]]} {append escaped \\$char }
        {[[:punct:]]} {append escaped \\$char }
        default       {append escaped '$char' }
      }
    }
    return $escaped
  }

これを行うためのより標準的な方法があるかどうか、私はまだ非常に興味があります。これまで誰もこれに遭遇したことがなければ、私は大いに驚きます!=)

于 2012-06-21T04:26:27.060 に答える