Como escapar aspas simples em cadeias de caracteres entre aspas simples


1018

Digamos que você tenha um Bash aliascomo:

alias rxvt='urxvt'

o que funciona bem

Contudo:

alias rxvt='urxvt -fg '#111111' -bg '#111111''

não funcionará e nem:

alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''

Então, como você acaba combinando as aspas de abertura e fechamento dentro de uma string depois de ter escapado das aspas?

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''

parece desajeitado, embora represente a mesma string se você puder concatená-las dessa maneira.


16
Você percebe que não precisa usar aspas simples para o alias? Aspas duplas são muito mais fáceis.
Teknopaul


3
As aspas duplas aninhadas são escapáveis; "\""portanto, essas devem ser usadas preferencialmente à resposta de @ liori sempre que possível.
quer

7
As aspas duplas se comportam de maneira bem diferente das aspas simples no * nix (incluindo Bash e ferramentas relacionadas como Perl), portanto, substituir aspas duplas sempre que houver um problema com aspas simples NÃO é uma boa solução. Aspas duplas especificam $ ... variáveis ​​devem ser substituídas antes da execução, enquanto as aspas simples especificam $ ... devem ser tratadas literalmente.
Chuck Kollars 28/05/19

Se você está pensando, eu usei aspas duplas, mas ainda não está funcionando , crie seu script novamente.
Samy Bencherif

Respostas:


1456

Se você realmente deseja usar aspas simples na camada mais externa, lembre-se de colar os dois tipos de cotação. Exemplo:

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
 #                     ^^^^^       ^^^^^     ^^^^^       ^^^^
 #                     12345       12345     12345       1234

Explicação de como '"'"'é interpretado como justo ':

  1. ' Finalize a primeira cotação que usa aspas simples.
  2. " Inicie a segunda cotação, usando aspas duplas.
  3. ' Caractere citado.
  4. " Finalize a segunda cotação, usando aspas duplas.
  5. ' Inicie a terceira cotação, usando aspas simples.

Se você não colocar espaços em branco entre (1) e (2) ou entre (4) e (5), o shell interpretará essa sequência como uma palavra longa.


5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Funciona quando há aspas simples e duplas na string de alias!
Uphill_ O que '1

17
Minha interpretação: bash implicitamente concatena expressões de string com citações diferentes.
Benjamin Atkin

2
trabalhou para mim, exemplo de dupla escapou aspas simples:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESSTONEco

2
Certamente não é a solução mais legível. Usa sobre aspas simples onde não são realmente necessárias.
Oberlies

26
Eu afirmo que '\''é muito mais legível na maioria dos contextos do que '"'"'. De fato, o primeiro é quase sempre claramente distinto dentro de uma string de aspas simples e, portanto, é apenas uma questão de mapeá-lo semanticamente para o significado de "é uma citação escapada", como acontece com as \"strings de aspas duplas. Considerando que o último se mistura em uma linha de ticks de cotação e precisa de inspeção cuidadosa em muitos casos para distinguir adequadamente.
Mtraceur # 5/16

263

Eu sempre substituo cada aspas simples incorporada pela sequência: '\''(que é: aspas de barra invertida entre aspas) que fecha a sequência, anexa uma aspas simples escapada e reabre a sequência.


Costumo criar uma função "quotify" nos meus scripts Perl para fazer isso por mim. Os passos seriam:

s/'/'\\''/g    # Handle each embedded quote
$_ = qq['$_']; # Surround result with single quotes.

Isso praticamente cuida de todos os casos.

A vida fica mais divertida quando você introduz evalseus scripts de shell. Você basicamente tem que refazer tudo novamente!

Por exemplo, crie um script Perl chamado quotify contendo as instruções acima:

#!/usr/bin/perl -pl
s/'/'\\''/g;
$_ = qq['$_'];

use-o para gerar uma sequência de caracteres corretamente citada:

$ quotify
urxvt -fg '#111111' -bg '#111111'

resultado:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

que pode ser copiado / colado no comando alias:

alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

(Se você precisar inserir o comando em uma avaliação, execute o quotify novamente:

 $ quotify
 alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

resultado:

'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

que pode ser copiado / colado em uma avaliação:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

1
Mas isso não é perl. E como Steve B apontou acima, com sua referência ao "manual de referência do gnu", você não pode escapar de aspas no bash dentro do mesmo tipo de cotação. E, na verdade, não precisa escapar deles dentro de aspas alternativos, por exemplo, "'" é uma string de aspas simples válido e '"' é uma string de aspas duplas válido sem necessidade de qualquer fuga.
nicerobot

8
@nicerobot: Adicionei um exemplo mostrando que: 1) Não tento escapar de aspas dentro do mesmo tipo de cotação, 2) nem em aspas alternativas, e 3) Perl é usado para automatizar o processo de geração de um código válido corda festa containg citações embutidas
Adrian Pronk

18
O primeiro parágrafo por si só é a resposta que eu estava procurando.
Dave Causey

9
Isto é o que o bash faz tão bem, digite set -xe echo "here's a string"e você verá que executa festança echo 'here'\''s a string'. ( set +xPara retornar o comportamento normal)
arekolek

196

Desde a sintaxe do Bash 2.04$'string' (em vez de apenas 'string'; aviso: não confunda com $('string')) é outro mecanismo de citação que permite seqüências de escape do tipo ANSI C e faz expansão para a versão de citação única.

Exemplo simples:

  $> echo $'aa\'bb'
  aa'bb

  $> alias myvar=$'aa\'bb'
  $> alias myvar
  alias myvar='aa'\''bb'

No seu caso:

$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
$> alias rxvt
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Sequências de escape comuns funcionam conforme o esperado:

\'     single quote
\"     double quote
\\     backslash
\n     new line
\t     horizontal tab
\r     carriage return

Abaixo está a cópia + documentação relacionada colada de man bash(versão 4.4):

Palavras da forma $ 'string' são tratadas especialmente. A palavra se expande para cadeia, com caracteres de escape com barra invertida substituídos conforme especificado pelo padrão ANSI C. Seqüências de escape de barra invertida, se presentes, são decodificadas da seguinte maneira:

    \a     alert (bell)
    \b     backspace
    \e
    \E     an escape character
    \f     form feed
    \n     new line
    \r     carriage return
    \t     horizontal tab
    \v     vertical tab
    \\     backslash
    \'     single quote
    \"     double quote
    \?     question mark
    \nnn   the eight-bit character whose value is the octal 
           value nnn (one to three digits)
    \xHH   the eight-bit character whose value is the hexadecimal
           value HH (one or two hex digits)
    \uHHHH the Unicode (ISO/IEC 10646) character whose value is 
           the hexadecimal value HHHH (one to four hex digits)
    \UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value 
               is the hexadecimal value HHHHHHHH (one to eight 
               hex digits)
    \cx    a control-x character

O resultado expandido é de aspas simples, como se o cifrão não estivesse presente.


Veja Citações e escape: ANSI C como strings no wiki do bash-hackers.org para mais detalhes. Observe também que o arquivo "Bash Changes" ( visão geral aqui ) menciona muito as alterações e correções de bugs relacionadas ao $'string'mecanismo de cotação.

De acordo com unix.stackexchange.com Como usar um caractere especial como normal? deve funcionar (com algumas variações) no bash, zsh, mksh, ksh93 e FreeBSD e busybox sh.


poderia ser usado, mas a única cadeia entre aspas aqui não é uma verdadeira único citado, o conteúdo deste texto poderá ser interprested pelo shell: echo $'foo\'b!ar'=> !ar': event not found
regilero

2
Na minha máquina > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41

1
Sim, essa é a razão do "may", eu o coloquei no Red Hat 6.4, certamente uma versão mais antiga do bash.
regilero

O Bash ChangeLog contém muitas correções de erros relacionados à $'maneira mais provavelmente mais fácil de tentar você mesmo em sistemas mais antigos.
Mj41

esteja ciente: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.Extraído de tiswww.case.edu/php/chet/bash/CHANGES . Ainda funciona em 4.3.42, mas não em 4.3.48.
stiller_leser

49

Não vejo a entrada em seu blog (link pls?), Mas de acordo com o manual de referência do gnu :

Os caracteres entre aspas simples ('' ') preservam o valor literal de cada caractere entre aspas. Uma aspas simples pode não ocorrer entre aspas simples, mesmo quando precedida por uma barra invertida.

então bash não vai entender:

alias x='y \'z '

no entanto, você pode fazer isso se estiver entre aspas duplas:

alias x="echo \'y "
> x
> 'y


O conteúdo entre aspas duplas está sendo avaliado, portanto, incluir apenas aspas simples entre aspas duplas, conforme sugerido por liori, parece ser a solução adequada.
Piotr Dobrogost

3
Esta é a resposta real à pergunta. Embora a resposta aceita possa fornecer uma solução, ela está tecnicamente respondendo a uma pergunta que não foi feita.
Matthew G

3
Matthew, a questão era escapar de aspas simples dentro de aspas simples. Esta resposta solicita que o usuário altere seu comportamento e, se você tiver um impedimento de usar aspas duplas (como o título da pergunta sugere), essa resposta não ajudaria. É bastante útil (embora óbvio) e, como tal, merece um voto positivo, mas a resposta aceita aborda o problema exato que Op perguntou.
Fernando Cordeiro

Não é necessário citar uma aspas simples em uma sequência de aspas duplas.
Matthew D. Scholefield

32

Posso confirmar que o uso '\''de uma única citação dentro de uma sequência de aspas simples funciona no Bash e pode ser explicado da mesma maneira que o argumento "colar" anteriormente no thread. Suponha que tenhamos uma string entre aspas: 'A '\''B'\'' C'(todas as aspas aqui são aspas simples). Se ele é passado para echo, ele imprime o seguinte: A 'B' C. Em cada uma, '\''a primeira citação fecha a cadeia de citação única atual, a seguir \'cola uma citação única na cadeia de caracteres anterior ( \'é uma maneira de especificar uma citação única sem iniciar uma cadeia de citação) e a última citação abre outra cadeia de citação única.


2
Isso é enganoso, essa sintaxe '\' 'não entra "dentro" de uma única string entre aspas. Nesta declaração 'A' \ '' B '\' 'C', você está concatenando 5 \ escape e strings de aspas simples
teknopaul

1
@teknopaul A atribuição alias something='A '\''B'\'' C'resulta em somethinguma única sequência, portanto, mesmo que o lado direito da tarefa não seja tecnicamente uma única sequência, acho que não importa muito.
Teemu Leisti 7/09/16

Embora isso funcione no seu exemplo, tecnicamente não está fornecendo uma solução para como inserir uma única citação dentro de uma única string entre aspas. Você já explicou, mas está fazendo 'A ' + ' + 'B' + ' + ' C'. Em outras palavras, uma solução para inserir caracteres de aspas simples em uma sequência de aspas simples deve permitir que eu crie essa sequência sozinha e imprima. No entanto, esta solução não funcionará neste caso. STR='\''; echo $STR. Conforme projetado, o BASH realmente não permite isso.
precisa saber é o seguinte

@mikhail_b, sim, '\''funciona para o bash. Você poderia apontar quais seções do gnu.org/software/bash/manual/bashref.html especificam esse comportamento?
Jingguo Yao 16/09/16

20

Ambas as versões estão funcionando, com concatenação usando o caractere de aspas simples (\ ') ou com concatenação colocando o caractere de aspas simples entre aspas duplas ("'").

O autor da pergunta não percebeu que havia uma citação única extra (') no final de sua última tentativa de fuga:

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
           │         │┊┊|       │┊┊│     │┊┊│       │┊┊│
           └─STRING──┘┊┊└─STRIN─┘┊┊└─STR─┘┊┊└─STRIN─┘┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      └┴─────────┴┴───┰───┴┴─────────┴┘│
                          All escaped single quotes    │
                                                       │
                                                       ?

Como você pode ver na bela peça anterior da arte ASCII / Unicode, a última aspas simples (\ ') com escape é seguida por uma aspas simples desnecessária ('). Usar um marcador de sintaxe como o presente no Notepad ++ pode ser muito útil.

O mesmo vale para outro exemplo como o seguinte:

alias rc='sed '"'"':a;N;$!ba;s/\n/, /g'"'"
alias rc='sed '\'':a;N;$!ba;s/\n/, /g'\'

Essas duas belas instâncias de aliases mostram de maneira muito complexa e ofuscada como um arquivo pode ser alinhado. Ou seja, de um arquivo com muitas linhas, você obtém apenas uma linha com vírgulas e espaços entre o conteúdo das linhas anteriores. Para entender o comentário anterior, a seguir é um exemplo:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214

3
Upwoted para a ilustração da tabela ASCII :)
php-dev

16

Exemplo simples de escape de aspas no shell:

$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc

Isso é feito finalizando um já aberto ( '), colocando um escapado ( \') e depois outro ( '). Essa sintaxe funciona para todos os comandos. É uma abordagem muito semelhante à 1ª resposta.


15

Não estou abordando especificamente a questão das citações porque, bem, às vezes, é apenas razoável considerar uma abordagem alternativa.

rxvt() { urxvt -fg "#${1:-000000}" -bg "#${2:-FFFFFF}"; }

que você pode chamar como:

rxvt 123456 654321

a ideia é que agora você pode alias isso sem se preocupar com aspas:

alias rxvt='rxvt 123456 654321'

ou, se você precisar incluir #todas as chamadas por algum motivo:

rxvt() { urxvt -fg "${1:-#000000}" -bg "${2:-#FFFFFF}"; }

que você pode chamar como:

rxvt '#123456' '#654321'

então, é claro, um alias é:

alias rxvt="rxvt '#123456' '#654321'"

(opa, acho que meio que resolvi a citação :)


1
Eu estava tentando colocar algo entre aspas simples que estivesse entre aspas duplas que, por sua vez, estavam entre aspas simples. Caramba. Obrigado pela resposta de "tente uma abordagem diferente". Isso fez a diferença.
Clinton Blackmore

1
Estou com 5 anos de atraso, mas você não perdeu uma única citação no seu último apelido?
Julien

1
@Julien Eu não vejo um problema ;-)
nicerobot

11

Como não é possível colocar aspas simples em seqüências de caracteres entre aspas, a opção mais simples e legível é usar uma sequência HEREDOC

command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)

alias rxvt=$command

No código acima, o HEREDOC é enviado ao catcomando e a saída disso é atribuída a uma variável por meio da notação de substituição de comando$(..)

É necessário colocar uma única cotação no HEREDOC, pois ele está dentro de um $()


Eu gostaria de ter rolado até aqui antes - reinventei essa abordagem e vim aqui para publicá-la! Isso é muito mais limpo e legível do que todas as outras abordagens de escape. Não, ele não funcionará em alguns shells que não sejam do bash, como dashqual é o shell padrão nos scripts iniciantes do Ubuntu e em outros lugares.
Korny

Obrigado! que o que eu procurei, a maneira de definir um comando como é via heredoc e passar o comando escape automático para ssh. BTW cat << COMMAND sem aspas permite interpolar variáveis ​​dentro do comando e funciona também para esta abordagem.
Igor Tverdovskiy

10

Eu apenas uso códigos de shell ... por exemplo, \x27ou \\x22conforme aplicável. Sem problemas, realmente.


Você poderia mostrar um exemplo disso em operação? Para mim ele só imprime uma literal x27(no CentOS 6.6)
Will Sheppard

6

A maioria dessas respostas ocorre no caso específico sobre o qual você está perguntando. Há uma abordagem geral que um amigo e tenho desenvolvido que permite arbitrária citando em caso de necessidade de citação do bash comandos através de várias camadas de expansão shell, por exemplo, através de ssh, su -c, bash -c, etc. Há um núcleo primitivo você precisa, aqui no bash nativo:

quote_args() {
    local sq="'"
    local dq='"'
    local space=""
    local arg
    for arg; do
        echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
        space=" "
    done
}

Isso faz exatamente o que diz: aspas de cada argumento individualmente (após a expansão do bash, é claro):

$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'

Faz o óbvio para uma camada de expansão:

$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2

(Observe que as aspas duplas $(quote_args ...)são necessárias para transformar o resultado em um único argumento para bash -c.) E pode ser usado de maneira mais geral para citar corretamente através de várias camadas de expansão:

$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2

O exemplo acima:

  1. aspas shell cada argumento para o interior quote_args individualmente e, em seguida, combina a saída resultante em um único argumento com as aspas duplas internas.
  2. citações do shell bash,-c e o resultado já uma vez citado a partir do passo 1, e em seguida, combina o resultado em uma única discussão com as aspas duplas exteriores.
  3. envia essa bagunça como argumento para o exterior bash -c.

Essa é a ideia em poucas palavras. Você pode fazer algumas coisas bem complicadas com isso, mas precisa ter cuidado com a ordem da avaliação e com quais substrings são citados. Por exemplo, o seguinte faz as coisas erradas (para alguma definição de "errado"):

$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure

No primeiro exemplo, o bash se expande imediatamente quote_args cd /; pwd 1>&2para dois comandos separados quote_args cd /e pwd 1>&2, portanto, o CWD permanece /tmpquando o pwdcomando é executado. O segundo exemplo ilustra um problema semelhante para globbing. De fato, o mesmo problema básico ocorre com todas as expansões do bash. O problema aqui é que uma substituição de comando não é uma chamada de função: está literalmente avaliando um script bash e usando sua saída como parte de outro script bash.

Se você tentar simplesmente escapar dos operadores de shell, falhará porque a cadeia resultante transmitida bash -cé apenas uma sequência de cadeias citadas individualmente que não são interpretadas como operadores, o que é fácil de ver se você faz eco à cadeia que seria foram passados ​​para o bash:

$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'

O problema aqui é que você está citando demais. O que você precisa é que os operadores não sejam cotados como entrada para o anexo bash -c, o que significa que eles precisam estar fora da $(quote_args ...)substituição de comando.

Consequentemente, o que você precisa fazer no sentido mais geral é citar shell cada palavra do comando que não pretende ser expandida no momento da substituição do comando separadamente e não aplicar nenhuma citação extra aos operadores do shell:

$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success

Depois de fazer isso, a sequência inteira é um jogo justo para citar ainda mais os níveis arbitrários de avaliação:

$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success

etc.

Esses exemplos podem parecer exagerados, considerando que palavras como success, sbine pwdnão precisam ser citadas pelo shell, mas o ponto-chave a ser lembrado ao escrever um script com entrada arbitrária é que você deseja citar tudo o que não tem certeza absoluta de que não é ' Não é necessário citar, porque você nunca sabe quando um usuário lançará um Robert'; rm -rf /.

Para entender melhor o que está acontecendo debaixo das cobertas, você pode brincar com duas pequenas funções auxiliares:

debug_args() {
    for (( I=1; $I <= $#; I++ )); do
        echo -n "$I:<${!I}> " 1>&2
    done
    echo 1>&2
}

debug_args_and_run() {
    debug_args "$@"
    "$@"
}

que enumerará cada argumento em um comando antes de executá-lo:

$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

Oi Kyle. Sua solução funcionou muito bem para um caso que eu tive, quando eu precisava para passar um grupo de argumentos como um único argumento: vagrant ssh -c {single-arg} guest. Ele {single-arg}precisa ser tratado como um único argumento, porque o vagrant aceita o próximo argumento depois como o nome do convidado. A ordem não pode ser alterada. Mas eu precisava passar um comando e seus argumentos dentro {single-arg}. Então eu usei o seu quote_args()citar o comando e seus argumentos, e colocar aspas em torno do resultado, e funcionou como um encanto: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Obrigado!!!
Andreas Maier

6

IMHO, a resposta real é que você não pode escapar de aspas simples em strings de aspas simples.

É impossível.

Se presumimos que estamos usando o bash.

Do manual do bash ...

Enclosing characters in single quotes preserves the literal value of each
character within the quotes.  A single quote may not occur
between single quotes, even when preceded by a backslash.

Você precisa usar um dos outros mecanismos de escape de cadeia "ou \

Não há nada mágico sobre alias nisso que exija o uso de aspas simples.

Ambos os trabalhos a seguir no bash.

alias rxvt="urxvt -fg '#111111' -bg '#111111'"
alias rxvt=urxvt\ -fg\ \'#111111\'\ -bg\ \'#111111\'

O último está usando \ para escapar do caractere de espaço.

Também não há nada mágico no # 111111 que exija aspas simples.

As seguintes opções alcançam o mesmo resultado que as outras duas opções, pois o alias rxvt funciona conforme o esperado.

alias rxvt='urxvt -fg "#111111" -bg "#111111"'
alias rxvt="urxvt -fg \"#111111\" -bg \"#111111\""

Você também pode escapar do problemático # diretamente

alias rxvt="urxvt -fg \#111111 -bg \#111111"

"a resposta real é que você não pode escapar de aspas simples dentro de strings de aspas simples." Isso é tecnicamente verdade. Mas você pode ter uma solução que comece com uma aspas simples, termine com uma aspas simples e contenha apenas aspas simples no meio. stackoverflow.com/a/49063038
wisbucky

Não escapando, apenas pela concatenação.
precisa saber é

4

No exemplo dado, basta usar aspas duplas em vez de aspas simples como mecanismo de escape externo:

alias rxvt="urxvt -fg '#111111' -bg '#111111'"

Essa abordagem é adequada para muitos casos em que você apenas deseja passar uma string fixa para um comando: verifique como o shell interpretará a string de aspas duplas por meio de um echo e, se necessário, escape caracteres com barra invertida.

No exemplo, você verá que aspas duplas são suficientes para proteger a string:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'

4

Obviamente, seria mais fácil simplesmente colocar entre aspas duplas, mas qual é o desafio nisso? Aqui está a resposta usando apenas aspas simples. Estou usando uma variável em vez de, aliasentão é mais fácil imprimir para prova, mas é o mesmo que usar alias.

$ rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'
$ echo $rxvt
urxvt -fg '#111111' -bg '#111111'

Explicação

A chave é que você pode fechar a aspas simples e reabri-las quantas vezes quiser. Por exemplo, foo='a''b'é o mesmo que foo='ab'. Assim, você pode fechar a aspas simples, inserir uma aspas simples literal \'e reabrir a próxima aspas simples.

Diagrama de detalhamento

Este diagrama deixa claro usando colchetes para mostrar onde as aspas simples são abertas e fechadas. As aspas não são "aninhadas", como podem ser parênteses. Você também pode prestar atenção ao realce de cores, que é aplicado corretamente. As strings citadas são marrons, enquanto as \'são pretas.

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'    # original
[^^^^^^^^^^] ^[^^^^^^^] ^[^^^^^] ^[^^^^^^^] ^    # show open/close quotes
 urxvt -fg   ' #111111  '  -bg   ' #111111  '    # literal characters remaining

(Essa é essencialmente a mesma resposta que a de Adrian, mas acho que isso explica melhor. Também a resposta dele tem duas aspas simples supérfluas no final.)


+1 por usar o '\''método que eu recomendo sobre o '"'"'método que geralmente é mais difícil para os humanos lerem.
mtraceur

3

Aqui está uma elaboração sobre A resposta verdadeira única, mencionada acima:

Às vezes eu vou baixar usando o rsync over ssh e tenho que escapar de um nome de arquivo com um 'DUAS VEZES! (OMG!) Uma vez para o bash e outra para o ssh. O mesmo princípio de delimitadores de cotação alternados está em ação aqui.

Por exemplo, digamos que queremos obter: as histórias de Louis Theroux em LA ...

  1. Primeiro, você coloca Louis Theroux entre aspas simples para bash e aspas duplas para ssh: '"Louis Theroux"'
  2. Então você usa aspas simples para escapar de aspas duplas '"'
  3. O uso de aspas duplas para escapar do apóstrofo "'"
  4. Em seguida, repita o número 2, usando aspas simples para escapar de aspas duplas '"'
  5. Em seguida, coloque LA Stories entre aspas simples para bash e aspas duplas para ssh: '"LA Stories"'

E eis! Você acaba com isso:

rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'

que é muito trabalho para um pouco '- mas lá vai você


3
shell_escape () {
    printf '%s' "'${1//\'/\'\\\'\'}'"
}

Explicação da implementação:

  • aspas duplas, para que possamos gerar facilmente aspas simples e usar a ${...}sintaxe

  • A pesquisa e substituição do bash se parece com: ${varname//search/replacement}

  • estamos substituindo 'por'\''

  • '\''codifica um único 'assim:

    1. ' termina a citação única

    2. \'codifica a '(a barra invertida é necessária porque não estamos entre aspas)

    3. ' inicia a citação única novamente

    4. o bash concatena automaticamente as strings sem espaço em branco entre

  • há um \antes de cada \e 'porque essas são as regras de escape ${...//.../...}.

string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded:  $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"

PS Sempre codifique para seqüências de caracteres simples entre aspas, porque elas são muito mais simples do que cadeias de caracteres entre aspas duplas.


2

Outra maneira de corrigir o problema de muitas camadas de cotação aninhada:

Você está tentando colocar muito em um espaço muito pequeno, então use uma função bash.

O problema é que você está tentando ter muitos níveis de aninhamento, e a tecnologia básica de alias não é poderosa o suficiente para acomodar. Use uma função bash como esta para fazer com que as aspas simples e duplas de retorno e os parâmetros passados ​​sejam todos tratados normalmente como seria de esperar:

lets_do_some_stuff() {
    tmp=$1                       #keep a passed in parameter.
    run_your_program $@          #use all your passed parameters.
    echo -e '\n-------------'    #use your single quotes.
    echo `date`                  #use your back ticks.
    echo -e "\n-------------"    #use your double quotes.
}
alias foobarbaz=lets_do_some_stuff

Em seguida, você pode usar suas variáveis ​​$ 1 e $ 2 e aspas simples e duplas e back ticks sem se preocupar com a função de alias que destrói sua integridade.

Este programa imprime:

el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385
alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------

2

Se você possui o GNU Parallel instalado, pode usar sua citação interna:

$ parallel --shellquote
L's 12" record
<Ctrl-D>
'L'"'"'s 12" record'
$ echo 'L'"'"'s 12" record'
L's 12" record

A partir da versão 20190222, você pode até --shellquotevárias vezes:

$ parallel --shellquote --shellquote --shellquote
L's 12" record
<Ctrl-D>
'"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
$ eval eval echo '"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
L's 12" record

Ele irá citar a string em todos os shells suportados (não apenas bash).


1

Esta função:

quote () 
{ 
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

permite citar 'dentro '. Use como este:

$ quote "urxvt -fg '#111111' -bg '#111111'"
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Se a linha a citar se tornar mais complexa, como aspas duplas misturadas com aspas simples, pode ser bastante complicado fazer com que a string cite dentro de uma variável. Quando tais casos aparecerem, escreva a linha exata que você precisa citar dentro de um script (semelhante a este).

#!/bin/bash

quote ()
{
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

while read line; do
    quote "$line"
done <<-\_lines_to_quote_
urxvt -fg '#111111' -bg '#111111'
Louis Theroux's LA Stories
'single quote phrase' "double quote phrase"
_lines_to_quote_

Saída:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
'Louis Theroux'\''s LA Stories'
''\''single quote phrase'\'' "double quote phrase"'

Todas as cadeias citadas corretamente entre aspas simples.


1

Se você estiver gerando a string do shell no Python 2 ou Python 3, o seguinte pode ajudar a citar os argumentos:

#!/usr/bin/env python

from __future__ import print_function

try:  # py3
    from shlex import quote as shlex_quote
except ImportError:  # py2
    from pipes import quote as shlex_quote

s = """foo ain't "bad" so there!"""

print(s)
print(" ".join([shlex_quote(t) for t in s.split()]))

Isso produzirá:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'

1

Aqui estão meus dois centavos - no caso, se alguém quiser ser shportável, não apenas bashespecífico (a solução não é muito eficiente, porém, ao iniciar um programa externo sed):

  • coloque isso quote.sh(ou apenas quote) em algum lugar do seu PATH:
# isso funciona com entrada padrão (stdin)
quote () {
  eco -n "'";
  sed 's / \ ([' "'"'] ['"' '' * *) / '"' "'" \ 1 "'" '' '/ g';
  eco -n "'"
}

caso "$ 1" em
 -) citação ;;
 *) echo "use: cat ... | quote - # entrada de aspas simples para o shell Bourne" 2> & 1 ;;
esac

Um exemplo:

$ echo -n "Bom dia, companheiro!" | ./quote.sh -
'G' "'"' dia, companheiro! '

E, claro, isso se converte novamente:

$ echo 'G' "'"' dia, companheiro! '
Bom dia, companheiro!

Explicação: basicamente temos que incluir a entrada entre aspas 'e, em seguida, também substituir qualquer aspas simples nesse micro-monstro: '"'"'(finalize a cotação de abertura com um emparelhamento ', escape da aspas simples encontrada envolvendo-as com aspas duplas - "'"e então finalmente emitir uma nova abertura aspas simples 'ou em pseudo-notação: ' + "'" + ' == '"'"')

Uma maneira padrão de fazer isso seria usar sedo seguinte comando de substituição:

s/\(['][']*\)/'"\1"'/g 

Um pequeno problema, porém, é que, para usá-lo no shell, é necessário escapar de todos esses caracteres de aspas simples na própria expressão sed - o que leva a algo como

sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' 

(e uma boa maneira de criar esse resultado é alimentar a expressão original s/\(['][']*\)/'"\1"'/gnos scripts de Kyle Rose 'ou George V. Reilly).

Finalmente, faz sentido esperar que a entrada venha, stdinjá que passá-la por argumentos da linha de comando já pode ser um problema demais.

(Ah, e pode ser que desejemos adicionar uma pequena mensagem de ajuda para que o script não pare quando alguém o executa como se ./quote.sh --helpperguntando o que ele faz.)


0

Aqui está outra solução. Essa função usa um único argumento e o cita adequadamente usando o caractere de aspas simples, assim como a resposta votada acima explica:

single_quote() {
  local quoted="'"
  local i=0
  while [ $i -lt ${#1} ]; do
    local ch="${1:i:1}"
    if [[ "$ch" != "'" ]]; then
      quoted="$quoted$ch"
    else
      local single_quotes="'"
      local j=1
      while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; do
        single_quotes="$single_quotes'"
        ((j++))
      done
      quoted="$quoted'\"$single_quotes\"'"
      ((i+=j-1))
    fi
    ((i++))
  done
  echo "$quoted'"
}

Então, você pode usá-lo desta maneira:

single_quote "1 2 '3'"
'1 2 '"'"'3'"'"''

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.