Jaki jest właściwy sposób cytowania $ (polecenie $ arg)?


17

Najwyższy czas rozwiązać tę zagadkę, która niepokoi mnie od lat ...

Spotykałem się z tym od czasu do czasu i myślałem, że to jest właściwa droga:

$(comm "$(arg)")

I pomyślałem, że moje zdanie jest mocno poparte doświadczeniem. Ale nie jestem już taki pewien. Shellcheck też nie może się zdecydować. Oba są:

"$(dirname $0)"/stop.bash
           ^-- SC2086: Double quote to prevent globbing and word splitting.

I:

$(dirname "$0")/stop.bash
^-- SC2046: Quote this to prevent word splitting.

Jaka jest logika?

(Jest to wersja Shellcheck 0.4.4, btw.)


1
Cytuj wszystkie podstawienia.
Ignacio Vazquez-Abrams

3
Po prostu tak: "$(dirname "$0")"/stop.bash? Wydaje się, że działa ... Jaka jest historia?
Tomasz

1
bash jest wystarczająco inteligentny, aby wiedzieć, kiedy zmienił się kontekst.
Ignacio Vazquez-Abrams

1
pomyśl o tym w ten sposób: shell_level_1 $(shell_level_2) shell_level_1gdy jesteś w środku $(....), wchodzisz w „podpoziom” powłoki, ALE możesz pisać w niej tak, jakbyś był na poziomie podstawowym (tzn. możesz pisać bezpośrednio "zamiast \", itp.). np. touch "/tmp/a file" ; echo "its size is: $(find "/tmp/a file" -ls | awk '{print $5}) ..." : if you used backticks you'd have to znajdź \ "/ tmp / plik \" i print \$5. Bez $(...)potrzeby: powłoka dostosowuje się do nowego poziomu i możesz pisać bezpośrednio, tak jakby Twój tłumacz również był na tym poziomie.
Olivier Dulac

„Shellcheck też nie może się zdecydować.” - Dwa przebiegi mają dwa różne komunikaty i wskazują różne miejsca. Chce obu.
Izkata

Odpowiedzi:


45

Musisz użyć "$(somecmd "$file")".

Bez cudzysłowów ścieżka ze spacją zostanie podzielona w argumencie do somecmdi będzie kierowana na niewłaściwy plik. Potrzebujesz więc cytatów w środku.

Wszelkie spacje na wyjściu somecmdrównież powodują dzielenie, więc potrzebujesz cudzysłowów na zewnątrz całego podstawienia polecenia.

Cytaty w podstawieniu polecenia nie mają wpływu na cytaty poza nim. Podręcznik referencyjny Bash nie jest zbyt jasny, ale BashGuide wyraźnie o tym wspomina . Tekst w POSIX wymaga również to, jak „każdej ważnej skryptu powłoki” jest dozwolone wewnątrz $(...):

W $(command)formularzu wszystkie znaki następujące po otwartym nawiasie do pasującego nawiasu zamykającego stanowią polecenie. Do polecenia można użyć dowolnego poprawnego skryptu powłoki, z wyjątkiem skryptu składającego się wyłącznie z przekierowań, który daje nieokreślone wyniki.


Przykład:

$ file="./space here/foo"

za. Brak cytatów, dirnameprzetwarza zarówno ./spacea here/foo:

$ printf "<%s>\n" $(dirname $file)
<.>
<here>

b. Cytaty wewnątrz, dirnameprocesy ./space here/foo, dawanie ./space here, które dzieli się na dwa:

$ printf "<%s>\n" $(dirname "$file")
<./space>
<here>

do. Cytuje na zewnątrz, dirnameprzetwarza oba ./spacei here/foo, wyprowadza na osobnych wierszach, ale teraz te dwa wiersze tworzą pojedynczy argument :

$ printf "<%s>\n" "$(dirname $file)"
<.
here>

re. Cytuje zarówno wewnątrz, jak i na zewnątrz, daje to prawidłową odpowiedź:

$ printf "<%s>\n" "$(dirname "$file")"
<./space here>

(byłoby to prawdopodobnie prostsze, gdyby dirnameprzetworzył tylko pierwszy argument, ale nie pokazałoby różnicy między przypadkami a i c.)

Zauważ, że wraz z dirname(i prawdopodobnie innymi) musisz również dodać --, aby zapobiec traktowaniu nazwy pliku jako opcji na wypadek, gdyby zaczynało się od myślnika, więc użyj "$(dirname -- "$file")".


16
Uwaga: Zasadniczo cytaty nie zagnieżdżają się w bash; ale w tym przypadku $( )tworzy nowy kontekst, więc cytaty w nim są niezależne od cudzysłowów poza nim. I ogólnie chcesz cytatów w obu kontekstach.
Gordon Davisson
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.