Kiedy zawijać cudzysłowy wokół zmiennej powłoki?

184

Czy ktoś mógłby mi powiedzieć, czy powinienem zawijać cudzysłowy wokół zmiennych w skrypcie powłoki?

Na przykład, czy następująca poprawność:

xdg-open $URL 
[ $? -eq 2 ]

lub

xdg-open "$URL"
[ "$?" -eq "2" ]

A jeśli tak, to dlaczego?

Cristian
źródło
To pytanie dostaje wiele duplikatów, z których wiele nie dotyczy zmiennych, więc zwróciłem się do „wartości” zamiast do „zmiennej”. Mam nadzieję, że dzięki temu więcej osób znajdzie ten temat.
tripleee
1
@codeforester Co słychać w przypadku przywróconej edycji?
tripleee

Odpowiedzi:

131

Ogólna zasada: podaj ją, jeśli może być pusta lub zawierać spacje (lub dowolne białe znaki) lub znaki specjalne (symbole wieloznaczne). Brak cytowania ciągów ze spacjami często prowadzi do tego, że powłoka dzieli jeden argument na wiele.

$?nie potrzebuje cudzysłowów, ponieważ jest to wartość liczbowa. To, czy $URLpotrzebujesz, zależy od tego, na co pozwalasz, i czy nadal chcesz argumentu, jeśli jest pusty.

Zazwyczaj cytuję struny po prostu z przyzwyczajenia, ponieważ w ten sposób jest to bezpieczniejsze.

paxdiablo
źródło
2
Zauważ, że „spacje” naprawdę oznaczają „dowolne białe znaki”.
William Pursell,
4
@Cristian: Jeśli nie jesteś pewien, co może być w zmiennej, bezpieczniej jest ją zacytować. Zwykle stosuję tę samą zasadę, co paxdiablo, i po prostu mam zwyczaj cytowania wszystkiego (chyba że istnieje konkretny powód, aby tego nie robić).
Gordon Davisson,
11
Jeśli nie znasz wartości IFS, podaj ją bez względu na wszystko. Jeśli IFS=0, to echo $?może być bardzo zaskakujące.
Charles Duffy,
3
Cytuj na podstawie kontekstu, a nie na podstawie oczekiwanych wartości, w przeciwnym razie Twoje błędy będą gorsze. Na przykład, możesz mieć pewność, że żaden z Twoich ścieżek mają spacje, więc uważasz, że można napisać cp $source1 $source2 $dest, ale jeśli za jakiś nieoczekiwany powód destnie zostanie ustawiona, trzeci argument po prostu znika i będzie cicho skopiować source1ponad source2zamiast co daje odpowiedni błąd dla pustego miejsca docelowego (tak jak w przypadku cytowania każdego argumentu).
Derek Veit
3
quote it if...ma proces myślowy wstecz - cytaty nie są czymś, co dodajesz, kiedy jest to konieczne, lecz są usuwane, kiedy jest to konieczne. Zawsze zawijaj ciągi i skrypty w pojedyncze cudzysłowy, chyba że musisz użyć podwójnych cudzysłowów (np. Aby pozwolić zmiennej rozwinąć się) lub nie musisz używać cudzysłowów (np. W celu globowania i interpretacji nazw plików).
Ed Morton,
92

Krótko mówiąc, cytuj wszystko, gdzie powłoka nie wymaga dzielenia tokenów i rozwijania symboli wieloznacznych.

Pojedyncze cudzysłowy chronią tekst między nimi dosłownie. Jest to właściwe narzędzie, gdy trzeba upewnić się, że skorupa w ogóle nie dotyka sznurka. Zazwyczaj jest to mechanizm cytowania z wyboru, gdy nie jest wymagana zmienna interpolacja.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Podwójne cudzysłowy są odpowiednie, gdy wymagana jest zmienna interpolacja. Przy odpowiednich dostosowaniach jest to również dobre obejście, gdy potrzebujesz pojedynczych cudzysłowów w ciągu. (Nie ma prostego sposobu na uniknięcie pojedynczego cudzysłowu między pojedynczymi cudzysłowami, ponieważ w pojedynczych cudzysłowach nie ma mechanizmu ucieczki - gdyby tak było, nie cytowałyby one całkowicie dosłownie.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Żadne cudzysłowy nie są odpowiednie, gdy specjalnie potrzebujesz powłoki do dzielenia tokenów i / lub rozwijania symboli wieloznacznych.

Podział tokenów;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

Dla kontrastu:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(Pętla działa tylko raz, nad pojedynczym cytowanym ciągiem.)

 $ for word in '$words'; do echo "$word"; done
 $words

(Pętla działa tylko raz, nad dosłownym ciągiem pojedynczym.)

Rozszerzenie symboli wieloznacznych:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

Dla kontrastu:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Nie ma pliku o nazwie dosłownie file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Nie ma też żadnego pliku o nazwie $pattern!)

Mówiąc bardziej konkretnie, wszystko, co zawiera nazwę pliku, powinno być zwykle cytowane (ponieważ nazwy plików mogą zawierać białe znaki i inne metaznaki powłoki). Wszystko, co zawiera adres URL, powinno być zwykle cytowane (ponieważ wiele adresów URL zawiera metaznaki powłoki, takie jak ?i &). Wszystko, co zawiera wyrażenie regularne, powinno być zwykle cytowane (ditto ditto). Wszystko, co zawiera znaczące spacje inne niż pojedyncze spacje między znakami niebiałymi, musi zostać zacytowane (ponieważ w przeciwnym razie powłoka będzie munge spacja w, skutecznie, pojedyncze spacje i przyciąć wszelkie wiodące lub końcowe białe spacje).

Gdy wiesz, że zmienna może zawierać tylko wartość, która nie zawiera metaznaków powłoki, cytowanie jest opcjonalne. Zatem niecytowany $?jest w zasadzie w porządku, ponieważ ta zmienna może zawsze zawierać tylko jedną liczbę. Jest jednak "$?"również poprawny i zalecany dla ogólnej spójności i poprawności (chociaż jest to moja osobista rekomendacja, a nie powszechnie uznana polityka).

Wartości, które nie są zmiennymi, zasadniczo podlegają tym samym regułom, ale można również uciec od wszelkich metaznaków zamiast je cytować. W typowym przykładzie adres URL z &nim będzie analizowany przez powłokę jako polecenie w tle, chyba że metaznak zostanie zmieniony lub cytowany:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Oczywiście dzieje się tak również wtedy, gdy adres URL znajduje się w zmiennej niecytowanej.) W przypadku ciągu statycznego najbardziej sensowne są pojedyncze cudzysłowy, chociaż tutaj działa jakakolwiek forma cytowania lub zmiany znaczenia.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

Ostatni przykład sugeruje także inną przydatną koncepcję, którą lubię nazywać „cytowaniem huśtawkowym”. Jeśli chcesz mieszać pojedyncze i podwójne cudzysłowy, możesz użyć ich obok siebie. Na przykład następujące ciągi cytowane

'$HOME '
"isn't"
' where `<3'
"' is."

można wkleić razem jeden po drugim, tworząc pojedynczy długi ciąg po tokenizacji i usunięciu cytatu.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Nie jest to strasznie czytelne, ale jest to powszechna technika, dlatego warto ją znać.

Nawiasem mówiąc, skrypty zwykle nie powinny być używane lsdo niczego. Aby rozwinąć symbol wieloznaczny, po prostu ... użyj go.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(W tym ostatnim przykładzie pętla jest całkowicie zbędna; printfszczególnie dobrze działa z wieloma argumentami stat. Ale zapętlanie przez dopasowanie symboli wieloznacznych jest częstym problemem i często jest wykonywane niepoprawnie).

Zmienna zawierająca listę tokenów do zapętlenia lub symbol zastępczy do rozwinięcia jest rzadziej widoczna, dlatego czasami skracamy „cytować wszystko, chyba że wiesz dokładnie, co robisz”.

potrójny
źródło
1
Jest to wariant (części) odpowiedzi, którą zamieściłem na powiązane pytanie . Wklejam to tutaj, ponieważ jest to zwięzłe i wystarczająco zdefiniowane, aby stać się kanonicznym pytaniem dla tego konkretnego problemu.
tripleee
4
Zwrócę uwagę, że jest to pozycja nr 0 i powracający motyw na mywiki.wooledge.org/BashPitfalls zbiór typowych błędów Bash. Wiele, wiele pojedynczych pozycji na tej liście dotyczy w zasadzie tego problemu.
tripleee
27

Oto ogólnie trzypunktowy wzór na cytaty:

Podwójne cytaty

W kontekstach, w których chcemy ukryć dzielenie słów i globowanie. Również w kontekstach, w których chcemy, aby literał był traktowany jako ciąg, a nie wyrażenie regularne.

Pojedyncze cytaty

W literałach łańcuchowych, w których chcemy stłumić interpolację i specjalne traktowanie ukośników odwrotnych. Innymi słowy, sytuacje, w których stosowanie podwójnych cudzysłowów byłoby niewłaściwe.

Brak cytatów

W kontekstach, w których jesteśmy absolutnie pewni, że nie występują problemy z dzieleniem wyrazów lub globowaniem lub chcemy podziału i globowania słów .


Przykłady

Podwójne cytaty

  • dosłowne ciągi znaków z białymi spacjami ( "StackOverflow rocks!", "Steve's Apple")
  • zmienne rozszerzenia ( "$var", "${arr[@]}")
  • podstawienia poleceń ( "$(ls)", "`ls`")
  • globs, w których ścieżka katalogu lub część nazwy pliku zawiera spacje ( "/my dir/"*)
  • w celu ochrony pojedynczych cudzysłowów ( "single'quote'delimited'string")
  • Rozszerzenie parametru Bash ( "${filename##*/}")

Pojedyncze cytaty

  • nazwy poleceń i argumenty, które zawierają w nich spacje
  • ciągi literalne, które wymagają tłumienia interpolacji ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • w celu ochrony podwójnych cudzysłowów ( 'The "crux"')
  • Wyrażenia regularne, które wymagają interpolacji, aby je stłumić
  • użyj cudzysłowu powłoki dla literałów zawierających znaki specjalne ( $'\n\t')
  • użyj cytowania powłoki, w którym musimy chronić kilka pojedynczych i podwójnych cudzysłowów ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Brak cytatów

  • około Standardowe parametry liczbowe ( $$, $?, $#itd.)
  • w kontekstach arytmetyczne podoba ((count++)), "${arr[idx]}","${string:start:length}"
  • [[ ]]wyrażenie wewnętrzne wolne od dzielenia słów i globowania (jest to kwestia stylu, a opinie mogą się znacznie różnić)
  • gdzie chcemy dzielić słowa ( for word in $words)
  • gdzie chcemy globbing ( for txtfile in *.txt; do ...)
  • gdzie chcemy ~być interpretowani jako $HOME( ~/"some dir"ale nie "~/some dir")

Zobacz też:

codeforester
źródło
3
Zgodnie z tymi wytycznymi można uzyskać listę plików w katalogu głównym, pisząc "ls" "/" Wyrażenie „wszystkie konteksty łańcuchowe” wymaga dokładniejszej kwalifikacji.
William Pursell
5
W [[ ]]cudzysłowiu ma znaczenie po prawej stronie =/ ==i =~: robi różnicę między interpretowaniem łańcucha jako wzorca / wyrażenia regularnego lub dosłownie.
Benjamin W.
6
Dobry przegląd, ale komentarze @ BenjaminW. Są warte integracji, a ciągi cytowane w ANSI C ( $'...') powinny zdecydowanie mieć własną sekcję.
mklement0
3
@ mklement0, rzeczywiście są one równoważne. Te wytyczne wskazują, że zawsze powinieneś pisać "ls" "/"zamiast bardziej powszechnych ls /, i uważam to za główną wadę w wytycznych.
William Pursell,
4
Dla żadnych cytatów można dodać zadanie lub zmienną case:)
PesaThe
4

Zazwyczaj używam cytowanych jak "$var"dla bezpieczeństwa, chyba że jestem pewien, że $varnie zawiera miejsca.

Używam $varjako prostego sposobu łączenia linii:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped
Bach Lien
źródło
Ostatni komentarz jest nieco mylący; nowe linie są skutecznie zastępowane spacjami, a nie tylko usuwane.
tripleee
-1

Aby użyć zmiennych w skrypcie powłoki, użyj „” cytowanych zmiennych, ponieważ cytowany oznacza, że ​​zmienna może zawierać spacje lub znaki specjalne, które nie wpłyną na wykonanie skryptu powłoki. W przeciwnym razie, jeśli masz pewność, że w nazwie zmiennej nie ma żadnych spacji ani znaków specjalnych, możesz ich użyć bez „”.

Przykład:

echo „$ url name” - (można używać przez cały czas)

echo „$ url name” - (Nie można używać w takich sytuacjach, więc należy zachować ostrożność przed użyciem)

Vipul Sharma
źródło