Как выполнить экранирование оболочки POSIX из Tcl

Есть ли способ выполнить экранирование строки POSIX из Tcl?

Фон:

У меня есть список произвольных имен файлов в списке Tcl. Мне нужно расширить список, чтобы вставить его во фрагмент оболочки, который впоследствии будет выполняться произвольной оболочкой POSIX (bash, dash, posh и т. д.) с помощью команды «sh -c».

Вот пример, иллюстрирующий проблему:

#!/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 есть средство форматирования %q для printf, которое делает то, что я хочу. Я мог бы выполнять вызовы 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 {}]
}

Итак, еще раз: есть ли способ выполнить escape-оболочку POSIX для строки из Tcl? Меня бы больше всего устроил "стандартный" способ сделать это, если он есть, но я бы также доволен нестандартной библиотекой Tcl или даже способом сделать это из C, чтобы я мог вызывать это из Tcl.


person wjl    schedule 21.06.2012    source источник
comment
Это posix_escape_via_bash столкнется с проблемами, если $name начинается с символа перенаправления, такого как >. exec усыпан подводными камнями…   -  person Donal Fellows    schedule 21.06.2012
comment
@Donal хорошее замечание по поводу версии, зависящей от bash; это еще одна причина, по которой я не могу его использовать!   -  person wjl    schedule 21.06.2012


Ответы (3)


Вы можете ' заключать в кавычки все не-' символы вместе, а не по отдельности, и вам нужно только закончить и возобновить '-кавычки в середине строки, чтобы \-экранировать любые ' символы.

Так что вы были на правильном пути со спамом '-quote, потому что вы уже поняли

  1. что одинарные кавычки экранируют все (кроме '), что сокращает число особых случаев до одного, и
  2. что вы можете просто объединить строки в кавычках в оболочке, и она интерпретирует их как одну строку ('a''b' анализирует ту же необработанную строку, что и 'ab').

Последний недостающий элемент заключался в том, что второй пункт позволяет нам оптимизировать почти все окончание и немедленное возобновление '-цитирования, которое происходило при '-цитировании каждого символа по отдельности.

Так что логика, которая вам нужна, это просто

  1. замените все ' на '\'' и
  2. поставьте один ' в начале и в конце:
proc posix_escape_via_minimal_quotes {name} {
  set escaped {}
  lappend escaped '
  lappend escaped [string map {' '\\''} $name]
  lappend escaped '
  return [join $escaped {}]
}

Пример выходных данных:

% posix_escape_via_minimal_quotes x
'x'
% posix_escape_via_minimal_quotes xxx
'xxx'
% posix_escape_via_minimal_quotes xxx'xxx
'xxx'\''xxx'
% posix_escape_via_minimal_quotes '
''\'''
person mtraceur    schedule 15.06.2021

Ключом к этому является использование 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 для преобразования набора символов

Другой метод — использовать 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 — в частности, она не обрабатывает саму обратную косую черту или пробелы (это может вызвать проблемы, поскольку вы хотите получить несколько слов), и она также должна обрабатывать следующие: {}[]~ — и регулярное выражение, возможно, немного слишком остроумно, ставя обратную косую черту перед совершенно невинными вещами. Действительно, некоторые виды использования (например, имена переменных) требуют гораздо большей осторожности, чем любой из вышеперечисленных подходов, поскольку в них есть вещи, которые просто нельзя использовать.

Основная проблема заключается в том, что оболочка на самом деле имеет очень сложный синтаксис с множеством взаимодействующих правил. Если вы можете написать свой код так, чтобы не требовался запуск оболочки, вы, вероятно, найдете вещи гораздо более надежными (по модулю того факта, что Tcl exec и конвейер open имеют свои собственные странные проблемы, которые возникают из-за слишком многого, чтобы быть похожим на оболочку) . Подходит ли это для вас, зависит от других вещей, о которых вы не сказали нам в своем вопросе.

person Donal Fellows    schedule 21.06.2012
comment
Спасибо большое за советы! Я все еще поражен тем, что нет более стандартного способа сделать что-то подобное, но, по крайней мере, теперь у меня есть несколько достойных способов приблизиться к этому в моей собственной реализации. - person wjl; 21.06.2012
comment
Оператор обратной кавычки (``) также может быть проблематичным, если вы хотите экранировать строку и предотвратить проблемы с безопасностью. - person Dereckson; 01.11.2014
comment
Я думаю, что этот ответ в целом хороший и информативный. Но я хочу сильно предупредить, что подход к исчерпывающему охвату всех символов, которые могут быть специальными, всегда влечет за собой риск того, что мы забыли еще один специальный символ. Это игра типа «ударь крота», которая никогда не скажет вам, закончили ли вы играть. Например, даже если мы только официально поддерживаем POSIX, в реальной жизни пользователи нашего кода могут использовать bash или zsh или какой-то /bin/sh, который настолько близок к POSIX, что им даже не приходит в голову, что существуют отклонения вроде ! или ^. быть особенным. - person mtraceur; 15.06.2021
comment
Кроме того, я могу понять, почему вы отложили рассмотрение [, ~, { и так далее до конца (потому что исходный вопрос не ускользнул и не упомянул их в своем примере), но я чувствую, что это хороший пример недостатка подхода охвата каждого возможно специального символа (вместо определения наименьшего возможного набора правил/случаев, который охватывает все символы). Бьюсь об заклад, спрашивающий не знал, что они хотели, чтобы [ сбежал из скорлупы! Потому что вы бы не знали, что это проблема экранирования, пока у вас не будет файла с именем вроде e в вашем рабочем каталоге. - person mtraceur; 15.06.2021
comment
Таким образом, в то время как вы могли бы знать, что в этой ситуации нужно исчерпывающе охватить [, типичный результат следования общему подходу я собираюсь обрабатывать каждый специальный символ (и я видел, что это почти всегда результат в избегании ошибок, попадающих в производственный код!) заключается в том, что люди избегают вещей, которые, как они знают, являются особенными или могут быстро обнаружить, являются особенными, хотя в некоторых случаях (включая оболочку Bourne/POSIX!) существует Чрезвычайно простой способ дословно передать буквально любые символы через оценку команды оболочки тому, чему оболочка передает их. - person mtraceur; 15.06.2021

В итоге я применил вариант упомянутого мной метода «рассылки спама с цитатами», но в специальном регистре различные классы символов, которые либо никогда не нужно заключать в кавычки, либо могут заключаться в кавычки с простой обратной косой чертой. Это все еще немного чрезмерно, но НАМНОГО лучше, чем оригинальный наивный подход. В большинстве случаев это дает тот же результат, что и метод bash printf.

  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
  }

Мне все еще очень интересно, есть ли более стандартный способ сделать это. Меня бы очень удивило, если бы никто никогда не сталкивался с этим раньше! знак равно

person wjl    schedule 21.06.2012
comment
Я думаю, что мой ответ представляет собой более стандартный способ сделать это. Я бы также сказал, что printf %q не является хорошим стандартом, потому что 1) если бы это был стандарт, он заставлял бы любого, кто реализует его, ввязываться в игру «ударь крота» по исчерпывающей идентификации каждого специального символа (который сама оболочка находится в привилегированном позиция, чтобы сделать это правильно для себя, но все остальные должны играть в догонялки и в идеале охватывать несколько оболочек), и 2) подходы с обратной косой чертой имеют худшую читабельность для человека и размер ввода-вывода. -size для гораздо большего количества возможных входных данных, чем то, что я считаю стандартом. - person mtraceur; 15.06.2021