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?




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.


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]

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 -


// 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
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.