Quali personaggi devono essere sfuggiti quando si usa Bash?


206

Esiste un elenco completo di personaggi che devono essere salvati in Bash? Può essere controllato solo con sed?

In particolare, stavo verificando se è %necessario evadere o meno. Provai

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

e ha funzionato bene, senza scappare %. Significa che %non è necessario scappare? È stato un buon modo per verificare la necessità?

E più in generale: sono gli stessi personaggi in cui fuggire shelle bash?


4
In generale, se ti interessa, lo stai facendo male. La gestione dei dati non dovrebbe mai comportare l'esecuzione attraverso il processo di analisi e valutazione utilizzato per il codice, rendendo impossibile la fuga. Questo è un parallelo molto stretto con le migliori pratiche per SQL: dove la cosa giusta è usare le variabili di bind e la cosa sbagliata è provare a "sanificare" i dati iniettati tramite sostituzioni di stringhe.
Charles Duffy,


8
@CharlesDuffy Sì, ma a volte ciò che il motore delle dichiarazioni preparate sta facendo sul back-end è solo sfuggire alle cose. SO sta "sbagliando" perché sfuggono ai commenti inviati dall'utente prima di mostrarli nel browser? No. Stanno impedendo XSS. Non preoccuparsi affatto è sbagliare.
Parthian Shot

@ParthianShot, se il motore delle istruzioni preparato non mantiene i dati completamente fuori banda dal codice, le persone che lo hanno scritto dovrebbero essere fucilate. Sì, so che il protocollo wire di MySQL è implementato in quel modo; la mia affermazione è valida.
Charles Duffy,

@CharlesDuffy E il mio punto - che a volte le tue opzioni sono di far funzionare qualcosa in sicurezza usando una toolchain che renderebbe una rabbia purista, o affondare otto volte il tempo e lo sforzo per renderla carina - anche.
Parthian Shot,

Risposte:


282

Ci sono due regole facili e sicure che funzionano non solo in shma anche bash.

1. Inserisci l'intera stringa tra virgolette singole

Funziona con tutti i caratteri tranne la singola virgoletta stessa. Per evitare la citazione singola, chiudere la citazione prima di essa, inserire la citazione singola e riaprire la citazione.

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

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

2. Scappa da ogni carattere con una barra rovesciata

Funziona con tutti i personaggi tranne Newline. Per i caratteri di nuova riga utilizzare virgolette singole o doppie. Le stringhe vuote devono ancora essere gestite - sostituire con""

\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. Versione più leggibile di 2

C'è un set di personaggi facile e sicuro, come [a-zA-Z0-9,._+:@%/-], che può essere lasciato senza caratteri di escape per renderlo più leggibile

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/$/"/'.


Si noti che in un programma sed, non si può sapere se l'ultima riga di input termina con un byte newline (tranne quando è vuoto). Ecco perché entrambi i comandi sed sopra riportati suppongono che non lo faccia. È possibile aggiungere manualmente una nuova riga citata.

Si noti che le variabili shell sono definite solo per il testo in senso POSIX. L'elaborazione dei dati binari non è definita. Per le implementazioni che contano, il binario funziona ad eccezione dei byte NUL (poiché le variabili sono implementate con stringhe C e intendono essere utilizzate come stringhe C, in particolare argomenti del programma), ma è necessario passare a una locale "binaria" come latin1 .


(È possibile convalidare facilmente le regole leggendo le specifiche POSIX per sh. Per bash, consultare il manuale di riferimento collegato da @AustinPhillips)


Nota: una buona variante del numero 1 può essere vista qui: github.com/scop/bash-completion/blob/… . Non richiede l'esecuzione sed, ma richiede bash.
jwd

4
Nota per chiunque (come me!) Che fa fatica a far funzionare questi .... sembra che il sapore di sed che si ottiene su OSX non esegua correttamente questi comandi sed. Funzionano bene su Linux però!
Dalelane,

@dalelane: non è possibile eseguire il test qui. Modifica quando hai una versione che funziona su entrambi.
Jo So,

Sembra che ti sei perso se la stringa inizia con un '-' (meno), o si applica solo ai nomi dei file? - in quest'ultimo caso è necessario un './' davanti.
Slashmais,

Non sono sicuro di cosa intendi. Con quei comandi sed la stringa di input viene presa dallo stdin.
Jo So,

59

formato che può essere riutilizzato come input della shell

Esiste una direttiva di formato speciale printf ( %q) creata per questo tipo di richiesta:

printf [-v var] format [argomenti]

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

Alcuni esempi:

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

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

Questo potrebbe essere usato anche attraverso le variabili:

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

Controllo rapido con tutti (128) byte ASCII:

Si noti che tutti i byte da 128 a 255 devono essere salvati.

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

Questo deve rendere qualcosa di simile:

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      

Laddove il primo campo è un valore esadecimale di byte, il secondo contiene Ese il carattere deve essere sfuggito e il terzo campo mostra la presentazione sfuggita del carattere.

Perché ,?

Potresti vedere alcuni personaggi che non hanno sempre bisogno di essere sfuggiti, come ,, }e {.

Quindi non sempre ma a volte :

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

o

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

ma attenzione:

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 

Questo ha il problema che, chiamando pritnf tramite bash / sh, la stringa deve prima essere evasa dalla shell per bash / sh
ThorSummoner

1
@ThorSummoner, non se passi la stringa come argomento letterale alla shell da una lingua diversa (dove presumibilmente sai già come citare). In Python: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()ti fornirà una versione di arbitrary_string.
Charles Duffy,

1
FYI bash è %qstato rotto per molto tempo - Se la mia mente mi serve bene, un errore è stato risolto (ma potrebbe ancora essere rotto) nel 2013 dopo essere stato rotto per ~ 10 anni. Quindi non fare affidamento su di esso.
Jo So,

@CharlesDuffy Naturalmente, una volta che ti trovi in ​​Python land, shlex.quote()(> = 3.3, pipes.quote()- non documentato - per le versioni precedenti) farà anche il lavoro e produrrà una versione più leggibile dall'uomo (aggiungendo virgolette e scappando, se necessario) della maggior parte delle stringhe, senza la necessità di generare un guscio.
Thomas Perl,

1
Grazie per aggiungere note speciali su ,. Sono stato sorpreso di apprendere che Bash integrato printf -- %q ','\,, ma /usr/bin/printf -- %q ',',(non fuggito). Lo stesso vale per gli altri caratteri: {, |, }, ~.
kevinarpe,

34

Per salvare qualcun altro dal dover RTFM ... in bash :

Racchiudere caratteri tra virgolette conserva il valore letterale di tutti i caratteri all'interno delle virgolette, ad eccezione di $, `, \e, quando l'espansione della cronologia è abilitata, !.

... quindi se sfuggi a quelli (e alla citazione stessa, ovviamente) probabilmente stai bene.

Se prendi un approccio più conservativo "in caso di dubbio, sfuggilo", dovrebbe essere possibile evitare di ottenere caratteri con significato speciale non sfuggendo ai caratteri identificativi (ad esempio lettere ASCII, numeri o "_"). È molto improbabile che questi mai (cioè in qualche strana shell POSIX) abbiano un significato speciale e quindi debbano essere evitati.



Questa è una risposta breve, dolce e per lo più corretta (+1 per quello) ma forse è ancora meglio usare le virgolette singole - vedi la mia risposta più lunga.
Jo So,

26

Usando la print '%q' tecnica , possiamo eseguire un ciclo per scoprire quali personaggi sono speciali:

#!/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à questo risultato:

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

Alcuni dei risultati, ad esempio, ,sembrano un po 'sospetti. Sarebbe interessante ottenere gli input di @ CharlesDuffy su questo.


2
Puoi leggere la risposta per ,sembrare un po 'sospetto nell'ultimo paragrafo della mia risposta
F. Hauri,

2
Ricorda che %qnon sai dove all'interno della shell stai pianificando di usare il personaggio, quindi sfuggirà a tutti i personaggi che possono avere un significato speciale in ogni possibile contesto della shell. ,di per sé non ha un significato speciale per la shell, ma come ha sottolineato @ F.Hauri nella sua risposta, ha un significato speciale {...}nell'espansione del tutore: gnu.org/savannah-checkouts/gnu/bash/manual/… Questo è come! che richiede anche solo l'espansione in situazioni specifiche, non in generale: echo Hello World!funziona bene, ma echo test!testfallirà.
Mecki,

18

I personaggi che necessitano di escape sono diversi nella shell Bourne o POSIX rispetto a Bash. Generalmente (molto) Bash è un superset di quelle conchiglie, quindi qualsiasi cosa in cui fuggire shelldovrebbe essere evasa in Bash.

Una buona regola generale sarebbe "in caso di dubbio, scappare". Ma sfuggire ad alcuni personaggi dà loro un significato speciale, come \n. Questi sono elencati nelle man bashpagine sotto Quotinge echo.

A parte questo, sfuggire a qualsiasi personaggio che non sia alfanumerico, è più sicuro. Non conosco un unico elenco definitivo.

Le pagine man le elencano tutte da qualche parte, ma non in un unico posto. Impara la lingua, questo è il modo per essere sicuri.

Uno che mi ha catturato è !. Questo è un personaggio speciale (espansione della storia) in Bash (e csh) ma non nella shell Korn. Anche echo "Hello world!"dà problemi. L'uso delle virgolette singole, come al solito, rimuove il significato speciale.


1
Mi piace soprattutto il consiglio " Una bella regola generale" sarebbe "in caso di dubbio, sfuggila" . Ho ancora il dubbio se il check-in sedsia abbastanza buono per vedere se deve essere evitato. Grazie per la tua risposta!
fedorqui "SO smettere di danneggiare" il

2
@fedorqui: il controllo con sednon è necessario, è possibile verificare con quasi tutto. sednon è il problema, lo bashè. All'interno delle virgolette singole non ci sono caratteri speciali (tranne le virgolette singole), non puoi nemmeno sfuggire ai caratteri lì. Un sedcomando dovrebbe di solito essere racchiuso tra virgolette singole perché i metacaratteri RE hanno troppe sovrapposizioni con i metacaratteri della shell per essere sicuri. L'eccezione è quando si incorporano variabili shell, che deve essere fatto con attenzione.
cdarke,

5
Verificare con echo. Se esci da ciò che hai inserito, non è necessario scappare. :)
Mark Reed,

6

Presumo che tu stia parlando di stringhe bash. Esistono diversi tipi di stringhe che presentano una serie diversa di requisiti per la escape. per esempio. Le stringhe a virgolette singole sono diverse dalle stringhe a virgolette doppie.

Il miglior riferimento è la sezione Citando del manuale di bash.

Spiega quali personaggi devono fuggire. È possibile che alcuni caratteri debbano essere sottoposti a escape in base alle opzioni abilitate come l'espansione della cronologia.


3
Quindi conferma che fuggire è una tale giungla senza una soluzione facile, dovrà controllare ogni caso. Grazie!
fedorqui "SO smettere di danneggiare" il

@fedorqui Come in ogni lingua, c'è una serie di regole da seguire. Per l'escaping della stringa bash, l'insieme di regole è piuttosto piccolo come descritto nel manuale. La stringa più semplice da usare sono le virgolette singole poiché non è necessario scappare. Tuttavia, non è possibile includere una singola virgoletta in una singola stringa tra virgolette.
Austin Phillips,

@fedorqui. E ' non è una giungla. La fuga è abbastanza fattibile. Vedi il mio nuovo post.
Jo So,

@fedorqui Non puoi usare una virgoletta singola all'interno di una stringa a virgoletta singola ma puoi "sfuggirla" con qualcosa del tipo: 'text' "'"' more text '
CR.

4

Ho notato che bash sfugge automaticamente ad alcuni personaggi quando si usa il completamento automatico.

Ad esempio, se si dispone di una directory denominata dir:A, bash si completerà automaticamentedir\:A

Usando questo, ho eseguito alcuni esperimenti usando i caratteri della tabella ASCII e ho derivato i seguenti elenchi:

Personaggi che bash scappano al completamento automatico : (include lo spazio)

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

I personaggi che colpiscono non sfuggono :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Ho escluso /, in quanto non può essere utilizzato nei nomi di directory)


2
Se volessi davvero avere un elenco completo, ti suggerirei di guardare quali personaggi printf %qfanno e non modificano se passati come argomento - idealmente, passando attraverso l'intero set di caratteri.
Charles Duffy,

Ci sono casi in cui anche con la stringa dell'apostrofo, potresti voler sfuggire a lettere e numeri per produrre caratteri speciali. Ad esempio: tr '\ n' '\ t' che traduce i caratteri di nuova riga in caratteri di tabulazione.
Dick Guertin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.