Jaki jest cel wbudowanego: (dwukropka) GNU Bash?

335

Jaki jest cel polecenia, które nic nie robi, będąc niewiele więcej niż liderem komentarzy, ale w rzeczywistości jest wbudowaną powłoką samą w sobie?

Jest to wolniejsze niż wstawianie komentarza do skryptów o około 40% na wywołanie, co prawdopodobnie różni się znacznie w zależności od wielkości komentarza. Jedyne możliwe powody, dla których to widzę, to:

# poor man's delay function
for ((x=0;x<100000;++x)) ; do : ; done

# inserting comments into string of commands
command ; command ; : we need a comment in here for some reason ; command

# an alias for `true' (lazy programming)
while : ; do command ; done

Wydaje mi się, że tak naprawdę szukam historycznej aplikacji.

amfetamachina
źródło
69
@Caleb - zapytałem o to dwa lata wcześniej.
amfetamachina
Nie powiedziałbym polecenia, które zwraca określoną wartość „nic nie robi”. Chyba że programowanie funkcjonalne polega na „nic nie robieniu”. :-)
LarsH

Odpowiedzi:

415

Historycznie , muszle Bourne nie miał truei falsejak wbudowanych komend. truezamiast tego został po prostu alias do :i falsedo czegoś podobnego let 0.

:jest nieco lepszy niż w trueprzypadku przenoszenia do starożytnych pocisków pochodzących z Bourne'a. Jako prosty przykład rozważmy brak !operatora potoku i operatora ||listy (tak jak w przypadku niektórych starożytnych powłok Bourne'a). Pozostawia to elseklauzulę ifinstrukcji jako jedyny sposób na rozgałęzienie na podstawie statusu wyjścia:

if command; then :; else ...; fi

Ponieważ ifwymaga niepustej thenklauzuli, a komentarze nie liczą się jako niepuste, :służy jako brak opcji.

W dzisiejszych czasach (czyli: w nowoczesnym kontekście) zazwyczaj można użyć jednej :lub true. Oba są określone przez POSIX, a niektóre są truełatwiejsze do odczytania. Jest jednak jedna interesująca różnica: :jest to tak zwany POSIX specjalne wbudowane , podczas gdy truejest to normalne wbudowane .

  • Specjalne wbudowane elementy muszą być wbudowane w powłokę; Zwykłe wbudowane są tylko „typowo” wbudowane, ale nie jest to ściśle gwarantowane. Zwykle nie powinno być zwykłego programu o nazwie :z funkcją truePATH większości systemów.

  • Prawdopodobnie najważniejszą różnicą jest to, że przy specjalnych wbudowanych dowolnych zmiennych ustawionych przez wbudowane - nawet w środowisku podczas prostej oceny polecenia - pozostaje po zakończeniu polecenia, jak pokazano tutaj za pomocą ksh93:

    $ unset x; ( x=hi :; echo "$x" )
    hi
    $ ( x=hi true; echo "$x" )
    
    $

    Zauważ, że Zsh ignoruje to wymaganie, podobnie jak GNU Bash, z wyjątkiem pracy w trybie kompatybilności z POSIX, ale wszystkie inne ważne powłoki „pochodnej sh POSIX” przestrzegają tego, włączając dash, ksh93 i mksh.

  • Kolejna różnica polega na tym, że regularne wbudowane elementy muszą być kompatybilne exec- zademonstrowane tutaj za pomocą Bash:

    $ ( exec : )
    -bash: exec: :: not found
    $ ( exec true )
    $
  • POSIX również wyraźnie zauważa, że :może być szybszy niż true, choć jest to oczywiście szczegół specyficzny dla implementacji.

hrabia
źródło
Czy chodziło Ci o to, że zwykłe wbudowane elementy nie mogą być kompatybilne exec?
Old Pro
1
@OldPro: Nie, ma rację, ponieważ truejest to zwykłe narzędzie wbudowane, ale nie execma /bin/trueracji, że używa zamiast niego.
Wstrzymano do odwołania.
1
@DennisWilliamson Właśnie szedłem drogą, w której sformułowano specyfikację. Sugeruje to oczywiście, że regularne wbudowane wersje powinny również zawierać wersję autonomiczną.
ormaaj
17
+1 Doskonała odpowiedź. Nadal chciałbym zwrócić uwagę na użycie do inicjowania zmiennych, jak : ${var?not initialized}i in.
potrójny
Mniej lub bardziej niepowiązane działania następcze. Powiedziałeś, że :jest to wbudowana funkcja specjalna i nie powinna mieć nazwanej funkcji. Ale czy nie jest najczęściej spotykanym przykładem :(){ :|: & };:nazewnictwa bomby nazewniczej funkcji o nazwie :?
Chong
63

Używam go do łatwego włączania / wyłączania poleceń zmiennych:

#!/bin/bash
if [[ "$VERBOSE" == "" || "$VERBOSE" == "0" ]]; then
    vecho=":"     # no "verbose echo"
else
    vecho=echo    # enable "verbose echo"
fi

$vecho "Verbose echo is ON"

A zatem

$ ./vecho
$ VERBOSE=1 ./vecho
Verbose echo is ON

To sprawia, że ​​skrypt jest czysty. Nie można tego zrobić za pomocą „#”.

Również,

: >afile

jest jednym z najprostszych sposobów zagwarantowania, że ​​„afile” istnieje, ale ma zerową długość.

Kevin Little
źródło
20
>afilejest jeszcze prostszy i osiąga ten sam efekt.
hrabia
2
Fajnie, wykorzystam tę sztuczkę $ vecho do uproszczenia skryptów, które prowadzę.
BarneySchmale,
5
Jaka jest korzyść z cytowania jelita grubego vecho=":"? Tylko dla czytelności?
leoj
56

Przydatna aplikacja dla: jest, jeśli interesuje Cię tylko stosowanie rozszerzeń parametrów do ich skutków ubocznych, a nie przekazywanie ich wyników do polecenia. W takim przypadku używasz PE jako argumentu: albo false, w zależności od tego, czy chcesz mieć status wyjścia 0 czy 1. Przykładem może być : "${var:=$1}". Ponieważ :jest wbudowany, powinien być dość szybki.

ormaaj
źródło
2
Możesz go również użyć do efektów ubocznych rozszerzania arytmetycznego: : $((a += 1))( ++i --operatory nie muszą być implementowane zgodnie z POSIX.). W bash, ksh i ewentualnych innych powłokach możesz także: ((a += 1))lub, ((a++))ale nie jest to określone przez POSIX.
pabouk
@pabouk Tak, to wszystko prawda, ale (())jest określona jako funkcja opcjonalna. „Jeśli sekwencja znaków rozpoczynająca się od„ ((”zostanie przeanalizowana przez powłokę jako rozszerzenie arytmetyczne, jeśli poprzedzona przez„ $ ”, powłoki, które implementują rozszerzenie, według których„ ((wyrażenie)) ”jest oceniane jako wyrażenie arytmetyczne może traktować „((jako wprowadzenie jako obliczenie arytmetyczne zamiast polecenia grupowania.”
ormaaj
50

:może być również dla komentarza blokowego (podobnie jak / * * / w języku C). Na przykład, jeśli chcesz pominąć blok kodu w skrypcie, możesz to zrobić:

: << 'SKIP'

your code block here

SKIP
zagpoint
źródło
3
Kiepski pomysł. Wszelkie zamiany poleceń w niniejszym dokumencie są nadal przetwarzane.
chepner
33
Niezły pomysł. Możesz uniknąć zmiennej rozdzielczości / podstawiania w tych dokumentach, cytując separator: << << SKIP '
Rondo
1
IIRC można też \ uciec którykolwiek z separatorów dla tego samego efektu: : <<\SKIP.
yyny
@zagpoint Czy to właśnie w tym miejscu Python korzysta z dokumentów jako komentarzy wielowierszowych?
Sapphire_Brick
31

Jeśli chcesz skrócić plik do zera bajtów, co jest przydatne do czyszczenia dzienników, spróbuj tego:

:> file.log
Tuńczyk Ahi
źródło
16
> file.logjest prostszy i osiąga ten sam efekt.
amfetamachina
55
Tak, ale radosna buzia to dla mnie to:>
Ahi Tuna,
23
@amphetamachine: :>jest bardziej przenośny. Niektóre powłoki (takie jak moja zsh) automatycznie tworzą instancję kota w bieżącej powłoce i nasłuchują standardowego wejścia po otrzymaniu przekierowania bez polecenia. Zamiast cat /dev/null, :jest o wiele prostsze. Często takie zachowanie jest inne w interaktywnych powłokach niż w skryptach, ale jeśli napiszesz skrypt w sposób, który działa również interaktywnie, debugowanie metodą kopiuj-wklej jest znacznie łatwiejsze.
Caleb
2
Czym : > fileróżni się true > file(oprócz liczby postaci i szczęśliwej twarzy) nowoczesną powłoką (zakładając :i truesą równie szybkie)?
Adam Katz,
30

Jest podobny do passPythona.

Jednym z zastosowań byłoby wykasowanie funkcji, dopóki nie zostanie napisana:

future_function () { :; }
Wstrzymano do odwołania.
źródło
29

Dwa kolejne zastosowania niewymienione w innych odpowiedziach:

Logowanie

Weź ten przykładowy skrypt:

set -x
: Logging message here
example_command

W pierwszym wierszu set -xpowłoka wypisuje polecenie przed jego uruchomieniem. To całkiem przydatna konstrukcja. Minusem jest to, że zwykły echo Log messagetyp instrukcji drukuje teraz wiadomość dwukrotnie. Metoda jelita grubego omija to. Pamiętaj, że nadal będziesz musiał uciec od znaków specjalnych, tak jak byś to zrobił echo.

Nazwy stanowisk Cron

Widziałem, że jest używany w zadaniach crona, takich jak to:

45 10 * * * : Backup for database ; /opt/backup.sh

To zadanie crona, które uruchamia skrypt /opt/backup.shcodziennie o 10:45. Zaletą tej techniki jest to, że poprawia wygląd tematów wiadomości e-mail, gdy /opt/backup.shdrukuje jakieś dane wyjściowe.

Flimm
źródło
Gdzie jest domyślna lokalizacja dziennika? Czy mogę ustawić lokalizację dziennika? Czy więcej służy celowi tworzenia wyjścia na standardowym wyjściu podczas skryptów / procesów w tle?
domdambrogia
1
@domdambrogia Podczas używania set -xwydrukowane polecenia (w tym coś podobnego : foobar) przechodzą do stderr.
Flimm
22

Możesz użyć go w połączeniu z backticks ( ``) do wykonania polecenia bez wyświetlania jego wyniku, jak poniżej:

: `some_command`

Oczywiście możesz po prostu to zrobić some_command > /dev/null, ale :-wersja jest nieco krótsza.

Biorąc to pod uwagę, nie polecałbym tego robić, ponieważ po prostu wprowadzałoby to w błąd ludzi. To właśnie przyszło mi do głowy jako możliwy przypadek użycia.

sepp2k
źródło
25
Nie jest to bezpieczne, jeśli polecenie zrzuci kilka megabajtów danych wyjściowych, ponieważ powłoka buforuje dane wyjściowe, a następnie przekazuje je jako argumenty wiersza poleceń (przestrzeń stosu) do „:”.
Juliano,
15

Jest także przydatny w przypadku programów poliglotycznych:

#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "$@"
~function(){ ... }

To jest teraz zarówno plik wykonywalny skrypt powłoki i program JavaScript: co oznacza ./filename.js, sh filename.jsi node filename.jswszystkie prace.

(Zdecydowanie trochę dziwne użycie, ale mimo to skuteczne.)


Niektóre wyjaśnienia, zgodnie z prośbą:

  • Skrypty powłoki są oceniane linia po linii; a execpolecenie po uruchomieniu kończy powłokę i zastępuje jej proces wynikową komendą. Oznacza to, że dla powłoki program wygląda następująco:

    #!/usr/bin/env sh
    ':' //; exec "$(command -v node)" "$0" "$@"
  • Dopóki nie występuje rozwinięcie parametru ani aliasing w słowie, każde słowo w skrypcie powłoki można zawijać w cudzysłów bez zmiany jego znaczenia; oznacza to, że ':'jest to równoważne :(umieściliśmy to tutaj w cudzysłowie, aby uzyskać semantykę JavaScript opisaną poniżej)

  • ... i jak opisano powyżej, pierwsze polecenie w pierwszym wierszu to no-op (przekłada się na : //, lub jeśli wolisz zacytować słowa,. ':' '//'Zwróć uwagę, że //nie ma tutaj specjalnego znaczenia, tak jak w JavaScript; to tylko bezsensowne słowo, które jest wyrzucane).

  • Wreszcie drugie polecenie w pierwszym wierszu (po średniku) jest prawdziwym mięsem programu: jest to execwywołanie, które zastępuje wywoływany skrypt powłoki , a proces Node.js jest wywoływany w celu oceny reszty skryptu.

  • Tymczasem pierwsza linia w JavaScript analizuje jako literał-ciąg ( ':'), a następnie komentarz, który jest usuwany; tak więc do JavaScript program wygląda następująco:

    ':'
    ~function(){ ... }

    Ponieważ literał-ciąg jest sam w wierszu, jest to instrukcja no-op, a zatem jest usuwana z programu; oznacza to, że cała linia jest usuwana, pozostawiając tylko kod programu (w tym przykładzie function(){ ... }treść).

ELLIOTTCABLE
źródło
Witaj, czy możesz wyjaśnić, co : //;i ~function(){}robić? Dziękuję:)
Stphane,
1
@Stphane Dodano podział! Co do ~function(){}, jest to trochę bardziej skomplikowane. Jest tu kilka innych odpowiedzi, które go dotykają, chociaż żadne z nich tak naprawdę nie wyjaśnia tego z moją satysfakcją… jeśli żadne z tych pytań nie wyjaśni ci wystarczająco dobrze, możesz opublikować je jako pytanie tutaj, będę chętnie udzielimy szczegółowych odpowiedzi na nowe pytanie.
ELLIOTTCABLE
1
Nie zwracałem uwagi na node. Więc część funkcji dotyczy javascript! Nie mam nic przeciwko jednemu operatorowi przed IIFE. Pomyślałem, że to też była bash i tak naprawdę nie zrozumiałem znaczenia twojego postu. Teraz czuję się dobrze, dziękuję za poświęcony czas na dodanie „podziału”!
Stphane
~{ No problem. (= }
ELLIOTTCABLE
12

Funkcje samo dokumentowania

Możesz także użyć : do osadzenia dokumentacji w funkcji.

Załóżmy, że masz skrypt biblioteczny mylib.shzapewniający różne funkcje. Możesz albo pobrać bibliotekę ( . mylib.sh) i wywołać funkcje bezpośrednio po niej ( lib_function1 arg1 arg2), albo uniknąć zaśmiecania przestrzeni nazw i wywołać bibliotekę argumentem funkcji (mylib.sh lib_function1 arg1 arg2 ).

Czy nie byłoby miło, gdybyś mógł także wpisać mylib.sh --helpi uzyskać listę dostępnych funkcji i ich użycia, bez konieczności ręcznego utrzymywania listy funkcji w tekście pomocy?

#! / bin / bash

# wszystkie funkcje „publiczne” muszą zaczynać się od tego prefiksu
LIB_PREFIX = „lib_”

# funkcje biblioteki „publicznej”
lib_function1 () {
    : Ta funkcja robi coś skomplikowanego z dwoma argumentami.
    :
    : Parametry:
    : „arg1 - pierwszy argument (1 $)”
    : „arg2 - drugi argument”
    :
    : Wynik:
    : " to skomplikowane"

    # rzeczywisty kod funkcji zaczyna się tutaj
}

lib_function2 () {
    : Dokumentacja funkcji

    # kod funkcji tutaj
}

# funkcja pomocy
--Wsparcie() {
    echo MyLib v0.0.1
    Echo
    echo Sposób użycia: mylib.sh [nazwa_funkcji [argumenty]]
    Echo
    echo Dostępne funkcje:
    deklaruj -f | sed -n -e '/ ^' $ LIB_PREFIX '/, / ^} $ / {/ \ (^' $ LIB_PREFIX '\) \ | \ (^ [\ t] *: \) / {
        s / ^ \ ('$ LIB_PREFIX'. * \) () / \ n === \ 1 === /; s / ^ [\ t] *: \? ['\' '"] \? / / ; s / ['\' '"]??; \? $ //; p}}'
}

# kod główny
if ["$ {BASH_SOURCE [0]}" = "$ {0}"]; następnie
    # skrypt został wykonany zamiast źródła
    # wywołaj żądaną funkcję lub wyświetl pomoc
    if ["$ (type -t -" $ 1 "2> / dev / null)" = function]; następnie
        „$ @”
    jeszcze
        --Wsparcie
    fi
fi

Kilka komentarzy na temat kodu:

  1. Wszystkie funkcje „publiczne” mają ten sam prefiks. Tylko te mają być wywoływane przez użytkownika i wymienione w tekście pomocy.
  2. Funkcja auto-dokumentowania opiera się na poprzednim punkcie i używa declare -fdo wyliczenia wszystkich dostępnych funkcji, a następnie filtruje je przez sed, aby wyświetlać tylko funkcje z odpowiednim prefiksem.
  3. Dobrym pomysłem jest zawarcie dokumentacji w pojedynczych cudzysłowach, aby zapobiec niepożądanemu rozszerzaniu i usuwaniu białych znaków. Musisz także zachować ostrożność, używając apostrofów / cytatów w tekście.
  4. Możesz napisać kod w celu internalizacji prefiksu biblioteki, tzn. Użytkownik musi tylko wpisać mylib.sh function1i zostanie on przetłumaczony wewnętrznie na lib_function1. To ćwiczenie pozostawiono czytelnikowi.
  5. Funkcja pomocy nosi nazwę „--help”. Jest to wygodne (tj. Leniwe) podejście, które wykorzystuje mechanizm wywoływania biblioteki, aby wyświetlić samą pomoc, bez konieczności dodatkowego kodowania $1. Jednocześnie zaśmieci twoją przestrzeń nazw, jeśli pozyskasz bibliotekę. Jeśli ci się to nie podoba, możesz zmienić nazwę na coś podobnego lib_helplub faktycznie sprawdzić argumenty --helpw głównym kodzie i ręcznie wywołać funkcję pomocy.
Sir Athos
źródło
4

Widziałem to użycie w skrypcie i pomyślałem, że to dobry zamiennik dla wywoływania basename w skrypcie.

oldIFS=$IFS  
IFS=/  
for basetool in $0 ; do : ; done  
IFS=$oldIFS  

... jest to zamiennik kodu: basetool=$(basename $0)

Griff Derryberry
źródło
Wolębasetool=${0##*/}
Amit Naidu