Lidar com vários níveis de citação (realmente, vários níveis de análise / interpretação) pode ser complicado. Isso ajuda a manter algumas coisas em mente:
- Cada "nível de citação" pode envolver um idioma diferente.
- As regras de cotação variam de acordo com o idioma.
- Ao lidar com mais de um ou dois níveis aninhados, geralmente é mais fácil trabalhar “de baixo para cima” (ou seja, da parte interna para a externa).
Níveis de cotação
Vamos dar uma olhada nos seus comandos de exemplo.
pgrep -fl java | grep -i datanode | awk '{print $1}'
Seu primeiro exemplo de comando (acima) usa quatro idiomas: seu shell, o regex no pgrep , o regex no grep (que pode ser diferente do idioma do regex no pgrep ) e o awk . Existem dois níveis de interpretação envolvidos: o shell e um nível após o shell para cada um dos comandos envolvidos. Há apenas um nível explícito de citação (shell citando no awk ).
ssh host …
Em seguida, você adicionou um nível de ssh na parte superior. Este é efetivamente outro nível de shell: o ssh não interpreta o comando em si, entrega-o a um shell na extremidade remota (via (por exemplo) sh -c …
) e esse shell interpreta a string.
ssh host "sudo su user -c …"
Então você perguntou sobre adicionar outro nível de shell no meio usando su (via sudo , que não interpreta seus argumentos de comando, para que possamos ignorá-lo). Neste ponto, você tem três níveis de aninhamento ( awk → shell, shell → shell ( ssh ), shell → shell ( su user -c ), portanto, aconselho o uso da abordagem "bottom-up". suas conchas são compatíveis com Bourne (por exemplo , sh , cinza , traço , ksh , bash , zsh , etc.) Algum outro tipo de concha ( peixe , rc, etc.) podem exigir sintaxe diferente, mas o método ainda se aplica.
Debaixo para cima
- Formule a sequência que você deseja representar no nível mais interno.
- Selecione um mecanismo de citação no repertório de citação do próximo idioma mais alto.
- Cite a sequência desejada de acordo com o mecanismo de cotação selecionado.
- Muitas vezes, existem muitas variações de como aplicar qual mecanismo de cotação. Fazer isso manualmente é geralmente uma questão de prática e experiência. Ao fazer isso de maneira programática, geralmente é melhor escolher o mais fácil de acertar (geralmente o "mais literal" (o menor número de fugas)).
- Opcionalmente, use a string entre aspas resultante com código adicional.
- Se você ainda não atingiu o nível desejado de citação / interpretação, pegue a sequência entre aspas resultante (mais qualquer código adicionado) e use-a como a sequência inicial na etapa 2.
A citação da semântica varia
O que deve ser lembrado aqui é que cada idioma (nível de citação) pode fornecer semântica ligeiramente diferente (ou mesmo semântica drasticamente diferente) ao mesmo caractere de citação.
A maioria dos idiomas possui um mecanismo de citação "literal", mas eles variam exatamente em como são literais. A citação simples de shells tipo Bourne é realmente literal (o que significa que você não pode usá-lo para citar um caractere de aspas simples). Outros idiomas (Perl, Ruby) são menos literais, pois interpretam algumas seqüências de barra invertida dentro de regiões entre aspas simples não literalmente (especificamente, \\
e \'
resultam em \
e '
, mas outras seqüências de barra invertida são realmente literais).
Você precisará ler a documentação de cada um dos seus idiomas para entender suas regras de cotação e a sintaxe geral.
Seu exemplo
O nível mais interno do seu exemplo é um programa awk .
{print $1}
Você vai incorporar isso em uma linha de comando do shell:
pgrep -fl java | grep -i datanode | awk …
Precisamos proteger (no mínimo) o espaço eo $
no awk programa. A escolha óbvia é usar aspas simples no shell em todo o programa.
Existem outras opções, porém:
{print\ \$1}
escapar diretamente do espaço e $
{print' $'1}
aspas simples apenas o espaço e $
"{print \$1}"
aspas duplas do todo e escapar do $
{print" $"1}
aspas duplas apenas o espaço e $
Isso pode estar dobrando um pouco as regras (sem escape $
no final de uma cadeia de caracteres entre aspas duplas é literal), mas parece funcionar na maioria dos shells.
Se o programa usasse uma vírgula entre as chaves de abertura e fechamento, também precisaríamos citar ou escapar da vírgula ou das chaves para evitar a "expansão da chave" em algumas conchas.
Nós escolhemos '{print $1}'
e incorporamos no restante do “código” do shell:
pgrep -fl java | grep -i datanode | awk '{print $1}'
Em seguida, você queria executar isso via su e sudo .
sudo su user -c …
su user -c …
é exatamente como some-shell -c …
(exceto executando sob outro UID), então su apenas adiciona outro nível de shell. O sudo não interpreta seus argumentos, portanto, não adiciona nenhum nível de citação.
Precisamos de outro nível de shell para nossa cadeia de comandos. Podemos escolher aspas simples novamente, mas precisamos dar um tratamento especial às aspas simples existentes. A maneira usual é assim:
'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Existem quatro strings aqui que o shell interpretará e concatenará: a primeira string entre aspas simples ( pgrep … awk
), uma aspas simples com escape, o programa awk com aspas simples , outra aspas simples com escape.
Existem, é claro, muitas alternativas:
pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}
escapar de tudo que é importante
pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}
o mesmo, mas sem espaço em branco supérfluo (mesmo no programa awk !)
"pgrep -fl java | grep -i datanode | awk '{print \$1}'"
aspas duplas a coisa toda, escape do $
'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"
sua variação; um pouco mais do que o habitual devido ao uso de aspas duplas (dois caracteres) em vez de escapes (um caractere)
O uso de aspas diferentes no primeiro nível permite outras variações nesse nível:
'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
'pgrep -fl java | grep -i datanode | awk {print\ \$1}'
A incorporação da primeira variação na linha de comando sudo / * su * fornece:
sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
Você pode usar a mesma string em qualquer outro contexto de nível de shell único (por exemplo ssh host …
).
Em seguida, você adicionou um nível de ssh na parte superior. Este é efetivamente outro nível de shell: o ssh não interpreta o comando em si, mas o entrega a um shell na extremidade remota (via (por exemplo) sh -c …
) e esse shell interpreta a string.
ssh host …
O processo é o mesmo: pegue a string, escolha um método de citação, use-o, incorpore-o.
Usando aspas simples novamente:
'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
Agora, existem onze cadeias de caracteres que são interpretadas e concatenadas 'sudo su user -c '
:, aspas simples escapadas, aspas simples 'pgrep … awk '
escapadas, barra invertida escapada, duas aspas simples escapadas, o programa awk entre aspas simples , uma aspas simples escapada, uma barra invertida escapada e uma única aspira final escapada .
A forma final é assim:
ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
É um pouco difícil de digitar manualmente, mas a natureza literal das aspas simples do shell facilita a automação de uma pequena variação:
#!/bin/sh
sq() { # single quote for Bourne shell evaluation
# Change ' to '\'' and wrap in single quotes.
# If original starts/ends with a single quote, creates useless
# (but harmless) '' at beginning/end of result.
printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}
# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }
ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"
ssh host "$(sq "$s2")"
Veja a resposta de Chris Johnsen para uma explicação clara e aprofundada com uma solução geral. Vou dar algumas dicas extras que ajudam em algumas circunstâncias comuns.
As aspas simples escapam de tudo, exceto uma única. Portanto, se você souber que o valor de uma variável não inclui aspas simples, é possível interpolá-lo com segurança entre aspas simples em um script de shell.
Se o seu shell local for ksh93 ou zsh, você poderá lidar com aspas simples na variável reescrevendo-as para
'\''
. (Embora o bash também tenha a${foo//pattern/replacement}
construção, seu tratamento de aspas simples não faz sentido para mim.)Outra dica para evitar ter que lidar com aspas aninhadas é passar seqüências de caracteres pelas variáveis de ambiente o máximo possível. Ssh e sudo tendem a descartar a maioria das variáveis de ambiente, mas geralmente são configuradas para permitir a
LC_*
passagem, porque normalmente são muito importantes para a usabilidade (contêm informações de localidade) e raramente são consideradas sensíveis à segurança.Aqui, como
LC_CMD
contém um trecho de shell, ele deve ser fornecido literalmente ao shell mais interno. Portanto, a variável é expandida pelo shell imediatamente acima. O shell mais interno mas um vê"$LC_CMD"
, e o shell mais interno vê os comandos.Um método semelhante é útil para passar dados para um utilitário de processamento de texto. Se você usar interpolação de shell, o utilitário tratará o valor da variável como um comando, por exemplo
sed "s/$pattern/$replacement/"
, não funcionará se as variáveis contiverem/
. Portanto, use awk (não sed) e sua-v
opção ou oENVIRON
array para transmitir dados do shell (se você passar por issoENVIRON
, lembre-se de exportar as variáveis).fonte
Como Chris Johnson descreve muito bem , você tem alguns níveis de indireção citada aqui; você instrui seu local
shell
a instruir o controle remotoshell
viassh
que ele devesudo
instruirsu
para instruir o controle remotoshell
a executar seu pipelinepgrep -fl java | grep -i datanode | awk '{print $1}'
comouser
. Esse tipo de comando requer muito tedioso\'"quote quoting"\'
.Se você seguir meu conselho, renunciará a toda bobagem e fará:
PARA MAIS:
Verifique minha resposta (talvez exagerada) aos caminhos de tubulação com diferentes tipos de cotações para substituição de barra, na qual discuto algumas das teorias por trás de por que isso funciona.
-Mike
fonte
<<
simplesmente substitui a primeira. Deveria dizer "zsh only" em algum lugar ou perdi alguma coisa? (Inteligente truque, porém, ter um heredoc que é parcialmente sujeitos a expansão local)Que tal usar mais aspas duplas?
Então você
ssh $host $CMD
deve funcionar bem com este:CMD="pgrep -fl java | grep -i datanode | awk '{print $1}'"
Agora, para o mais complexo, o
ssh $host "sudo su user -c \"$CMD\""
. Eu acho que tudo que você tem a fazer é escapar caracteres sensíveis emCMD
:$
,\
e"
. Então, eu ia tentar e ver se isso funciona:echo $CMD | sed -e 's/[$\\"]/\\\1/g'
.Se isso parecer bom, envolva o echo + sed em uma função shell e você estará pronto para usá-lo
ssh $host "sudo su user -c \"$(escape_my_var $CMD)\""
.fonte