Quais caracteres precisam ser escapados ao usar o Bash?

206

Existe alguma lista abrangente de caracteres que precisam ser escapados no Bash? Pode ser verificado apenas com sed?

Em particular, eu estava verificando se é %necessário escapar ou não. eu tentei

echo "h%h" | sed 's/%/i/g'

e funcionou bem, sem escapar %. Isso significa %que não precisa ser escapado? Essa foi uma boa maneira de verificar a necessidade?

E mais geral: eles são os mesmos personagens para escapar shelle bash?

fedorqui 'Então pare de prejudicar'
fonte
4
Em geral, se você se importa, está fazendo errado. O manuseio de dados nunca deve envolver executá-lo através do processo de análise e avaliação usado para o código, tornando-se discutível. Esse é um paralelo muito próximo às práticas recomendadas para SQL - onde a coisa certa é usar variáveis ​​de ligação e a coisa errada é tentar "higienizar" os dados injetados por meio de substituições de seqüência de caracteres.
Charles Duffy
Relacionado com stackoverflow.com/questions/2854655/…
skywinder
8
@CharlesDuffy Sim, mas às vezes o que o mecanismo de instruções preparadas está fazendo no back-end é apenas escapar das coisas. O SO está "fazendo errado" porque eles escapam dos comentários enviados pelo usuário antes de exibi-los no navegador? Não. Eles estão impedindo o XSS. Não se importar é fazer errado.
Tiro parta
@ParthianShot, se o mecanismo de instruções preparado não mantiver os dados completamente fora de banda do código, as pessoas que os escreveram devem ser atingidas. Sim, eu sei que o protocolo de conexão do MySQL é implementado dessa maneira; minha afirmação permanece.
Charles Duffy
@CharlesDuffy E o meu argumento - que às vezes suas opções são fazer algo funcionar com segurança usando uma cadeia de ferramentas que causaria um arrepio purista ou diminuir oito vezes o tempo e o esforço para torná-la bonita - também permanece.
Tiro parta

Respostas:

282

Existem duas regras fáceis e seguras que funcionam não apenas shcomo também bash.

1. Coloque a string inteira entre aspas simples

Isso funciona para todos os caracteres, exceto as aspas simples. Para escapar da aspas simples, feche as aspas antes dela, insira a aspas simples e reabra a aspas.

'I'\''m a s@fe $tring which ends in newline
'

comando sed: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. Escape de todos os caracteres com uma barra invertida

Isso funciona para todos os caracteres, exceto nova linha. Para caracteres de nova linha, use aspas simples ou duplas. As strings vazias ainda devem ser manuseadas - substitua por""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

comando sed: sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. Versão mais legível do 2

Há um conjunto fácil de caracteres seguro, como [a-zA-Z0-9,._+:@%/-], que pode ser deixado sem escape para mantê-lo mais legível

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

comando sed: LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.


Observe que em um programa sed, não se pode saber se a última linha de entrada termina com um byte de nova linha (exceto quando está vazio). É por isso que ambos os comandos sed acima assumem que não. Você pode adicionar uma nova linha entre aspas manualmente.

Observe que variáveis ​​de shell são definidas apenas para texto no sentido POSIX. O processamento de dados binários não está definido. Para as implementações importantes, o binário funciona com exceção de bytes NUL (porque as variáveis ​​são implementadas com cadeias C e devem ser usadas como cadeias C, ou seja, argumentos de programa), mas você deve alternar para um código de idioma "binário", como latin1 .


(Você pode validar facilmente as regras lendo a especificação POSIX para sh. Para o bash, consulte o manual de referência vinculado por @AustinPhillips)

Jo So
fonte
Nota: uma boa variação do número 1 pode ser vista aqui: github.com/scop/bash-completion/blob/… . Não requer execução sed, mas exige bash.
jwd
4
Nota para qualquer outra pessoa (como eu!) Que se esforça para fazê-las funcionar ... parece que o sabor do sed que você recebe no OSX não executa esses comandos sed corretamente. Eles funcionam bem no Linux!
precisa saber é o seguinte
@dalelane: Não é possível testar aqui. Edite quando você tiver uma versão que funcione nos dois.
Jo Então,
Parece que você perdeu a sequência de caracteres deve começar com '-' (menos) ou isso se aplica apenas aos nomes de arquivos? - neste último caso, precisa de um './' na frente.
precisa saber é o seguinte
Não sei bem o que você quer dizer. Com esses comandos sed, a string de entrada é retirada do stdin.
Jo
59

formato que pode ser reutilizado como entrada do shell

Existe uma diretiva de formato especial printf ( %q) criada para este tipo de solicitação:

formato printf [-v var] [argumentos]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

Algumas amostras:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

Isso também pode ser usado através de variáveis:

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

Verificação rápida com todos os (128) bytes ascii:

Observe que todos os bytes de 128 a 255 precisam ser escapados.

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

Isso deve render algo como:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

Onde o primeiro campo é o valor hexa do byte, o segundo contém Ese o caractere precisa ser escapado e o terceiro campo mostra a apresentação do caractere escapado.

Por que ,?

Você pode ver alguns caracteres que nem sempre precisam ser escapados, como ,, }e {.

Então nem sempre, mas em algum momento :

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

ou

echo test { 1, 2, 3 }
test { 1, 2, 3 }

mas cuidado:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 
F. Hauri
fonte
Isto tem o problema que, chamando pritnf via bash / sh, a string deve ser primeiro shell escapou para a festança / sh
ThorSummoner
1
@ThorSummoner, não se você passar a string como argumento literal para o shell de um idioma diferente (onde você provavelmente já sabe citar). No Python: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()você fornecerá uma versão do shell com aspas apropriadas arbitrary_string.
Charles Duffy
1
FYI bash's %qfoi quebrado por um longo tempo - Se minha mente me serve bem, um erro foi corrigido (mas ainda pode ser quebrado) em 2013, depois de ser quebrado por ~ 10 anos. Portanto, não confie nisso.
Jo #
@CharlesDuffy Naturalmente, quando você estiver em Python, shlex.quote()(> = 3.3, pipes.quote()- não documentado - para versões mais antigas) também fará o trabalho e produzirá uma versão mais legível por humanos (adicionando aspas e escapando, conforme necessário) da maioria das strings, sem a necessidade de gerar uma concha.
Thomas Perl
1
Obrigado por adicionar notas especiais sobre ,. Fiquei surpreso ao saber que o Bash embutido printf -- %q ','\,, mas /usr/bin/printf -- %q ',',(não escapou). Mesmo para outros caracteres: {, |, }, ~.
kevinarpe
34

Para evitar que outra pessoa precise RTFM ... no bash :

Anexando caracteres entre aspas duplas preserva o valor literal de todos os caracteres entre as aspas, com exceção de $, `, \, e, quando a expansão história está habilitado, !.

... então, se você escapar desses (e da própria citação, é claro), provavelmente estará bem.

Se você adotar uma abordagem mais conservadora 'em caso de dúvida, escape', deve ser possível evitar obter caracteres com significado especial, não escapando aos caracteres identificadores (ou seja, letras ASCII, números ou '_'). É muito improvável que eles tenham um significado especial e, portanto, precisem ser escapados.

Mateus
fonte
1
Aqui está o manual citado acima: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk
Esta é uma resposta curta, agradável e quase sempre correta (+1 para isso), mas talvez seja ainda melhor usar aspas simples - veja minha resposta mais longa.
Jo Então,
26

Usando a print '%q' técnica , podemos executar um loop para descobrir quais caracteres são especiais:

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

Dá esta saída:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

Alguns dos resultados, como ,parecem um pouco suspeitos. Seria interessante obter as contribuições de @ CharlesDuffy sobre isso.

codeforester
fonte
2
Você pode ler a resposta para ,parecer um pouco desconfiado no último parágrafo da minha resposta
F. Hauri
2
Lembre-se de que %qnão sabe em que parte do shell você planeja usar o personagem; portanto, ele escapará de todos os caracteres que podem ter um significado especial em qualquer contexto possível do shell. ,ela mesma não tem significado especial para ela, mas como @ F.Hauri apontou em sua resposta, ela tem um significado especial na {...}expansão de chaves: gnu.org/savannah-checkouts/gnu/bash/manual/… É assim! o que também requer expansão apenas em situações específicas, não em geral: echo Hello World!funciona muito bem, mas echo test!testfalha.
Mecki
18

Os caracteres que precisam ser escapados são diferentes no shell Bourne ou POSIX e no Bash. Geralmente (muito) o Bash é um superconjunto dessas conchas; portanto, tudo o que você escapar shelldeve escapar no Bash.

Uma boa regra geral seria "em caso de dúvida, escape". Mas escapar de alguns personagens lhes dá um significado especial, como \n. Eles estão listados nas man bashpáginas em Quotinge echo.

Fora isso, escape de qualquer caractere que não seja alfanumérico; é mais seguro. Não conheço uma única lista definitiva.

As páginas de manual listam todas elas em algum lugar, mas não em um só lugar. Aprenda o idioma, esse é o caminho para ter certeza.

Um que me chamou a atenção é !. Este é um caractere especial (expansão do histórico) no Bash (e csh), mas não no shell Korn. Até echo "Hello world!"dá problemas. Usar aspas simples, como sempre, remove o significado especial.

cdarke
fonte
1
Eu gosto especialmente do bom conselho geral seria "em caso de dúvida, escape" . Ainda temos a dúvida de que verificar com sedé bom o suficiente para ver se precisa escapar. Obrigado pela sua resposta!
fedorqui 'Então, pare de prejudicar'
2
@fedorqui: sedNão é necessário verificar com, você pode verificar com quase tudo. sednão é o problema, bashé. Dentro de aspas simples, não há caracteres especiais (exceto aspas simples), você não pode nem mesmo escapar caracteres lá. Um sedcomando geralmente deve estar entre aspas simples, porque os metacaracteres do RE têm muitas sobreposições com os metacaracteres do shell para serem seguros. A exceção é ao incorporar variáveis ​​de shell, o que deve ser feito com cuidado.
cdarke
5
Verifique com echo. Se você conseguir o que colocar, ele não precisará ser escapado. :)
Mark Reed
6

Presumo que você esteja falando sobre strings do bash. Existem diferentes tipos de strings que possuem um conjunto diferente de requisitos para escapar. por exemplo. As strings de aspas simples são diferentes das strings de aspas duplas.

A melhor referência é a citação seção do manual bash.

Explica quais caracteres precisam ser escapados. Observe que alguns caracteres podem precisar ser escapados, dependendo de quais opções estão ativadas, como a expansão do histórico.

Austin Phillips
fonte
3
Por isso, confirma que escapar é uma selva sem uma solução fácil, terá que verificar cada caso. Obrigado!
fedorqui 'SO stop prejudying'
@fedorqui Como em qualquer idioma, há um conjunto de regras a serem seguidas. Para escape de string bash, o conjunto de regras é bem pequeno, conforme descrito no manual. A string mais fácil de usar são aspas simples, pois nada precisa escapar. No entanto, não há como incluir uma única citação em uma única seqüência de caracteres entre aspas.
Austin Phillips
@fedorqui. É não uma selva. Escapar é bastante factível. Veja meu novo post.
Jo #
@fedorqui Você não pode usar uma única citação dentro de uma string de aspas simples, mas pode "escapá-la" com algo como: 'text' "'"' more text '
CR.
4

Notei que o bash escapa automaticamente alguns caracteres ao usar o preenchimento automático.

Por exemplo, se você tiver um diretório chamado dir:A, o bash será concluído automaticamente paradir\:A

Usando isso, realizei algumas experiências usando caracteres da tabela ASCII e derivou as seguintes listas:

Caracteres que o bash escapa ao concluir automaticamente : (inclui espaço)

 !"$&'()*,:;<=>?@[\]^`{|}

Personagens que o bash não escapa :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Excluí /, pois não pode ser usado em nomes de diretório)

yuri
fonte
2
Se você realmente deseja ter uma lista abrangente, sugiro olhar quais caracteres printf %qsão modificados e não modificados se passados ​​como argumento - idealmente, percorrendo todo o conjunto de caracteres.
Charles Duffy
Há casos em que, mesmo com a sequência do apóstrofo, você pode escapar de letras e números para produzir caracteres especiais. Por exemplo: tr '\ n' '\ t' que traduz caracteres de nova linha em caracteres de tabulação.
Dick Guertin