Cytowanie w $ (podstawianie poleceń) w Bash

126

W moim środowisku Bash używam zmiennych zawierających spacje i używam tych zmiennych w ramach zastępowania poleceń. Niestety nie mogę znaleźć odpowiedzi na SE.

Jaki jest prawidłowy sposób cytowania moich zmiennych? Jak mam to zrobić, jeśli są zagnieżdżone?

DIRNAME=$(dirname "$FILE")

lub czy mam wycenę poza zamianą?

DIRNAME="$(dirname $FILE)"

lub obydwa?

DIRNAME="$(dirname "$FILE")"

lub czy używam tykania wstecznego?

DIRNAME=`dirname "$FILE"`

Jak to zrobić? Jak mogę łatwo sprawdzić, czy cytaty są ustawione poprawnie?

Kuzyn Kokaina
źródło
1
To dobre pytanie, ale biorąc pod uwagę wszystkie problemy z osadzonymi odstępami, dlaczego miałbyś utrudniać sobie życie, używając ich celowo?
Joe
3
@Joe, z osadzonymi odstępami masz na myśli miejsce w nazwach plików? Osobiście nie używam ich tak często, ale pracuję z katalogami i plikami innych ludzi, których nie jestem pewien, czy zawierają spacje. Co więcej, myślę, że lepiej jest to zrobić od razu, więc nie muszę się martwić w przyszłości.
CousinCocaine
4
Tak. Co zrobimy z tymi „innymi ludźmi”? <G>
Joe

Odpowiedzi:

188

W kolejności od najgorszego do najlepszego:

  • DIRNAME="$(dirname $FILE)" nie zrobi tego, co chcesz, jeśli $FILEzawiera białe znaki lub znaki globowania \[?*.
  • DIRNAME=`dirname "$FILE"`jest technicznie poprawny, ale backtyki nie są zalecane do rozszerzania poleceń ze względu na dodatkową złożoność podczas ich zagnieżdżania.
  • DIRNAME=$(dirname "$FILE")jest poprawne, ale tylko dlatego, że jest to zadanie . Jeśli użyjesz podstawienia polecenia w jakimkolwiek innym kontekście, takim jak export DIRNAME=$(dirname "$FILE")lub du $(dirname "$FILE"), brak cudzysłowów spowoduje problemy, jeśli wynik rozwinięcia zawiera białe znaki lub znaki globowania.
  • DIRNAME="$(dirname "$FILE")"jest zalecanym sposobem. Możesz zamienić DIRNAME=na komendę i spację, nie zmieniając niczego innego, i dirnameotrzymasz poprawny ciąg.

Aby jeszcze bardziej poprawić:

  • DIRNAME="$(dirname -- "$FILE")"działa, jeśli $FILEzaczyna się od myślnika.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"działa, nawet jeśli $FILEkończy się na nowej linii, ponieważ $()odcina nowe linie na końcu wyniku i dirnamewypisuje nową linię po wyniku. Sheesh dirname, dlaczego musisz być inny?

Możesz zagnieżdżać rozszerzenia poleceń tak, jak chcesz. Z $()wami utworzyć nowy powołując kontekst, dzięki czemu można robić takie rzeczy:

foo "$(bar "$(baz "$(ban "bla")")")"

Zdajesz nie chcą spróbować z backticks.

l0b0
źródło
3
Jasna odpowiedź na moje pytanie. Czy po zagnieżdżeniu tych zmiennych mogę dalej cytować tak jak teraz?
Kuzyn Kokaina
3
Czy istnieje odwołanie / zasób opisujący zachowanie cytatów w podstacji komend w cudzysłowie?
AmadeusDrZaius
12
@AmadeusDrZaius „ $()Zawsze tworzysz nowy kontekst cytowania”, więc to tak, jak poza zewnętrznymi cytatami. O ile mi wiadomo, nie ma w tym nic więcej.
l0b0
4
@ l0b0 Dzięki, tak, znalazłem twoje wyjaśnienie bardzo jasne. Zastanawiałem się tylko, czy to gdzieś w instrukcji. Znalazłem to (choć nieoficjalnie) w wooledge . Sądzę, że jeśli dokładnie przeczytasz o kolejności podstawień , możesz w ten sposób wyciągnąć ten fakt.
AmadeusDrZaius
1
Tak więc zagnieżdżone cudzysłowy są dopuszczalne, ale zniechęcają nas, ponieważ większość schematów kolorowania składni nie wykrywa szczególnych okoliczności. Schludny.
Luke Davis
19

Zawsze możesz pokazać efekty cytowania zmiennych za pomocą printf.

Podział słów na var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 cytowany, więc bez podziału słów:

$ printf '[%s]\n' "$var1"
[hello     world]

Podział słów w var1środku $(), co odpowiada echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

Bez podziału słów var1, nie ma problemu z nie cytowaniem $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

var1Ponowne dzielenie słów :

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Cytując oba, najłatwiejszy sposób, aby się upewnić.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Problem globbingowy

Brak cytowania zmiennej może również prowadzić do globalnego rozszerzenia jej zawartości:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Zauważ, że dzieje się to po rozwinięciu zmiennej. Podczas przypisywania nie jest konieczne cytowanie globu:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Użyj, set -faby wyłączyć to zachowanie:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

I set +fponownie włączyć go:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
Graeme
źródło
8
Ludzie często zapominają, że dzielenie słów nie jest jedynym problemem, możesz zmienić swój przykład, var1='hello * world'aby zilustrować problem globowania.
Stéphane Chazelas
10

Dodatek do zaakceptowanej odpowiedzi:

Chociaż ogólnie zgadzam się z odpowiedzią @ l0b0 tutaj, podejrzewam, że umieszczenie odsłoniętych backticksów na liście „najgorszych do najlepszych” przynajmniej częściowo wynika z założenia, które $(...)jest dostępne wszędzie. Zdaję sobie sprawę, że pytanie określa Bash, ale zdarza się, że Bash ma na myśli wiele /bin/sh, co nie zawsze może być pełną powłoką Bourne Again.

W szczególności zwykła powłoka Bourne'a nie będzie wiedziała, co z tym zrobić $(...), więc skrypty, które twierdzą, że są z nią kompatybilne (np. Poprzez #!/bin/shlinię shebang) prawdopodobnie źle się zachowają, jeśli faktycznie są uruchamiane przez „prawdziwe” /bin/sh- to jest szczególnie interesujące, gdy powiedzmy, tworzenie skryptów inicjujących lub pakowanie skryptów wstępnych i końcowych, i może wylądować w zaskakującym miejscu podczas instalacji systemu podstawowego.

Jeśli coś takiego brzmi jak coś, co planujesz zrobić z tą zmienną, zagnieżdżanie jest prawdopodobnie mniejszym problemem niż faktyczne, przewidywalne uruchomienie skryptu. Gdy jest to wystarczająco prosta sprawa, a przenośność stanowi problem, nawet jeśli spodziewam się, że skrypt będzie zwykle działał w systemach, w których /bin/shznajduje się Bash, często z tego powodu używam back -icksa z wieloma przypisaniami zamiast zagnieżdżania.

Powiedziawszy to wszystko, wszechobecność powłok, które implementują $(...)(Bash, Dash i in.), Pozostawia nas w dobrym miejscu, aby trzymać się ładniejszej, łatwiejszej do zagnieżdżenia, a ostatnio w większości przypadków preferowanej składni POSIX, dla wszystkie powody wymienione w l0b0.

Poza tym: czasami pojawia się to również na StackOverflow -

rsandwick3
źródło
// Doskonała odpowiedź. W przeszłości miałem również problemy z kompatybilnością wsteczną z / bin / sh. Czy masz jakieś porady, jak poradzić sobie z jego problemem, stosując metody kompatybilne wstecz?
Nathan Basanese
z mojego doświadczenia: czasami może zajść potrzeba zmiany backticksa na dolara zamkniętego i nigdy nie pomogło (było to konieczne, by być konkretnym) zamianę dolara zamkniętego na backticksa. Piszę wstecz tylko w kodzie javascript, a nie w kodzie powłoki.
Steven Lu