Jak uniknąć znaku wieloznacznego / gwiazdki w bash?

136

Na przykład:

me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR

i używając \znaku ucieczki:

me$ FOO="BAR \* BAR"
me$ echo $FOO
BAR \* BAR

Oczywiście robię coś głupiego.

Jak uzyskać wynik BAR * BAR?

andyuk
źródło

Odpowiedzi:

139

Cytowanie, gdy ustawienie $FOOnie wystarczy. Musisz również zacytować odniesienie do zmiennej:

me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR
finnw
źródło
8
to jest tajemnicze, dlaczego tak jest? co się dzieje?
tofutim
Ponieważ zmienne się rozszerzają
Daniel
103

KRÓTKA ODPOWIEDŹ

Jak powiedzieli inni - zawsze powinieneś cytować zmienne, aby zapobiec dziwnemu zachowaniu. Więc użyj echo "$ foo" in zamiast po prostu echo $ foo .

DŁUGA ODPOWIEDŹ

Myślę, że ten przykład zasługuje na dalsze wyjaśnienia, ponieważ dzieje się więcej, niż mogłoby się wydawać na pierwszy rzut oka.

Widzę, gdzie pojawia się twoje zamieszanie, ponieważ po uruchomieniu pierwszego przykładu prawdopodobnie pomyślałeś, że powłoka najwyraźniej robi:

  1. Rozszerzenie parametrów
  2. Rozszerzenie nazwy pliku

A więc z pierwszego przykładu:

me$ FOO="BAR * BAR"
me$ echo $FOO

Po rozwinięciu parametrów odpowiada:

me$ echo BAR * BAR

A po rozwinięciu nazwy pliku jest równoważne:

me$ echo BAR file1 file2 file3 file4 BAR

A jeśli po prostu wpiszesz echo BAR * BARw wierszu poleceń, zobaczysz, że są one równoważne.

Więc prawdopodobnie pomyślałeś sobie „jeśli uniknę *, mogę zapobiec rozszerzaniu nazwy pliku”

A więc z drugiego przykładu:

me$ FOO="BAR \* BAR"
me$ echo $FOO

Po rozwinięciu parametrów powinno odpowiadać:

me$ echo BAR \* BAR

A po rozwinięciu nazwy pliku powinno być równoważne z:

me$ echo BAR \* BAR

A jeśli spróbujesz wpisać „echo BAR \ * BAR” bezpośrednio w wierszu poleceń, rzeczywiście wypisze to „BAR * BAR”, ponieważ rozwinięcie nazwy pliku jest blokowane przez znak ucieczki.

Dlaczego więc użycie $ foo nie działa?

To dlatego, że ma miejsce trzecie rozszerzenie - Quote Removal. Z ręcznego usuwania cytatów bash jest:

Po poprzednich rozszerzeniach wszystkie niecytowane wystąpienia znaków '\', '' 'i' "', które nie powstały w wyniku jednego z powyższych rozszerzeń, są usuwane.

Więc co się dzieje, kiedy wpiszesz polecenie bezpośrednio w linii poleceń, znak ucieczki nie jest wynikiem poprzedniego interpretacji, więc BASH usuwa go przed wysłaniem do polecenia echo, ale w drugim przykładzie znak "\ *" był wynik poprzedniej interpretacji parametru, więc NIE jest usuwany. W rezultacie echo otrzymuje "\ *" i to właśnie drukuje.

Zwróć uwagę na różnicę między pierwszym przykładem - „*” nie jest uwzględniona w znakach, które zostaną usunięte przez usunięcie cytatu.

Mam nadzieję, że to ma sens. Na koniec wniosek w tym samym - wystarczy użyć cudzysłowu. Pomyślałem tylko, że wyjaśnię, dlaczego ucieczka, która logicznie powinna działać, jeśli w grę wchodzi tylko rozszerzenie parametrów i nazwy pliku, nie działa.

Aby uzyskać pełne wyjaśnienie rozszerzeń BASH, zobacz:

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions

mithu
źródło
1
Świetna odpowiedź! Teraz nie czuję, że zadałem tak głupie pytanie. :-)
andyuk
1
Czy są jakieś narzędzia do ucieczki przed znakami specjalnymi?
Jigar Joshi
„zawsze należy cytować zmienne, aby zapobiec dziwnemu zachowaniu” - jeśli chcesz użyć ich jako ciągów znaków
Angelo
Chociaż odpowiedź z @finnw powyżej bezpośrednio odpowiada na pytanie, ta odpowiedź znacznie lepiej wyjaśnia, dlaczego tak jest, co znacznie bardziej pomaga w ekstrapolacji, jak użycie w naszych własnych scenariuszach będzie działać. To jest rodzaj odpowiedzi, której potrzebujemy, aby zobaczyć więcej.
Nicolas Coombs
54

Dodam trochę do tego starego wątku.

Zwykle używałbyś

$ echo "$FOO"

Jednak nawet z taką składnią miałem problemy. Rozważmy następujący skrypt.

#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"

Te *muszą być przekazywane do verbatim curl, ale te same problemy pojawią. Powyższy przykład nie zadziała (rozwinie się do nazw plików w bieżącym katalogu) i też nie \*. Nie możesz również cytować, $curl_optsponieważ zostanie to rozpoznane jako pojedyncza (nieprawidłowa) opcja curl.

curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

Dlatego zalecałbym użycie bashzmiennej, $GLOBIGNOREaby całkowicie zapobiec rozwijaniu nazwy pliku, jeśli zostanie zastosowana do wzorca globalnego, lub użycie set -fwbudowanej flagi.

#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"  ## no filename expansion

W odniesieniu do oryginalnego przykładu:

me$ FOO="BAR * BAR"

me$ echo $FOO
BAR file1 file2 file3 file4 BAR

me$ set -f
me$ echo $FOO
BAR * BAR

me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR
AlexandreH
źródło
4
Świetne wyjaśnienie, dzięki! Moim przypadkiem jest to SELECT * FROM etc., że to jedyny sposób, który działa.
knutole 18.07.15
1
Dziękujemy za zaprezentowanie rozwiązania set -f!
hachre
5
FOO='BAR * BAR'
echo "$FOO"
tzot
źródło
To działa, ale zmiana pojedynczych cudzysłowów w pierwszym wierszu na podwójne cudzysłowy nie jest konieczna.
finnw
1
Nie, nie jest, ale nawyk używania pojedynczych cudzysłowów jest lepszy, gdy zamierzasz wstawiać znaki specjalne powłoki i nie chcesz żadnego zastępowania.
tzot
3

Warto przyzwyczaić się do używania printfraczej niż echoz linii poleceń.

W tym przykładzie nie daje to większych korzyści, ale może być bardziej przydatne przy bardziej złożonych wynikach.

FOO="BAR * BAR"
printf %s "$FOO"
Dave Webb
źródło
Dlaczego do diabła? printf to oddzielny proces (a przynajmniej nie wbudowany bash), a użycie printf, które demonstrujesz, nie ma żadnej korzyści w porównaniu z echo.
ddaa,
2
printf jest wbudowanym bashem i powiedziałem w mojej odpowiedzi „W tym przykładzie nie daje to większych korzyści, ale może być bardziej użyteczne przy bardziej złożonych wynikach.
Dave Webb