Dlaczego muszę cytować zmienną dla if, ale nie dla echa?


26

Czytałem, że potrzebujesz podwójnych cudzysłowów do rozwijania zmiennych, np

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

będzie działać zgodnie z oczekiwaniami, podczas gdy

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

zawsze powie, $test oknawet jeśli $testjest zerowy.

ale dlaczego nie potrzebujemy cytatów echo $test?


2
Jeśli nie podasz zmiennej używanej jako argument do echo, dodatkowe spacje i znaki nowej linii zostaną usunięte.
jordanm

Odpowiedzi:


36

Zawsze potrzebujesz cudzysłowów wokół zmiennych we wszystkich kontekstach listy , czyli wszędzie tam, gdzie zmienna może być rozwinięta do wielu wartości, chyba że chcesz, aby 3 skutki uboczne pozostawienia zmiennej nie były cytowane.

konteksty list zawierają argumenty do prostych poleceń, takich jak [lub echo, for i in <here>przypisania do tablic ... Istnieją inne konteksty, w których należy również cytować zmienne. Najlepiej jest zawsze cytować zmienne, chyba że masz bardzo dobry powód, aby tego nie robić.

Pomyśl o braku cudzysłowów (w kontekście list) jako o operatorze split + glob .

Jakby echo $testbyło echo glob(split("$test")).

Zachowanie powłoki jest mylące dla większości ludzi, ponieważ w większości innych języków umieszczasz cudzysłowy wokół ustalonych ciągów znaków, takich jak puts("foo"), a nie wokół zmiennych (jak puts(var)), podczas gdy w powłoce jest odwrotnie: wszystko jest ciągiem znaków w powłoce, więc umieszczanie cudzysłowów wokół wszystkiego byłoby kłopotliwe, ty echo testnie musisz "echo" "test". W powłoce cudzysłowy są używane do czegoś innego: zapobiegają specjalnemu znaczeniu niektórych znaków i / lub wpływają na zachowanie niektórych rozszerzeń.

W [ -n $test ]lub echo $testpowłoka zostanie podzielona $test(domyślnie na puste), a następnie wygeneruje nazwę pliku (rozwiń wszystkie *wzorce „?” ... do listy pasujących plików), a następnie przekaże tę listę argumentów do poleceń [lub echo.

Ponownie pomyśl o tym jak "[" "-n" glob(split("$test")) "]". Jeśli $testjest pusty lub zawiera tylko spacje (spc, tab, nl), to operator split + glob zwróci pustą listę, więc [ -n $test ]będzie "[" "-n" "]", co jest testem sprawdzającym, czy „-n” jest pustym łańcuchem, czy nie. Ale wyobraź sobie, co by się stało, gdyby $test„*” lub „= foo” ...

W [ -n "$test" ], [jest przekazywana cztery argumenty "[", "-n", ""oraz "]"(bez cudzysłowów), który jest to, co chcemy.

Niezależnie od tego, czy to robi różnicę, echoczy [nie, po prostu echogeneruje to samo, niezależnie od tego, czy przekazano pusty argument, czy nie.

Zobacz także tę odpowiedź na podobne pytanie, aby uzyskać więcej informacji na temat [polecenia i [[...]]konstrukcji.


7

Odpowiedź @ h3rrmiller jest dobra do wyjaśnienia, dlaczego potrzebujesz cytatów dla if(a raczej, [/ test), ale tak naprawdę uznałbym, że twoje pytanie jest nieprawidłowe.

Wypróbuj następujące polecenia, a zobaczysz, co mam na myśli.

export testvar="123    456"
echo $testvar
echo "$testvar"

Bez cudzysłowów podstawienie zmiennej powoduje rozwinięcie drugiego polecenia do:

echo 123    456

a wiele spacji jest zwiniętych do jednego:

echo 123 456

Dzięki cudzysłowom spacje są zachowane.

Dzieje się tak, ponieważ gdy cytujesz parametr (niezależnie od tego, czy parametr ten jest przekazywany echo, testczy jakaś inna komenda), wartość tego parametru jest wysyłana jako jedna wartość do komendy. Jeśli go nie zacytujesz, powłoka wykonuje swoją normalną magię, szukając białych znaków, aby określić, gdzie zaczyna się i kończy każdy parametr.

Można to również zilustrować następującym (bardzo, bardzo prostym) programem C. Wypróbuj poniższe w wierszu poleceń (możesz to zrobić w pustym katalogu, aby nie ryzykować zastąpienia czegoś).

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

i wtedy...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

Po uruchomieniu paramtest, $?odbędzie się szereg parametrów zostało przekazanych (i że numer zostanie podany).


2

Chodzi o to, jak powłoka interpretuje wiersz przed uruchomieniem programu.

Jeśli wiersz jest czytany echo I am $USER, powłoka rozwija go echo I am blrfli echonie ma pojęcia, czy początek tekstu jest dosłowny czy zmienny. Podobnie, jeśli linia będzie czytać echo I am $UNDEFINED, powłoka rozwinie się $UNDEFINEDw nic i argumenty echa będą I am, i to jest koniec. Ponieważ echodziała dobrze bez argumentów, echo $UNDEFINEDjest całkowicie poprawny.

Twój problem z iftak naprawdę nie jest if, ponieważ ifpo prostu uruchamia dowolny program i argumenty za nim i wykonuje thenczęść, jeśli program się kończy 0(lub elseczęść, jeśli istnieje, a program kończy się inaczej 0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

Kiedy używasz if [ ... ]do porównania, nie używasz prymitywów wbudowanych w powłokę. W rzeczywistości instruujesz powłokę, aby uruchamiała program o nazwie, [który jest bardzo niewielkim nadzbiorem test(1)tego wymaganym ostatnim argumentem ]. Oba programy kończą działanie, 0jeśli warunek testu spełni się, a 1jeśli nie.

Niektóre testy przerywają się, gdy zmienna jest niezdefiniowana, ponieważ testnie widać, że używasz zmiennej. Ergo, [ $UNDEFINED -eq 2 ]psuje się, ponieważ zanim skończona jest z nim powłoka, testwidoczne są tylko argumenty -eq 2 ], co nie jest poprawnym testem. Jeśli zrobiłeś to z czymś zdefiniowanym, np. Działałoby to [ $DEFINED -ne 0 ], ponieważ powłoka rozszerzyłaby go do ważnego testu (np 0 -ne 0.).

Jest różnica semantyczna między foo $UNDEFINED bar, która rozwija się do dwóch argumentów ( fooi bar), ponieważ $UNDEFINEDzasłużyła na swoją nazwę. Porównaj to z foo "$UNDEFINED" bar, które rozwija się do trzech argumentów ( foopusty ciąg i `bar). Cudzysłowy zmuszają powłokę do interpretowania ich jako argumentu, czy coś jest między nimi, czy nie.


0

Bez cudzysłowów $testmoże rozwinąć się w więcej niż jedno słowo, dlatego trzeba je zacytować, aby nie złamać składni, ponieważ każdy przełącznik w [poleceniu oczekuje jednego argumentu, co robią cudzysłowy (powoduje, że cokolwiek $testrozwija się w jeden argument)

Powodem, dla którego nie potrzebujesz cudzysłowów, aby rozwinąć zmienną, echojest to, że nie oczekuje ona jednego argumentu. Po prostu wydrukuje to, co mu powiesz. Więc nawet jeśli $testrozwinie się do 100 słów, echo nadal go wydrukuje.

Spójrz na Bash Pitfalls


tak, ale dlaczego nie potrzebujemy tego echo?
CharlesB

@CharlesB Potrzebujesz cytatów echo. Co sprawia, że ​​myślisz inaczej?
Gilles „SO- przestań być zły”

Nie potrzebuję ich, mogę echo $testi to działa (wyświetla wartość $ test)
CharlesB

1
@CharlesB Wykazuje wartość $ test tylko wtedy, gdy nie zawiera nigdzie wielu spacji. Wypróbuj program w mojej odpowiedzi, aby zilustrować przyczynę.
CVn

0

Puste parametry są usuwane, jeśli nie są cytowane:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

Wywołane polecenie nie widzi, że w wierszu poleceń powłoki był pusty parametr. Wygląda na to, że [jest zdefiniowane tak, aby zwracało 0 dla -n bez następowania. Dlaczego zawsze.

Cytowanie ma również wpływ na echo w kilku przypadkach:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"

2
To nie jest echo, to skorupa. Zobaczysz to samo zachowanie z ls. Poświęć touch '*'trochę czasu na przygodę. :)
CVn

To tylko sformułowanie, ponieważ nie ma różnicy w przypadku „jeśli [...]”. [nie jest specjalnym poleceniem powłoki. Różni się to od [[(w bash), gdzie cytowanie nie jest konieczne.
Hauke ​​Laging
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.