Escape переменная для использования в качестве содержимого другого скрипта

20

Этот вопрос не о том, как написать правильно экранированный строковый литерал. Я не смог найти ни одного связанного вопроса, который бы не касался того, как экранировать переменные для непосредственного использования в скрипте или другими программами.

Моя цель - включить скрипт для генерации других скриптов. Это связано с тем, что задачи в сгенерированных сценариях будут выполняться от 0 до n раз на другом компьютере, и данные, из которых они сгенерированы, могут измениться до того, как они будут выполнены (снова), поэтому выполнение операций напрямую по сети приведет к не работает.

Учитывая известную переменную, которая может содержать специальные символы, такие как одинарные кавычки, мне нужно записать это как полностью экранированный строковый литерал, например, fooсодержащая переменная bar'bazдолжна появиться в сгенерированном скрипте как:

qux='bar'\''baz'

который будет написан путем добавления "qux=$foo_esc"к другим строкам сценария. Я сделал это с помощью Perl следующим образом:

foo_esc="'`perl -pe 's/('\'')/\\1\\\\\\1\\1/g' <<<"$foo"`'"

но это кажется излишним.

У меня не было успеха в том, чтобы сделать это только с Bash. Я пробовал много вариантов этих:

foo_esc="'${file//\'/\'\\\'\'}'"
foo_esc="'${file//\'/'\\''}'"

но либо лишние косые черты появляются в выводе (когда я это делаю echo "$foo"), либо они вызывают синтаксическую ошибку (ожидая дальнейшего ввода, если это сделано из оболочки).

WALF
источник
aliasи / или setдовольно универсально
mikeserv
@mikeserv Извините, я не уверен, что вы имеете в виду.
Уолф
alias "varname=$varname" varnameилиvar=value set
mikeserv
2
@mikeserv Для меня недостаточно контекста, чтобы понять, что должно делать ваше предложение, и как я мог бы использовать его как универсальный метод экранирования любой переменной. Это решенная проблема, приятель.
Уолф

Ответы:

26

Bash имеет опцию расширения параметров именно для этого случая :

${parameter@Q}Расширение - это строка, представляющая собой значение параметра в кавычках в формате, который можно использовать повторно в качестве входных данных.

Итак, в этом случае:

foo_esc="${foo@Q}"

Это поддерживается в Bash 4.4 и выше. Есть также несколько вариантов для других форм расширения, а также для конкретной генерации полных операторов присваивания ( @A).

Майкл Гомер
источник
7
Аккуратный, но только 4,2, который дает bad substitution.
Уолф
3
Эквивалент Z-оболочки равен "${foo:q}".
JdeBP
Спаси мою жизнь! "${foo@Q}"работает!
хао
@JdeBP, что эквивалент оболочки Z не работает. Любые другие идеи для Zsh?
Стивен Шоу
1
Я нашел ответ: "$ {(@ qq) foo}"
Стивен Шоу
10

Bash предоставляет printfвстроенный %qспецификатор формата, который выполняет экранирование оболочки даже в более старых (<4.0) версиях Bash:

printf '[%q]\n' "Ne'er do well"
# Prints [Ne\'er\ do\ well]

printf '[%q]\n' 'Sneaky injection $( whoami ) `ls /root`'
# Prints [Sneaky\ injection\ \$\(\ whoami\ \)\ \`ls\ /root\`]

Этот прием также можно использовать для возврата массивов данных из функции:

function getData()
{
  printf '%q ' "He'll say hi" 'or `whoami`' 'and then $( byebye )'
}

declare -a DATA="( $( getData ) )"
printf 'DATA: [%q]\n' "${DATA[@]}"
# Prints:
# DATA: [He\'ll\ say\ hi]
# DATA: [or\ \`whoami\`]
# DATA: [and\ then\ \$\(\ byebye\ \)]

Обратите внимание, что printfвстроенная Bash отличается от printfутилиты, которая поставляется в комплекте с большинством Unix-подобных операционных систем. Если по какой-то причине printfкоманда вызывает встроенную утилиту вместо встроенной, вы всегда можете выполнить ее builtin printfвместо.

Дежай Клэйтон
источник
Я не уверен, как это поможет, если то, что мне нужно напечатать, будет 'Ne'\''er do well'и т. Д., То есть цитаты, включенные в вывод.
Уолф
1
@ Walf Я думаю, вы не понимаете, что эти две формы эквивалентны, и обе совершенно безопасны, как друг друга. Например, [[ 'Ne'\''er do well' == Ne\'er\ do\ well ]] && echo 'equivalent!'будет эхомequivalent!
Деджей Клейтон
Я упустил это: P, однако я предпочитаю цитируемую форму, так как ее легче читать в средстве просмотра / редакторе с подсветкой синтаксиса.
Уолф
@ Кажется, ваш подход довольно опасен, если учесть, что в вашем примере Perl передача подобного значения 'hello'приводит к неверному значению ''\''hello'', которое имеет ненужную начальную пустую строку (первые две одинарные кавычки) и неподходящую конечную одинарную кавычку.
Деджей Клэйтон
1
@Walf, для пояснения, $'escape-these-chars'это функция цитирования ANSI-C в Bash, которая позволяет экранировать все символы в указанной строке. Таким образом, чтобы легко создать строковый литерал, который содержит новую строку в имени файла (например $'first-line\nsecond-line'), используйте \nв этой конструкции.
Деджей Клэйтон
8

Я думаю, что я не RTFM. Это можно сделать так:

q_mid=\'\\\'\'
foo_esc="'${foo//\'/$q_mid}'"

Затем echo "$foo_esc"дает ожидаемый'bar'\''baz'


Как я на самом деле использую это с функцией:

function esc_var {
    local mid_q=\'\\\'\'
    printf '%s' "'${1//\'/$mid_q}'"
}

...

foo_esc="`esc_var "$foo"`"

Модифицируя это, чтобы использовать printfвстроенное решение Деджея:

function esc_vars {
    printf '%q' "$@"
}
WALF
источник
3
Я бы использовал решение @ michael-homer, если бы моя версия поддерживала его.
Уолф
4

Есть несколько решений процитировать значение переменной:

  1. псевдоним
    В большинстве оболочек (где псевдоним доступен) (кроме csh, tcsh и, вероятно, других csh, как):

    $ alias qux=bar\'baz
    $ alias qux
    qux='bar'\''baz'

    Да, это работает во многих shподобных оболочках, таких как приборная панель или пепел.

  2. установить
    также в большинстве оболочек (опять же, не csh):

    $ qux=bar\'baz
    $ set | grep '^qux='
    qux='bar'\''baz'
  3. верстать
    В некоторых оболочках (КШ, Баш и ЗШ , по крайней мере):

    $ qux=bar\'baz
    $ typeset -p qux
    typeset qux='bar'\''baz'             # this is zsh, quoting style may
                                         # be different for other shells.
  4. экспорт
    сначала сделать:

    export qux=bar\'baz

    Затем используйте:
    export -p | grep 'qux=' export -p | grep 'qux='
    export -p qux

  5. цитата
    echo "${qux@Q}"
    echo "${(qq)qux}" # может использоваться от одного до четырех q.

Исаак
источник
Подход псевдонима является умным и кажется, что он определен POSIX. Для максимальной мобильности я думаю, что это путь. Я считаю, что предложения, связанные grepс exportили setмогут нарушать переменные, содержащие встроенные символы новой строки.
jw013