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")"