Które postacie należy uciec podczas korzystania z Bash?

206

Czy jest jakaś obszerna lista postaci, które należy uciec w Bash? Czy można to sprawdzić tylko za pomocą sed?

W szczególności sprawdzałem, czy %trzeba uciec, czy nie. próbowałem

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

i działało dobrze, bez ucieczki %. Czy to znaczy, %że nie trzeba uciekać? Czy to był dobry sposób na sprawdzenie konieczności?

I bardziej ogólnie: czy są to te same postacie, do których można uciec shelli bash?

fedorqui „SO przestań szkodzić”
źródło
4
Ogólnie, jeśli cię to obchodzi, robisz to źle. Przetwarzanie danych nigdy nie powinno wiązać się z uruchomieniem go przez proces analizowania i oceny używany w kodzie, dzięki czemu można uniknąć ucieczki. Jest to bardzo zbliżone do najlepszych praktyk dotyczących SQL - gdzie Właściwą Rzeczą jest użycie zmiennych powiązania, a Niewłaściwą rzeczą jest próba „oczyszczenia” danych wstrzykniętych przez podstawienia łańcucha.
Charles Duffy
8
@CharlesDuffy Tak, ale czasami to, co silnik instrukcji przygotowuje na backendie, po prostu ucieka. Czy SO „robi to źle”, ponieważ unikają komentarzy przesłanych przez użytkowników przed wyświetleniem ich w przeglądarce? Nie. Zapobiegają XSS. W ogóle nie dbanie o to oznacza, że ​​robi to źle.
Parthian Shot
@ParthianShot, jeśli przygotowany silnik instrukcji nie utrzymuje danych poza pasmem kodu, ludzie, którzy je napisali, powinni zostać zastrzeleni. Tak, wiem, że protokół przewodowy MySQL jest zaimplementowany w ten sposób; moje oświadczenie jest ważne.
Charles Duffy,
@CharlesDuffy I mój punkt - że czasami masz opcje, aby coś działało bezpiecznie przy użyciu łańcucha narzędzi, który spowodowałby purystyczne kulenie się, lub zatonął osiem razy więcej czasu i wysiłku, aby uczynić to ładniejszym - również nadal stoi.
Parthian Shot

Odpowiedzi:

282

Istnieją dwie proste i bezpieczne zasady, które działają nie tylko w, shale także bash.

1. Umieść cały ciąg w pojedynczych cudzysłowach

Działa to dla wszystkich znaków, z wyjątkiem pojedynczego cudzysłowu. Aby uniknąć pojedynczego cytatu, zamknij cytat przed nim, wstaw pojedynczy cytat i ponownie otwórz cytat.

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

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

2. Unikaj każdego znaku odwrotnym ukośnikiem

Działa to dla wszystkich znaków z wyjątkiem znaku nowej linii. W przypadku znaków nowej linii użyj pojedynczych lub podwójnych cudzysłowów. Puste ciągi muszą być nadal obsługiwane - zamień na""

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

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

2b. Bardziej czytelna wersja 2

Istnieje łatwy bezpieczny zestaw znaków, takich jak [a-zA-Z0-9,._+:@%/-], które można pozostawić bez ukrycia, aby był bardziej czytelny

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

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


Zauważ, że w programie sed nie wiadomo, czy ostatni wiersz danych wejściowych kończy się bajtem nowej linii (chyba że jest pusty). Dlatego oba powyższe polecenia sed zakładają, że tak nie jest. Możesz dodać cytowany znak nowej linii ręcznie.

Zauważ, że zmienne powłoki są zdefiniowane tylko dla tekstu w sensie POSIX. Przetwarzanie danych binarnych nie jest zdefiniowane. W przypadku implementacji, które mają znaczenie, plik binarny działa z wyjątkiem bajtów NUL (ponieważ zmienne są implementowane za pomocą ciągów C i przeznaczone do użycia jako ciągi C, a mianowicie argumenty programu), ale należy przełączyć się na ustawienia „binarne”, takie jak latin1 .


(Możesz łatwo zweryfikować reguły, czytając specyfikację POSIX dla sh. W przypadku bash sprawdź podręcznik referencyjny połączony przez @AustinPhillips)

Jo So
źródło
Uwaga: dobrą odmianę nr 1 można zobaczyć tutaj: github.com/scop/bash-completion/blob/… . Nie wymaga uruchamiania sed, ale wymaga bash.
jwd
4
Uwaga dla każdego innego (takiego jak ja!), Który stara się, aby te działały .... wygląda na to, że smak sed otrzymany w OSX nie uruchamia poprawnie tych komend. Jednak działają dobrze w systemie Linux!
dalelane
@dalelane: Nie można przetestować tutaj. Edytuj, gdy masz wersję, która działa na obu.
Jo So
Wygląda na to, że nie zauważyłeś, że ciąg powinien zaczynać się od „-” (minus), czy też dotyczy to tylko nazw plików? - w tym drugim przypadku potrzebujesz z przodu „./”.
slashmais
Nie jestem pewny co masz na myśli. Za pomocą tych komend sed łańcuch wejściowy jest pobierany ze standardowego wejścia.
Jo So
59

format, który można ponownie wykorzystać jako dane wejściowe powłoki

Dla tego rodzaju żądań stworzono specjalną printf dyrektywę formatu ( %q):

printf [-v var] format [argumenty]

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

Niektóre próbki:

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

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

Można to również wykorzystać poprzez zmienne:

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

Szybkie sprawdzenie wszystkich (128) bajtów ascii:

Zauważ, że wszystkie bajty od 128 do 255 muszą być poprzedzone znakami ucieczki.

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

To musi renderować coś takiego:

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      

Tam, gdzie pierwsze pole jest wartością szesnastkową bajtu, drugie zawiera, Ejeśli znak musi być poprzedzony znakiem ucieczki, a trzecie pole pokazuje znak zmiany znaczenia.

Dlaczego ,?

Można zobaczyć kilka znaków, które nie zawsze muszą być uciekł, jak ,, }i {.

Więc nie zawsze, ale kiedyś :

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

lub

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

ale dbaj:

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
źródło
To ma problemu, nazywając pritnf pośrednictwem bash / sh, łańcuch musi być najpierw uciekł do powłoki bash / sh
ThorSummoner
1
@ThorSummoner, nie jeśli przekażesz ciąg jako dosłowny argument do powłoki z innego języka (gdzie prawdopodobnie już umiesz cytować). W Pythonie: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()otrzymasz poprawnie cytowaną wersję powłoki arbitrary_string.
Charles Duffy,
1
FYI bash %qbył zepsuty przez długi czas - Jeśli mój umysł dobrze mi służy, błąd został naprawiony (ale nadal może być zepsuty) w 2013 roku po tym, jak został zepsuty przez ~ 10 lat. Więc nie polegaj na tym.
Jo So
@CharlesDuffy Oczywiście, gdy jesteś w krainie Python, shlex.quote()(> = 3.3, pipes.quote()- nieudokumentowane - dla starszych wersji) również wykona zadanie i stworzy wersję bardziej czytelną dla ludzi (w razie potrzeby dodając cudzysłowy i znaki specjalne), bez potrzeby odradzania powłoki.
Thomas Perl
1
Dziękujemy za dodanie specjalnych uwag na temat ,. Byłem zaskoczony, gdy dowiedziałem się, że wbudowany Bash printf -- %q ','daje \,, ale /usr/bin/printf -- %q ','daje ,(bez ucieczki). Takie same dla innych znaków: {, |, }, ~.
kevinarpe
34

Aby uratować kogoś innego przed koniecznością korzystania z RTFM ... w bash :

Załączając znaków w cudzysłowy chroni dosłowne wartości wszystkich znaków w cudzysłowie, z wyjątkiem $, `, \i, gdy ekspansja historia jest włączona !.

... więc jeśli je unikniesz (i oczywiście sam cytat), prawdopodobnie nic ci nie jest.

Jeśli zastosujesz bardziej konserwatywne podejście „w razie wątpliwości, unikaj go”, powinno być możliwe uniknięcie uzyskiwania znaków o specjalnym znaczeniu, nie unikając znaków identyfikacyjnych (tj. Liter ASCII, cyfr lub „_”). Jest bardzo mało prawdopodobne, aby kiedykolwiek (tj. W jakiejś dziwnej powłoce POSIX-owej) miał specjalne znaczenie i dlatego trzeba go uciec.

Mateusz
źródło
1
oto instrukcja cytowana powyżej: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk
To krótka, słodka i najczęściej poprawna odpowiedź (+1 za to), ale może nawet lepiej jest używać pojedynczych cudzysłowów - patrz moja dłuższa odpowiedź.
Jo So
26

Korzystając z tej print '%q' techniki , możemy uruchomić pętlę, aby dowiedzieć się, które znaki są wyjątkowe:

#!/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

Daje to wynik:

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

Niektóre wyniki, na przykład ,wyglądają trochę podejrzanie. Byłoby interesujące uzyskać wkład @ CharlesDuffy na ten temat.

codeforester
źródło
2
Możesz przeczytać odpowiedź, aby ,wyglądać trochę podejrzanie w ostatnim akapicie mojej odpowiedzi
F. Hauri
2
Pamiętaj, że %qnie wie, gdzie w powłoce planujesz użyć znaku, więc ucieknie on od wszystkich znaków, które mogą mieć specjalne znaczenie w każdym możliwym kontekście powłoki. ,samo w sobie nie ma specjalnego znaczenia dla jej powłoki, ale jak zauważył @ F.Hauri w swojej odpowiedzi, ma specjalne znaczenie w {...}rozszerzaniu nawiasów klamrowych: gnu.org/savannah-checkouts/gnu/bash/manual/… To jest jak! co również wymaga rozszerzenia w określonych sytuacjach, a nie ogólnie: echo Hello World!działa dobrze, ale echo test!testzawiedzie.
Mecki
18

Znaki wymagające ucieczki są inne w powłoce Bourne'a lub POSIX niż Bash. Ogólnie (bardzo) Bash jest nadzbiorem tych pocisków, więc wszystko, w czym uciekasz, shellpowinno być ucieczką w Bash.

Dobrą ogólną zasadą byłoby „jeśli masz wątpliwości, unikaj tego”. Ale ucieczka przed niektórymi postaciami nadaje im specjalne znaczenie \n. Są one wymienione na man bashstronach pod Quotingi echo.

Poza tym, unikaj znaków, które nie są alfanumeryczne, jest to bezpieczniejsze. Nie znam jednej ostatecznej listy.

Strony podręcznika wymieniają je wszystkie gdzieś, ale nie w jednym miejscu. Naucz się języka, to jest sposób, aby być pewnym.

Tym, który mnie złapał, jest !. Jest to znak specjalny (rozszerzenie historii) w Bash (i csh), ale nie w powłoce Korna. echo "Hello world!"Daje nawet problemy. Używanie pojedynczych cudzysłowów, jak zwykle, usuwa specjalne znaczenie.

cdarke
źródło
1
Szczególnie podoba mi się, że miłą ogólną zasadą jest rada „w razie wątpliwości, unikaj tego” . Wciąż masz wątpliwości, czy sprawdzanie za pomocą sedwystarczającej ilości informacji pozwala sprawdzić, czy należy uciec. Dziękuję za odpowiedź!
fedorqui „SO przestań szkodzić”
2
@fedorqui: Sprawdzanie za pomocą sednie jest konieczne, możesz sprawdzić za pomocą prawie wszystkiego. sednie jest problemem, bashjest. Wewnątrz pojedynczych cudzysłowów nie ma znaków specjalnych (oprócz pojedynczych cudzysłowów), nie można nawet uciec przed nimi. sedPolecenie powinno być zwykle wewnątrz pojedynczych cudzysłowów bo metaznakami RE mają zbyt wiele interferencji z metaznaki powłoki pewności. Wyjątkiem jest osadzanie zmiennych powłoki, co należy zrobić ostrożnie.
cdarke
5
Sprawdź za pomocą echo. Jeśli wydostaniesz się z tego, co wkładasz, nie musisz uciekać. :)
Mark Reed,
6

Zakładam, że mówisz o ciągach bash. Istnieją różne typy ciągów, które mają inny zestaw wymagań dotyczących ucieczki. na przykład. Ciągi pojedynczych cudzysłowów różnią się od ciągów podwójnych cudzysłowów.

Najlepszym źródłem informacji jest sekcja Cytowanie w podręczniku bash.

Wyjaśnia, które postacie potrzebują ucieczki. Pamiętaj, że niektóre znaki mogą wymagać ucieczki w zależności od włączonych opcji, takich jak rozszerzanie historii.

Austin Phillips
źródło
3
Potwierdza więc, że ucieczka jest taką dżunglą bez łatwego rozwiązania, trzeba będzie sprawdzić każdy przypadek. Dzięki!
fedorqui „SO przestań szkodzić”
@fedorqui Podobnie jak w przypadku każdego języka, istnieje zbiór zasad, których należy przestrzegać. W przypadku ucieczki ciągu bash zestaw reguł jest dość mały, jak opisano w instrukcji. Najłatwiejszym w użyciu ciągiem są pojedyncze cudzysłowy, ponieważ nic nie wymaga ucieczki. Nie ma jednak sposobu na zawarcie pojedynczego cudzysłowu w ciągu pojedynczego cudzysłowu.
Austin Phillips
@fedorqui. To nie jest dżungla. Ucieczka jest całkiem wykonalna. Zobacz mój nowy post.
Jo So
@fedorqui Nie można użyć pojedynczego cudzysłowu w ciągu pojedynczego cudzysłowu, ale można go „uciec” za pomocą czegoś takiego jak: „tekst” „” „„ więcej tekstu ”
CR.
4

Zauważyłem, że bash automatycznie ucieka niektórym postaciom podczas korzystania z autouzupełniania.

Na przykład, jeśli masz katalog o nazwie dir:A, bash automatycznie się uzupełnidir\:A

Korzystając z tego, przeprowadziłem kilka eksperymentów z wykorzystaniem znaków z tabeli ASCII i wyprowadziłem następujące listy:

Znaki, które bash ucieka przy autouzupełnianiu : (zawiera spację)

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

Znaki, które bash nie ucieka :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Wykluczyłem /, ponieważ nie można go używać w nazwach katalogów)

Jurij
źródło
2
Jeśli naprawdę chcesz mieć wyczerpującą listę, sugeruję sprawdzenie, które postacie printf %qzmieniają się i nie modyfikują, jeśli zostaną przekazane jako argument - najlepiej przejrzyj cały zestaw znaków.
Charles Duffy
Są przypadki, w których nawet przy łańcuchu apostrofów możesz uciec od liter i cyfr, aby uzyskać znaki specjalne. Na przykład: tr '\ n' '\ t ”, który tłumaczy znaki nowej linii na znaki tabulacji.
Dick Guertin,