it-swarm-pt.tech

Citando nas construções do tipo ssh $ Host $ FOO e ssh $ Host "Sudo su user -c $ FOO"

Freqüentemente acabo emitindo comandos complexos sobre ssh; esses comandos envolvem piping para awk ou Perl de uma linha e, como resultado, contêm aspas simples e $ 's. Não fui capaz de descobrir uma regra rígida e rápida para fazer as citações corretamente, nem encontrei uma boa referência para isso. Por exemplo, considere o seguinte:

# what I'd run locally:
CMD='pgrep -fl Java | grep -i datanode | awk '{print $1}'
# this works with ssh $Host "$CMD":
CMD='pgrep -fl Java | grep -i datanode | awk '"'"'{print $1}'"'"

(Observe as aspas extras na instrução awk.)

Mas como faço para que isso funcione, por exemplo, ssh $Host "Sudo su user -c '$CMD'"? Existe uma receita geral para gerenciar cotações em tais cenários? ..

31
Leo Alekseyev

Lidar com vários níveis de citação (na verdade, 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 potencialmente uma linguagem 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, do interior para o exterior).

Níveis de citação

Vejamos 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 em pgrep , o regex em grep (que pode ser diferente da linguagem regex em pgrep ) e 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. Existe apenas um nível explícito de citação (Shell citação em awk ).

ssh Host …

Em seguida, você adicionou um nível de ssh no topo. Este é efetivamente outro nível de Shell: ssh não interpreta o comando em si, ele o entrega a um Shell na extremidade remota (via (por exemplo) sh -c …) e que Shell interpreta a string.

ssh Host "Sudo su user -c …"

Então, você perguntou sobre a adição de outro nível de Shell no meio usando su (via Sudo , que não interpreta seus argumentos de comando, portanto, podemos ignorá-lo). Neste ponto, você tem três níveis de aninhamento em andamento ( awk → Shell, Shell → Shell ( ssh ), Shell → Shell ( su user -c ), então aconselho usar a abordagem “de baixo para cima”. Vou assumir que seus shells são compatíveis com Bourne (por exemplo sh , ash , traço , ksh , bash , zsh , etc.). Algum outro tipo de Shell ( fish , rc , etc.) pode exigir sintaxe diferente, mas o método ainda se aplica.

Debaixo para cima

  1. Formule a string que deseja representar no nível mais interno.
  2. Selecione um mecanismo de citações do repertório de citações do idioma seguinte.
  3. Cite a string desejada de acordo com o mecanismo de cotação selecionado.
    • Freqüentemente, há muitas variações de como aplicar qual mecanismo de cotação. Fazer manualmente é geralmente uma questão de prática e experiência. Ao fazer isso de forma programática, geralmente é melhor escolher o mais fácil de acertar (geralmente o “mais literal” (menos escapes)).
  4. Opcionalmente, use a string entre aspas resultante com código adicional.
  5. Se você ainda não atingiu o nível desejado de citação/interpretação, pegue a string entre aspas resultante (mais qualquer código adicionado) e use-a como string inicial na etapa 2.

A semântica das citações varia

O que se deve ter em mente aqui é que cada idioma (nível de citação) pode fornecer semânticas ligeiramente diferentes (ou mesmo semânticas drasticamente diferentes) para o mesmo personagem de citação.

A maioria das linguagens tem um mecanismo de citação “literal”, mas eles variam exatamente em quão literais são. A aspa simples de shells do tipo Bourne é literal (o que significa que você não pode usá-la para citar um caractere de aspas simples). Outras linguagens (Perl, Ruby) são menos literais porque interpretam algumas sequências de barra invertida dentro de regiões entre aspas simples não literalmente (especificamente, \\ e \' resulta em \ e ', mas outras sequências de barra invertida são, na verdade, literais).

Você terá que ler a documentação de cada um de 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 e o $ no programa awk . A escolha óbvia é usar aspas simples no Shell em todo o programa.

  • '{print $1}'

No entanto, existem outras opções:

  • {print\ \$1} escape diretamente do espaço e $
  • {print' $'1} aspas simples apenas o espaço e $
  • "{print \$1}" aspas duplas o todo e escapar de $
  • {print" $"1} aspas duplas apenas o espaço e $
    Isso pode estar distorcendo um pouco as regras (sem escape $ no final de uma string 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 a vírgula ou as chaves para evitar a “expansão da chave” em alguns shells.

Nós escolhemos '{print $1}' e incorporá-lo ao resto do “código” 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 … é como some-Shell -c … (exceto rodando em algum outro UID), então su apenas adiciona outro nível de Shell. Sudo não interpreta seus argumentos, então não adiciona nenhum nível de citação.

Precisamos de outro nível de Shell para nossa string de comando. Podemos escolher aspas simples novamente, mas temos que dar 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 irá interpretar e concatenar: a primeira string entre aspas simples (pgrep … awk), uma aspa simples de escape, o programa de aspas simples awk , outra aspa simples de escape.

Existem, é claro, muitas alternativas:

  • pgrep\ -fl\ Java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1} fuja de tudo que é importante
  • pgrep\ -fl\ Java\|grep\ -i\ datanode\|awk\ \'{print\$1} o mesmo, mas sem espaços em branco supérfluos (mesmo no programa awk !)
  • "pgrep -fl Java | grep -i datanode | awk '{print \$1}'" aspas duplas, escape de $
  • 'pgrep -fl Java | grep -i datanode | awk '"'"'{print \$1}'"'" sua variação; um pouco mais longo do que o normal devido ao uso de aspas duplas (dois caracteres) em vez de escapes (um caractere)

Usar citações diferentes no primeiro nível permite outras variações neste nível:

  • 'pgrep -fl Java | grep -i datanode | awk "{print \$1}"'
  • 'pgrep -fl Java | grep -i datanode | awk {print\ \$1}'

Incorporar a primeira variação na linha de comando Sudo /* su * fornece o seguinte:

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 (por exemplo, ssh Host …).

Em seguida, você adicionou um nível de ssh no topo. Este é efetivamente outro nível de Shell: ssh não interpreta o comando em si, mas o entrega a um Shell na extremidade remota (via (por exemplo) sh -c …) e que Shell interpreta a string.

ssh Host …

O processo é o mesmo: pegue a string, escolha um método de cotação, use-a, incorpore-a.

Usando aspas simples novamente:

'Sudo su user -c '\''pgrep -fl Java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

Agora, existem onze strings que são interpretadas e concatenadas: 'Sudo su user -c ', aspas simples com escape, 'pgrep … awk ', aspas simples de escape, barra invertida de escape, duas aspas simples de escape, o programa entre aspas simples awk , aspas simples de escape, barra invertida de escape e um aspas simples com escape final.

A forma final é parecida com esta:

ssh Host 'Sudo su user -c '\''pgrep -fl Java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'

É um pouco complicado digitar à mão, mas a natureza literal da citação única do Shell torna mais fácil automatizar uma ligeira 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")"
35
Chris Johnsen

Veja resposta de Chris Johnsen para uma explicação clara e detalhada 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 aspa simples. Portanto, se você sabe que o valor de uma variável não inclui nenhuma aspa simples, pode interpolar com segurança entre aspas simples em um script Shell.

su -c "grep '$pattern' /root/file"  # assuming there is no ' in $pattern

Se seu Shell local for ksh93 ou zsh, você pode lidar com aspas simples na variável reescrevendo-as para '\''. (Embora o bash também tenha o ${foo//pattern/replacement} construção, seu tratamento de aspas simples não faz sentido para mim.)

su -c "grep '${pattern//'/'\''}' /root/file"  # if the outer Shell is zsh
su -c "grep '${pattern//\'/\'\\\'\'}' /root/file"  # if the outer Shell is ksh93

Outra dica para evitar ter que lidar com aspas aninhadas é passar strings por variáveis ​​de ambiente tanto quanto possível. Ssh e Sudo tendem a eliminar a maioria das variáveis ​​de ambiente, mas geralmente são configurados para permitir que LC_* até, porque normalmente são muito importantes para a usabilidade (contêm informações de localidade) e raramente são considerados confidenciais.

LC_CMD='what you would use locally' ssh $Host 'Sudo su user -c "$LC_CMD"'

Aqui, desde LC_CMD contém um fragmento de Shell, ele deve ser fornecido literalmente ao Shell mais interno. Portanto, a variável é expandida pelo Shell imediatamente acima. O mais interno, mas um, Shell 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 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 ou seu -v opção ou o array ENVIRON para passar dados do Shell (se você passar por ENVIRON, lembre-se de exportar as variáveis).

awk -vpattern="$pattern" replacement="$replacement" '{gsub(pattern,replacement); print}'
7

Como Chris Johnson descreve muito bem , você tem alguns níveis de indireção citados aqui; você instrui seu local Shell para instruir o remoto Shell via ssh que deve instruir Sudo para instruir su para instruir o remoto Shell para executar seu pipeline pgrep -fl Java | grep -i datanode | awk '{print $1}' como user. Esse tipo de comando exige muito \'"quote quoting"\'.

Se você seguir meu conselho, vai abrir mão de todas as bobagens e fazer:

% ssh $Host <<REM=LOC_EXPANSION <<'REMOTE_CMD' |
> localhost_data='$(commands run on localhost at runtime)' #quotes don't affect expansion
> more_localhost_data="$(values set at heredoc expansion)" #remote Shell will receive m_h_d="result"
> REM=LOC_EXPANSION
> commands typed exactly as if located at 
> the remote terminal including variable 
> "${{more_,}localhost_data}" operations
> 'quotes and' \all possibly even 
> a\wk <<'REMOTELY_INVOKED_HEREDOC' |
> {as is often useful with $awk
> so long as the terminator for}
> REMOTELY_INVOKED_HEREDOC
> differs from that of REM=LOC_EXPANSION and
> REMOTE_CMD
> and here you can | pipeline operate on |\
> any output | received from | ssh as |\
> run above | in your local | terminal |\
> however | tee > ./you_wish.result
<desired output>

PARA MAIS:

Verifique minha resposta (talvez muito prolixa) para Tubulação de caminhos com diferentes tipos de aspas para substituição de barra na qual discuto parte da teoria por trás de por que isso funciona.

-Mike

2
mikeserv