Zmienna ze znakami cudzysłowu „$ ()”

12

Napisałem ten skrypt:

#!/bin/bash
while [ true ] 
do
    currentoutput="$(lsusb)"
    if [ "$currentoutput" != "$lastoutput" ]
    then
        echo "" date and Time >> test.log
        date +%x_r >> test.log
        lastoutput="$(lsusb)"
        lsusb >> test.log
    fi
    sleep 5
done

Jestem nowicjuszem, który próbuje szybko się uczyć i mam pytanie o cudzysłowy zmiennej .

Ustaw zmienną pomiędzy $ (), rozumiem, ale dlaczego znaki cudzysłowu są potrzebne nawet w ifinstrukcji? Czy to polecenie zagnieżdżone?

Shankhara
źródło
5
Zauważ, że while [ true ]tworzy nieskończoną pętlę, ale być może nie z tego powodu, dla którego tak myślisz; while [ false ] również produkuje nieskończoną pętlę, bo z jednym argumentem, [ ... ]uda, jeśli ten argument jest niepusty ciąg. while truerzeczywiście uruchomić polecenie nazwie true(które zawsze udaje).
chepner
4
Nie umieszczasz zmiennej w środku $(). Wstawiłeś polecenie do środka $().
Wildcard

Odpowiedzi:

23

Cudzysłowy uniemożliwiają „dzielenie słów”. To znaczy: dzielenie zmiennych na wiele elementów przy znakach spacji (a ściślej, w odstępach, tabulatorach i znakach nowej linii, jak określono w wartości domyślnej $IFSzmiennej powłoki).

Na przykład,

$ var="one two"
$ howmany(){ echo $#;  }
$ howmany $var
2
$ howmany "$var"
1

Tutaj definiujemy howmanyfunkcję, która po prostu informuje nas o liczbie podanych parametrów pozycyjnych. Jak widać, do zmiennej przekazywane są dwa elementy, a wraz z cudzysłowami tekst w zmiennej jest traktowany jako jedna jednostka.

Jest to ważne dla dokładnego przekazywania informacji. Na przykład, jeśli zmienna zawiera ścieżkę do pliku, a nazwa pliku zawiera spacje w dowolnym miejscu na ścieżce, polecenie, które próbujesz uruchomić, może zakończyć się niepowodzeniem lub dać niedokładne wyniki. Gdybyśmy próbowali utworzyć plik ze $varzmienną, touch $varutworzylibyśmy dwa pliki, ale touch "$var"tylko jeden.

To samo dotyczy twojej [ "$currentoutput" != "$lastoutput" ]części. Ten konkretny test przeprowadza porównanie dwóch ciągów. Po uruchomieniu testu [polecenie musi zobaczyć 3 argumenty - ciąg tekstowy, !=operator i kolejny ciąg tekstowy. Trzymanie podwójnych cudzysłowów zapobiega dzieleniu słów, a [polecenie rozpoznaje dokładnie 3 argumenty. Co się stanie, jeśli zmienne nie będą cytowane?

$ var="hello world"
$ foo="hi world"
$ [ $var != $foo ]
bash: [: too many arguments
$ 

Tutaj występuje dzielenie słów, a zamiast tego [widzimy dwa ciągi, helloa worldnastępnie !=, a następnie dwa inne ciągi hi world. Kluczową kwestią jest to, że bez podwójnych cudzysłowów zawartość zmiennych należy rozumieć jako oddzielne jednostki, a nie jedną całość.

Przypisanie podstawienia polecenia nie wymaga podwójnych cudzysłowów jak w

var=$( df )

gdzie dfzapisano dane wyjściowe polecenia var. Jednak dobrym zwyczajem jest zawsze podwójne cytowanie zmiennych i zastępowanie poleceń, $(...)chyba że faktycznie chcesz, aby dane wyjściowe były traktowane jako osobne elementy.


Na marginesie:

while [ true ]

część może być

while true

[to polecenie, które ocenia jego argumenty i [ whatever ]jest zawsze prawdziwe, niezależnie od tego, co jest w środku. Natomiast while trueużywa polecenia, truektóre zawsze zwraca status wyjścia pomyślnego zakończenia (i właśnie tego whilepotrzebuje pętla). Różnica polega na nieco większej przejrzystości i mniejszej liczbie przeprowadzonych testów. Możesz też użyć :zamiasttrue

Podwójne cytaty echo "" date and Timeczęściowo mogłyby zostać prawdopodobnie usunięte. Po prostu wstawiają pusty ciąg i dodają dodatkową przestrzeń do wyniku. Jeśli jest to pożądane, możesz je tam zachować, ale w tym przypadku nie ma żadnej szczególnej wartości funkcjonalnej.

lsusb >> test.log

Ta część prawdopodobnie mogłaby zostać zastąpiona echo "$currentoutput" >> test.log. Nie ma powodu, aby uruchomić lsusbponownie po tym, jak już został uruchomiony currentoutput=$(lsusb). W przypadkach, gdy końcowe znaki nowej linii muszą zostać zachowane na wyjściu - można było zobaczyć wartość przy wielokrotnym uruchamianiu polecenia, ale w przypadku lsusbtakiej potrzeby nie jest to konieczne. Im mniej wywołujesz zewnętrznych poleceń, tym lepiej, ponieważ każde wywołanie niewbudowanego polecenia powoduje koszty procesora, zużycia pamięci i czasu wykonania (nawet jeśli polecenia są prawdopodobnie wstępnie ładowane z pamięci).


Zobacz też:

Sergiy Kolodyazhnyy
źródło
1
Świetna odpowiedź, jeśli jeszcze tego nie zrobiłeś, powinieneś napisać książkę o bash. Ale w ostatniej części nie powinno echo "$currentoutput" >> test.log być lepiej printf '%s\n' "$currentoutput" >> test.log ?
pLumo,
2
@RoVo Tak, printfpowinno być preferowane ze względu na przenośność. Ponieważ używamy tutaj skryptu specyficznego dla bash, można go usprawiedliwić echo. Ale twoja obserwacja jest bardzo poprawna
Sergiy Kolodyazhnyy
1
@SergiyKolodyazhnyy, ... Nie jestem pewien, czy echojest to całkowicie niezawodne, nawet jeśli wiadomo, że powłoka to bash (i znana jest wartość flag xpg_echoi posix); jeśli twoja wartość może wynosić -nlub -e, może zniknąć zamiast zostać wydrukowana.
Charles Duffy,
4

W currentoutput="$(lsusb)"lsusb nie jest zmienną, jest to polecenie. To, co robi ta instrukcja, wykonuje lsusbpolecenie i przypisuje dane wyjściowe do currentoutputzmiennej.

Była to starsza składnia

currentoutput=`lsusb`

można go znaleźć w wielu przykładach i skryptach

Aby odpowiedzieć na drugą część pytania, if [ ]wystarczy ifzdefiniować składnię w bash. Zobacz więcej na https://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html

marosg
źródło
3
Myślę, że ważne jest, aby powiedzieć, że [ ]tak naprawdę jest to testpolecenie. Możesz używać ifinstrukcji także z innymi poleceniami, ponieważ zależy to od 0 lub niezerowego testu ich kodu wyjścia.
Arroniczny
... rzeczywiście, dużo lepiej jest biegać if grep -qe "somestring" fileniż biegać grep -qe "somestring" file; if [ $? = 0 ]; then ..., więc twierdzenie, które if [ ...jest częścią definicji ifskładni, nie tylko wprowadza w błąd, ale prowadzi do złych praktyk.
Charles Duffy,
4

Poniżej uruchamia zewnętrzne polecenie commandi zwraca jego wynik.

"$(command)"

Bez nawiasów / nawiasów szukałoby zmiennej zamiast uruchamiania polecenia:

"$variable"

Jeśli chodzi o różnicę między $variablei "$variable", staje się to istotne, gdy $variablezawiera spacje. Podczas używania "$variable"cała zawartość zmiennej zostanie wstawiona do jednego ciągu, nawet jeśli zawartość zawiera spacje. Podczas korzystania $variablez zawartości zmiennej można rozwinąć listę argumentów zawierającą wiele argumentów.

thomasrutter
źródło
Cześć @ thomasrutter, przepraszam, miałem na myśli znak cudzysłowu ... Teraz edytuję swój komentarz!
Shankhara,
1

Być contrarian - bash zaleca użyć [[ ... ]]na [ ... ]konstrukcie, aby uniknąć konieczności zacytować zmienne w teście, a więc związanych z tym problemów z podziałem na słowa, że inni zwrócili uwagę.

[jest zapewniony w bash dla zgodności z POSIX ze skryptami przeznaczonymi do uruchamiania #!/bin/shlub tymi, które są przenoszone do bash - w większości przypadków powinieneś tego unikać na korzyść [[.

na przykład

# With [ - quotes are needed

$ foo='one two'; bar='one two'; [ $foo = $bar ] && echo "they're equal"
-bash: [: too many arguments

$ foo='one two'; bar='one two'; [ "$foo" = "$bar" ] && echo "they're equal"                                                                                                              
they're equal

# versus [[ - quotes not needed

$ foo='one two'; bar='one two'; [[ $foo = $bar ]] && echo "they're equal"
they're equal
shalomb
źródło
bashnie daje takich zaleceń; zapewnia to, [[ ... ]]co może być wygodniejsze i ma pewną funkcjonalność, która [ ... ]tego nie robi, ale nie ma problemu z [ ... ] prawidłowym użyciem .
chepner
@chepner - Może powinienem być tutaj jaśniejszy. Jasne, że nie jest to jednoznaczna rekomendacja na stronie podręcznika bash, ale biorąc pod uwagę, że [[robi wszystko [i więcej, a mimo to wiele razy [tripuje ludzi, a następnie próbuje i modernizuje funkcje z [[powrotem [tylko po to, aby zawieść i pozostawiać rozrzucone błędy - można powiedzieć, że jest to rekomendacje społeczności w zakresie, w jakim najbardziej odpowiednie przewodniki stylu bash zalecają nigdy nie używać[ .
shalomb