Expansão de variáveis ​​entre aspas simples em um comando no Bash

393

Eu quero executar um comando a partir de um script bash que possui aspas simples e alguns outros comandos dentro de aspas simples e uma variável.

por exemplo repo forall -c '....$variable'

Nesse formato, $é escapado e a variável não é expandida.

Tentei as seguintes variações, mas elas foram rejeitadas:

repo forall -c '...."$variable" '

repo forall -c " '....$variable' "

" repo forall -c '....$variable' "

repo forall -c "'" ....$variable "'"

Se eu substituir o valor no lugar da variável, o comando será executado perfeitamente.

Por favor, diga-me onde estou errado.

Rachit
fonte
33
repo forall -c ' ...before... '"$variable"' ...after...'
n. 'pronomes' m.
2
@nm as aspas simples fazem parte do comando repo. Eu não acho que isso vai funcionar
Rachit
11
bashcome aspas simples. Você não está dentro bashou as aspas simples não fazem parte do repocomando.
n. 'pronomes' m.
Não tenho certeza se entendi, mas, caso as aspas simples precisem estar lá, não reposicionará o comando -c "'... antes ..." $ variable "... depois de ...'" funcionar?
ECV
É um script bash executado no comando bash shell e repo forall leva parâmetros entre aspas simples. Se eu substituir o valor real, o comando será executado corretamente.
Rachit

Respostas:

621

Dentro de aspas simples, tudo é preservado literalmente, sem exceção.

Isso significa que você precisa fechar as aspas, inserir algo e depois inserir novamente.

'before'"$variable"'after'
'before'"'"'after'
'before'\''after'

A concatenação de palavras é simplesmente feita por justaposição. Como você pode verificar, cada uma das linhas acima é uma única palavra para o shell. As aspas (aspas simples ou duplas, dependendo da situação) não isolam as palavras. Eles são usados ​​apenas para desativar a interpretação de vários caracteres especiais, como espaço em branco $, ;... Para um bom tutorial sobre como citar, consulte a resposta de Mark Reed. Também relevante: Quais caracteres precisam ser escapados no bash?

Não concatenar cadeias interpretadas por um shell

Você deve absolutamente evitar a construção de comandos shell concatenando variáveis. Essa é uma má ideia semelhante à concatenação de fragmentos SQL (injeção de SQL!).

Geralmente, é possível ter espaços reservados no comando e fornecê-lo juntamente com variáveis ​​para que o receptor possa recebê-las da lista de argumentos de chamada.

Por exemplo, o seguinte é muito inseguro. NÃO FAÇA ISTO

script="echo \"Argument 1 is: $myvar\""
/bin/sh -c "$script"

Se o conteúdo de $myvarnão for confiável, aqui está uma exploração:

myvar='foo"; echo "you were hacked'

Em vez da invocação acima, use argumentos posicionais. A seguinte invocação é melhor - não é explorável:

script='echo "arg 1 is: $1"'
/bin/sh -c "$script" -- "$myvar"

Observe o uso de marcações simples na atribuição de script, o que significa que é literalmente usado, sem expansão variável ou qualquer outra forma de interpretação.

Jo So
fonte
@ Jo.SO que não vai funcionar. porque o comando repo utilizará apenas os parâmetros até que as primeiras cotações sejam fechadas. Qualquer coisa depois disso será ignorado para repo e gerará outro erro.
Rachit
8
@Rachit - o shell não funciona assim. "some"thing' like'thisé tudo uma palavra para o shell. As aspas não terminam nada no que diz respeito à análise, e não há como um comando chamado pelo shell dizer o que foi citado como.
Re
3
Essa segunda frase ("você nem consegue escapar de aspas simples") faz com que aspas simples sejam os buracos negros do Shell.
@ Evite: Não sei como dizer melhor. Eu removi a frase.
Jo
@ JoSo Isso é uma pena: não, a piada cai.
96

O repocomando não pode se importar com o tipo de cotação que recebe. Se você precisar de expansão de parâmetro, use aspas duplas. Se isso significa que você acaba tendo que inverter uma série de coisas, use aspas simples para a maioria delas e, em seguida, separe-as e entre em duplas na parte em que você precisa que a expansão ocorra.

repo forall -c 'literal stuff goes here; '"stuff with $parameters here"' more literal stuff'

A explicação segue, se você estiver interessado.

Quando você executa um comando a partir do shell, o que esse comando recebe como argumentos é uma matriz de cadeias terminadas em nulo. Essas cadeias podem conter absolutamente qualquer caractere não nulo.

Mas quando o shell está construindo esse conjunto de strings a partir de uma linha de comando, ele interpreta alguns caracteres especialmente; isso foi projetado para facilitar a digitação de comandos (na verdade, possível). Por exemplo, os espaços normalmente indicam o limite entre as strings na matriz; por esse motivo, os argumentos individuais às vezes são chamados de "palavras". Mas um argumento pode, no entanto, ter espaços; você só precisa de uma maneira de dizer ao shell que é isso que você deseja.

Você pode usar uma barra invertida na frente de qualquer caractere (incluindo espaço ou outra barra invertida) para dizer ao shell para tratar esse caractere literalmente. Mas enquanto você pode fazer algo assim:

echo \"Thank\ you.\ \ That\'ll\ be\ \$4.96,\ please,\"\ said\ the\ cashier

... pode ficar cansativo. Portanto, o shell oferece uma alternativa: aspas. Estes vêm em duas variedades principais.

Aspas duplas são chamadas de "aspas de agrupamento". Eles impedem que caracteres curinga e aliases sejam expandidos, mas principalmente são para incluir espaços em uma palavra. Outras coisas como expansão de parâmetro e comando (o tipo de coisa sinalizada por a $) ainda acontecem. E, claro, se você deseja uma aspas duplas literal dentro de aspas duplas, é necessário fazer uma barra invertida:

echo "\"Thank you. That'll be \$4.96, please,\" said the cashier"

Aspas simples são mais draconianas. Tudo entre eles é tomado completamente literalmente, incluindo barras invertidas. Não há absolutamente nenhuma maneira de obter uma citação única literal dentro de aspas simples.

Felizmente, as aspas no shell não são delimitadores de palavras ; sozinhos, eles não terminam uma palavra. Você pode entrar e sair de aspas, inclusive entre diferentes tipos de aspas, dentro da mesma palavra para obter o resultado desejado:

echo '"Thank you. That'\''ll be $4.96, please," said the cashier'

Portanto, isso é mais fácil - muito menos barras invertidas, embora a seqüência de aspas simples, de barra invertida e de aspas simples literal e de aspas simples demore um tempo para se acostumar.

Os shells modernos adicionaram outro estilo de citação não especificado pelo padrão POSIX, no qual as aspas simples à esquerda são prefixadas com um cifrão. As strings assim citadas seguem convenções semelhantes às literais de strings na linguagem de programação ANSI C e, portanto, às vezes são chamadas de "strings ANSI" e o par $'... '"aspas ANSI". Dentro de tais cadeias, o conselho acima sobre barras invertidas sendo literalmente não se aplica mais. Em vez disso, eles se tornam especiais novamente - não apenas você pode incluir aspas ou barra invertida literal colocando uma barra invertida antes, mas o shell também expande as fugas de caracteres ANSI C (como \npara uma nova linha, \tpara tab e \xHHpara o caractere com código hexadecimalHH) Caso contrário, no entanto, eles se comportam como cadeias de caracteres simples: nenhuma substituição de parâmetro ou comando ocorre:

echo $'"Thank you.  That\'ll be $4.96, please," said the cashier'

O importante a ser observado é que a única sequência recebida como argumento para o echocomando é exatamente a mesma em todos esses exemplos. Após o shell concluir a análise de uma linha de comando, não há como executar o comando para saber como foi citado. Mesmo que quisesse.

Mark Reed
fonte
2
Sim. Eu entendo agora. Funciona perfeitamente. Muito obrigado por sua ajuda!
Rachit
6
Essa resposta foi incrível.
Vinícius Ferrão
11
O $'string'formato é compatível com POSIX? Além disso, existe um nome para isso?
Curinga
2
@Wildcard $'string'é uma extensão não-POSIX, que eu já vi referida como "seqüências de caracteres ANSI"; Eu incorporei esses fatos na resposta. Tais strings funcionam na maioria dos shells modernos compatíveis com Bourne: bash, dash, ksh (AT&T e PD) e zsh todos os suportam.
Mark Reed
11
Link de volta - Quais caracteres precisam ser escapados nos argumentos da linha de comando? na troca de pilha Unix e Linux.
Curinga
3

Abaixo está o que funcionou para mim -

QUOTE="'"
hive -e "alter table TBL_NAME set location $QUOTE$TBL_HDFS_DIR_PATH$QUOTE"
Manmohan
fonte
2
Espero TBL_HDFS_DIR_PATH não é usuário contribuiu btw, isso permitiria que uma injeção de bom sql ...
domenukk
11
Colocar QUOTEem uma variável separada é completamente supérfluo; aspas simples dentro de aspas duplas são apenas um caractere comum. (Além disso, não use letras maiúsculas para as suas variáveis privadas.)
tripleee
2

EDIT: (Conforme os comentários em questão :)

Eu estive investigando isso desde então. Eu tive a sorte de ter repo deitado. Ainda não está claro para mim se você precisa colocar seus comandos entre aspas simples à força. Examinei a sintaxe do repositório e não acho que você precise. Você pode usar aspas duplas ao redor do seu comando e, em seguida, usar as aspas simples e duplas que precisar dentro, desde que você escape das aspas duplas.

ecv
fonte
Obrigado Kevin por sua ajuda.
Rachit
2

basta usar printf

ao invés de

repo forall -c '....$variable'

use printf para substituir o token de variável pela variável expandida.

Por exemplo:

template='.... %s'

repo forall -c $(printf "${template}" "${variable}")
AndrewD
fonte
Isso está quebrado; printfrealmente não compra nada aqui, e deixar de citar o comando subsititution causa novos problemas de cotação.
tripleee
0

Variáveis ​​podem conter aspas simples.

myvar=\'....$variable\'

repo forall -c $myvar
user3734617
fonte
Eles podem, mas este não é o caminho certo para fazê-lo - você ainda deve colocar "$myvar"aspas duplas.
tripleee
-2

Isso funciona para você?

eval repo forall -c '....$variable'
drgnfr
fonte
8
Faz para você? Esta questão é de cinco anos de idade e já tem algumas respostas, mesmo um aceita ...
Nico Haase