Escapar de uma variável para usar como conteúdo de outro script

20

Esta questão não é sobre como escrever uma literal de string com escape apropriado. Não consegui encontrar nenhuma pergunta relacionada que não seja sobre como escapar de variáveis ​​para consumo direto em um script ou em outros programas.

Meu objetivo é habilitar um script para gerar outros scripts. Isso ocorre porque as tarefas nos scripts gerados serão executadas de 0 a n vezes em outra máquina, e os dados dos quais eles são gerados podem ser alterados antes de serem executados (novamente), fazendo as operações diretamente, através de uma rede não funciona.

Dada uma variável conhecida que pode conter caracteres especiais, como aspas simples, preciso escrevê-la como um literal de string totalmente escapado, por exemplo, uma variável que foocontenha bar'bazdeve aparecer no script gerado como:

qux='bar'\''baz'

que seria escrito anexando "qux=$foo_esc"às outras linhas de script. Eu fiz isso usando Perl assim:

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

mas isso parece exagero.

Não tive sucesso em fazê-lo apenas com o bash. Eu tentei muitas variações destes:

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

mas as barras extras aparecem na saída (quando o faço echo "$foo") ou causam um erro de sintaxe (esperando uma entrada adicional se feita a partir do shell).

Walf
fonte
aliase / ou setrazoavelmente universal
mikeserv
@ MikeServ Desculpe, não sei o que você quer dizer.
Walf
alias "varname=$varname" varnameouvar=value set
mikeserv 28/07
2
@ mikeserv Isso não é contexto suficiente para eu entender o que sua sugestão deve fazer, nem como eu a usaria como um método genérico de escapar de qualquer variável. É um problema resolvido, companheiro.
Walf

Respostas:

26

O Bash tem uma opção de expansão de parâmetro exatamente para este caso :

${parameter@Q}A expansão é uma sequência que é o valor do parâmetro citado em um formato que pode ser reutilizado como entrada.

Então, neste caso:

foo_esc="${foo@Q}"

Isso é suportado no Bash 4.4 ou superior. Existem várias opções para outras formas de expansão e também para gerar especificamente instruções de atribuição completas ( @A).

Michael Homer
fonte
7
Limpo, mas só tem 4,2 que dá bad substitution.
Walf
3
O equivalente do shell Z é "${foo:q}".
JdeBP
Realmente salve minha vida! "${foo@Q}"trabalho!
hao
@JdeBP esse equivalente do shell Z não funciona. Alguma outra idéia para o zsh?
Steven Shaw
1
Encontrei a resposta: "$ {(@ qq) foo}"
Steven Shaw
10

O Bash fornece um especificador de formato printfinterno %q, que executa o escape de shell para você, mesmo nas versões mais antigas (<4.0) do 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\`]

Este truque também pode ser usado para retornar matrizes de dados de uma função:

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\ \)]

Observe que o Bash printfembutido é diferente do printfutilitário fornecido com a maioria dos sistemas operacionais semelhantes ao Unix. Se, por algum motivo, o printfcomando chamar o utilitário em vez do incorporado, você sempre poderá executar builtin printf.

Dejay Clayton
fonte
Não tenho certeza de como isso ajuda se o que eu precisaria ser impresso'Ne'\''er do well' , etc., ou seja, citações incluídas na saída.
Walf
1
@ Walf Eu acho que você não está entendendo que as duas formas são equivalentes, e ambas são perfeitamente tão seguras quanto as outras. Por exemplo [[ 'Ne'\''er do well' == Ne\'er\ do\ well ]] && echo 'equivalent!', equivalent!
ecoará
Eu senti falta disso: P, no entanto, prefiro o formulário citado, pois é mais fácil ler em um visualizador / editor com destaque para sintaxe.
Walf
@Walf parece que sua abordagem é bastante perigosa, considerando que no seu exemplo Perl, passar um valor como 'hello'resulta em um valor incorreto ''\''hello'', que possui uma sequência vazia inicial desnecessária (as duas primeiras aspas simples) e uma aspas simples à direita.
DeJay Clayton
1
@Walf, para esclarecimento, $'escape-these-chars'é o recurso de citação ANSI-C do Bash que faz com que todos os caracteres dentro da string especificada sejam escapados. Assim, para criar facilmente um literal de cadeia que contenha uma nova linha dentro do nome (por exemplo $'first-line\nsecond-line'), o uso \nneste constructo.
DeJay Clayton
8

Eu acho que não fiz RTFM. Isso pode ser feito da seguinte maneira:

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

Então echo "$foo_esc"dá o esperado'bar'\''baz'


Como eu estou realmente usando isso é com uma função:

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

...

foo_esc="`esc_var "$foo"`"

Modificando isso para usar o printfbuilt-in da solução de Dejay:

function esc_vars {
    printf '%q' "$@"
}
Walf
fonte
3
Eu usaria a solução @ michael-homer se minha versão o suportasse.
Walf
4

Existem várias soluções para citar um valor var:

  1. alias
    Na maioria dos shells (onde o alias está disponível) (exceto csh, tcsh e provavelmente outros como o csh):

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

    Sim, isso funciona em várias shconchas como traço ou cinza.

  2. set
    Também na maioria dos shells (novamente, não no csh):

    $ qux=bar\'baz
    $ set | grep '^qux='
    qux='bar'\''baz'
  3. typeset
    Em algumas conchas (ksh, bash e zsh, pelo menos):

    $ qux=bar\'baz
    $ typeset -p qux
    typeset qux='bar'\''baz'             # this is zsh, quoting style may
                                         # be different for other shells.
  4. exportar
    Primeiro faça:

    export qux=bar\'baz

    Então use:
    export -p | grep 'qux=' export -p | grep 'qux='
    export -p qux

  5. pode-se citar
    echo "${qux@Q}"
    echo "${(qq)qux}" # de um a quatro q's.

Isaac
fonte
A abordagem do alias é inteligente e parece especificada pelo POSIX. Para máxima portabilidade, acho que este é o caminho a percorrer. Eu acredito que as sugestões envolvendo grepcom exportou setpodem quebrar em variáveis que contêm novas linhas incorporadas.
jw013 2/01