Co jest nie tak z „echo $ (rzeczy)” lub „echo„ rzeczy ”?

31

Użyłem jednego z poniższych

echo $(stuff)
echo `stuff`

(gdzie stuffjest np pwdlub dateczy coś bardziej skomplikowane).

Potem powiedziano mi, że ta składnia jest nieprawidłowa, zła praktyka, nie elegancki, nadmierny, zbędny, zbyt skomplikowany, program kultu kultowego, noobish, naiwny itp.

Ale polecenie nie działa, więc co dokładnie jest nie tak?

Kamil Maciorowski
źródło
11
Jest zbędny i dlatego nie należy go używać. Porównaj z Useless Use Of Cat: porkmail.org/era/unix/award.html
dr01
7
echo $(echo $(echo $(echo$(echo $(stuff)))))również ma pracę, a jeszcze prawdopodobnie nie będzie go używać, prawda? ;-)
Peter - Przywróć Monikę
1
Co dokładnie próbujesz zrobić z tym rozszerzeniem?
ciekawy
@curiousguy Próbuję pomóc innym użytkownikom, stąd moja odpowiedź poniżej. Ostatnio widziałem co najmniej trzy pytania, które używają tej składni. Ich autorzy próbowali zrobić… różne stuff. : D
Kamil Maciorowski
2
Czyż nie wszyscy zgadzamy się, że nierozsądne jest ograniczanie się do komory echa? ;-)
Peter - Przywróć Monikę

Odpowiedzi:

63

tl; dr

Podeszwa stuffbędzie najprawdopodobniej pracować dla Ciebie.



Pełna odpowiedź

Co się dzieje

Kiedy biegniesz foo $(stuff), dzieje się tak:

  1. stuff biegnie;
  2. jego wyjście (standardowe wyjście), zamiast być drukowane, zastępuje $(stuff)wywołanie foo;
  3. następnie foouruchamia się, argumenty wiersza poleceń oczywiście zależą od tego, co zostało stuffzwrócone.

Ten $(…)mechanizm nazywa się „zastępowaniem poleceń”. W twoim przypadku głównym poleceniem jest echozasadniczo wypisywanie argumentów wiersza poleceń na standardowe wyjście. Wszystko, co stuffpróbuje wydrukować na standardowe wyjście, jest przechwytywane, przekazywane echoi drukowane na standardowe wyjście echo.

Jeśli chcesz, aby wyjście stuffzostało wydrukowane na standardowe wyjście, po prostu uruchom podeszwę stuff.

`…`Składnia służy temu samemu celowi co $(…)(pod tym samym tytułem: „podstawienia polecenia”), istnieje kilka różnic chociaż, więc nie można ślepo je wymieniać. Zobacz to FAQ i to pytanie .


Czy powinienem unikać echo $(stuff)bez względu na wszystko?

Istnieje powód, z którego możesz skorzystać, echo $(stuff)jeśli wiesz, co robisz. Z tego samego powodu powinieneś unikać, echo $(stuff)jeśli tak naprawdę nie wiesz, co robisz.

Chodzi o to stuffi echo $(stuff)nie jest dokładnie równoważne. Ten ostatni oznacza wywołanie operatora split + glob na wyjściu stuffz wartością domyślną $IFS. Podwójne cytowanie podstawienia polecenia zapobiega temu. Cytując pojedynczą zamianę polecenia, nie jest to już podstawienie polecenia.

Aby to zaobserwować, jeśli chodzi o dzielenie, uruchom następujące polecenia:

echo "a       b"
echo $(echo "a       b")
echo "$(echo "a       b")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "a       b")'

I do globowania:

echo "/*"
echo $(echo "/*")
echo "$(echo "/*")"  # the shell is smart enough to identify the inner and outer quotes
echo '$(echo "/*")'

Jak widać, echo "$(stuff)"jest to odpowiednik (-ish *) stuff. Możesz go użyć, ale po co komplikować to w ten sposób?

Z drugiej strony, jeśli chcesz wyjście stuffdo poddania dzielenie + uogólniające wtedy mogą znaleźć echo $(stuff)przydatne. To jednak musi być twoja świadoma decyzja.

Istnieją polecenia generujące dane wyjściowe, które powinny zostać ocenione (w tym dzielenie, globowanie i inne) i uruchamiane przez powłokę, więc eval "$(stuff)"jest taka możliwość (patrz ta odpowiedź ). Nigdy nie widziałem polecenia, które wymagałoby wyjścia w celu dodatkowego podziału + globowania przed wydrukowaniem . Celowe używanie echo $(stuff)wydaje się bardzo rzadkie.


Co var=$(stuff); echo "$var"?

Słuszna uwaga. Ten fragment kodu:

var=$(stuff)
echo "$var"

powinno być równoważne z echo "$(stuff)"(-ish *) do stuff. Jeśli jest to cały kod, po prostu uruchom stuffzamiast tego.

Jeśli jednak musisz użyć wyników stuffwięcej niż jeden raz, to podejście

var=$(stuff)
foo "$var"
bar "$var"

jest zwykle lepszy niż

foo "$(stuff)"
bar "$(stuff)"

Nawet jeśli footak, echoi dostaniesz echo "$var"kod, może być lepiej zachować go w ten sposób. Rzeczy do rozważenia:

  1. Z var=$(stuff) stuffbiegami raz; nawet jeśli polecenie jest szybkie, właściwym rozwiązaniem jest unikanie dwukrotnego obliczania tego samego wyniku. A może stuffma skutki inne niż pisanie na standardowe wyjście (np. Tworzenie pliku tymczasowego, uruchamianie usługi, uruchamianie maszyny wirtualnej, powiadamianie zdalnego serwera), więc nie chcesz uruchamiać go wiele razy.
  2. Jeśli stuffgeneruje czasu w zależności czy wyjście nieco przypadkowy, można uzyskać niespójne wyniki foo "$(stuff)"i bar "$(stuff)". Po var=$(stuff)ustaleniu wartości $vari możesz być pewny foo "$var"i bar "$var"uzyskać identyczny argument wiersza poleceń.

W niektórych przypadkach zamiast tego foo "$var"możesz chcieć (potrzebować) użyć foo $var, szczególnie jeśli stuffgeneruje wiele argumentów dla foo(zmienna tablicowa może być lepsza, jeśli twoja powłoka ją obsługuje). Znowu wiedz, co robisz. Jeśli chodzi o echoróżnicę między echo $vari echo "$var"jest taka sama jak między echo $(stuff)i echo "$(stuff)".


* Odpowiednik ( -ish )?

Powiedziałem, że echo "$(stuff)"jest równoważne (-ish) do stuff. Istnieją co najmniej dwa problemy, które sprawiają, że nie jest to dokładnie równoważne:

  1. $(stuff)działa stuffw podpowłoce, więc lepiej powiedzieć, że echo "$(stuff)"jest równoważne (-ish) (stuff). Polecenia, które wpływają na powłokę, w której działają, jeśli są w podpowłoce, nie wpływają na główną powłokę.

    W tym przykładzie stuffjest a=1; echo "$a":

    a=0
    echo "$(a=1; echo "$a")"      # echo "$(stuff)"
    echo "$a"
    

    Porównaj to z

    a=0
    a=1; echo "$a"                # stuff
    echo "$a"
    

    i z

    a=0
    (a=1; echo "$a")              # (stuff)
    echo "$a"
    

    Kolejny przykład, zacznij od stuffbycia cd /; pwd:

    cd /bin
    echo "$(cd /; pwd)"           # echo "$(stuff)"
    pwd
    

    oraz testy stuffi (stuff)wersje.

  2. echonie jest dobrym narzędziem do wyświetlania niekontrolowanych danych . To, o echo "$var"czym rozmawialiśmy, powinno być printf '%s\n' "$var". Ale ponieważ wspomniano w tym pytaniu echoi ponieważ najbardziej prawdopodobnym rozwiązaniem nie jest zastosowanie echow pierwszej kolejności, postanowiłem nie przedstawiać printfdo tej pory.

  3. stufflub (stuff)przeplata wyjście stdout i stderr, jednocześnie echo $(stuff)wypisując wszystkie wyjście stderr z stuff(które uruchamia się jako pierwsze), a dopiero potem przetworzone wyjście stdout przez echo(które uruchamia się jako ostatnie).

  4. $(…)usuwa wszelkie końcowe znaki nowej linii, a następnie echododaje je z powrotem. echo "$(printf %s 'a')" | xxdDaje więc inną wydajność niż printf %s 'a' | xxd.

  5. Niektóre polecenia ( lsna przykład) działają inaczej w zależności od tego, czy standardowym wyjściem jest konsola, czy nie; to ls | catsamo nie lsrobi. Podobnie echo $(ls)będzie działać inaczej niż ls.

    Odkładając na lsbok, w ogólnym przypadku, jeśli musisz zmusić to inne zachowanie, to stuff | catjest lepsze niż echo $(ls)lub echo "$(ls)"ponieważ nie powoduje to wszystkich innych problemów wymienionych tutaj.

  6. Prawdopodobnie inny status wyjścia (wspomniany dla kompletności tej odpowiedzi na wiki; szczegółowe informacje znajdują się w innej odpowiedzi, która zasługuje na uznanie).

rev Kamil Maciorowski
źródło
5
Jeszcze jedna różnica: stuffsposób będzie przeplatał stdouti stderrgenerował, jednocześnie echo `stuff` drukując wszystkie dane stderrwyjściowe z stuff, i tylko wtedy dane stdoutwyjściowe.
Ruslan
3
A to kolejny przykład na: W skryptach bash nie może być zbyt wielu cytatów. echo $(stuff)może echo "$(stuff)"sprawić, że będziesz mieć o wiele więcej problemów , jakbyś nie chciał jawnie globowania i ekspansji.
rexkogitans
2
Jeszcze jedna drobna różnica: $(…)usuwa wszelkie końcowe znaki nowej linii, a następnie echododaje je z powrotem. echo "$(printf %s 'a')" | xxdDaje więc inną wydajność niż printf %s 'a' | xxd.
derobert
1
Nie należy zapominać, że niektóre polecenia ( lsna przykład) działają inaczej w zależności od tego, czy standardowym wyjściem jest konsola, czy nie; to ls | catsamo nie lsrobi. I oczywiście echo $(ls)będzie działać inaczej niż ls.
Martin Rosenau,
@Ruslan Odpowiedzią jest teraz wiki społeczności. Dodałem twój użyteczny komentarz do wiki. Dziękuję Ci.
Kamil Maciorowski
5

Kolejna różnica: utracono kod wyjścia podpowłoki, dlatego echozamiast tego pobierany jest kod wyjścia .

> stuff() { return 1
}
> stuff; echo $?
1
> echo $(stuff); echo $?
0
Wafel Suflet
źródło
3
Witamy w Super User! Czy potrafisz wyjaśnić różnicę, którą demonstrujesz? Dzięki
bertieb,
4
Wierzę, że to pokazuje, że kodem zwrotnym $?będzie kod powrotu echo(for echo $(stuff)) zamiast tego returnedytowanego przez stuff. stuffFunkcja return 1(kod wyjścia), ale echoustawia go w pozycji „0”, niezależnie od kodu powrotu stuff. Nadal powinien być w odpowiedzi.
Ismael Miguel
wartość zwrotna z podpowłoki została utracona
phuclv