wydaje się, że Bash jest kompletnym językiem Turinga
Pojęcie kompletności Turinga jest całkowicie odrębne od wielu innych pojęć przydatnych w języku programowania dużych : użyteczność, ekspresja, zrozumiałość, szybkość itp.
Jeśli Turing-kompletność wszyscy mamy potrzeby, nie mielibyśmy żadnych języków programowania w ogóle , nawet montaż język . Wszyscy programiści komputerowi po prostu piszą w kodzie maszynowym , ponieważ nasze procesory są również wyposażone w Turinga.
dlaczego Bash jest używany prawie wyłącznie do pisania stosunkowo prostych skryptów?
Duże, złożone skrypty powłoki - takie jak configure
skrypty generowane przez GNU Autoconf - są nietypowe z wielu powodów:
Do niedawna nie można było liczyć na to, że wszędzie będzie dostępna powłoka zgodna z POSIX .
Wiele systemów, szczególnie starszych, ma technicznie powłokę kompatybilną z POSIX gdzieś w systemie, ale może nie znajdować się w przewidywalnej lokalizacji /bin/sh
. Jeśli piszesz skrypt powłoki i musi on działać na wielu różnych systemach, to jak piszesz linię shebang ? Jedną z opcji jest skorzystanie z niej/bin/sh
, ale wybierz ograniczenie się do dialektu powłoki Bourne'a sprzed POSIX, na wypadek, gdyby uruchomił się w takim systemie.
Pociski Bourne'a sprzed POSIX-a nawet nie mają wbudowanej arytmetyki; musisz zawołać na expr
lubbc
to zrobić.
Nawet z powłoką POSIX brakuje Ci tablic asocjacyjnych i innych funkcji, których spodziewaliśmy się w językach skryptowych Unix, odkąd Perl stał się popularny na początku lat 90 .
Ten fakt historii oznacza, że istnieje dziesięciolecia tradycji ignorowania wielu zaawansowanych funkcji współczesnych interpreterów skryptów powłoki rodziny Bourne'a tylko dlatego, że nie można liczyć na ich wszędzie.
Wciąż trwa to do dziś: Bash nie uzyskał tablic asocjacyjnych aż do wersji 4 , ale możesz być zaskoczony, ile systemów nadal jest opartych na Bash 3. Apple nadal dostarcza Bash 3 z macOS w 2017 roku - najwyraźniej dla przyczyny licencyjne - a serwery Unix / Linux często działają praktycznie bez zmian przez bardzo długi czas, więc możesz mieć stabilny stary system, w którym nadal działa Bash 3, taki jak CentOS 5. Jeśli masz takie systemy w swoim środowisku, nie możesz używać tablic asocjacyjnych w skryptach powłoki, które muszą na nich działać.
Jeśli odpowiedzią na ten problem jest to, że piszesz tylko skrypty powłoki dla „nowoczesnych” systemów, musisz poradzić sobie z faktem, że ostatnim wspólnym punktem odniesienia dla większości powłok uniksowych jest standard powłoki POSIX , który w dużej mierze pozostaje niezmieniony, ponieważ był wprowadzony w 1989 roku. Istnieje wiele różnych powłok opartych na tym standardzie, ale wszystkie różniły się w różnym stopniu od tego standardu. Aby wziąć tablice asocjacyjne znowu bash
, zsh
i ksh93
wszystkie mają tę cechę, ale istnieje wiele niezgodności wdrożeniowe. Zatem twoim wyborem jest użycie tylko Bash, lub tylko Zsh, lub tylko użycieksh93
.
Jeśli Twoja odpowiedź na ten problem brzmi: „więc po prostu zainstaluj Bash 4” ksh93
lub cokolwiek innego, to dlaczego nie „po prostu” zainstalować Perla, Pythona lub Ruby? W wielu przypadkach jest to niedopuszczalne; wartości domyślne mają znaczenie.
Żaden z modułów obsługi skryptów powłoki rodziny Bourne'a nie jest obsługiwany .
Najbliżej systemu modułów w skrypcie powłoki można znaleźć .
polecenie - inaczej source
w nowszych wariantach powłoki Bourne'a - które zawodzi na wielu poziomach względem odpowiedniego systemu modułów, z których najbardziej podstawowym jest przestrzeń nazw .
Niezależnie od języka programowania ludzkie zrozumienie zaczyna oznaczać, gdy jakikolwiek pojedynczy plik w większym ogólnym programie przekracza kilka tysięcy wierszy. Głównym powodem, dla którego dzielimy duże programy na wiele plików, jest to, że możemy streścić ich treść w jednym lub dwóch zdaniach. Plik A to analizator składający się z wiersza poleceń, plik B to pompa sieciowa we / wy, plik C to podkładka między biblioteką Z a resztą programu itp. Gdy jedyną metodą łączenia wielu plików w jeden program jest włączenie tekstu , nakładasz limit na to, jak duże mogą być Twoje programy.
Dla porównania byłoby tak, jakby język programowania C nie miał linkera, tylko #include
instrukcje. Taki dialekt C-lite nie potrzebuje słów kluczowych takich jak extern
lub static
. Te funkcje istnieją, aby umożliwić modułowość.
POSIX nie definiuje sposobu określania zakresu zmiennych dla funkcji skryptu z pojedynczą powłoką, a tym bardziej do pliku.
Dzięki temu wszystkie zmienne stają się globalne , co ponownie szkodzi modułowości i kompozycyjności.
Istnieją rozwiązania tego w muszli po POSIX - z pewnością w bash
, ksh93
izsh
przynajmniej - ale to tylko przynosi powrót do punktu 1 powyżej.
Możesz zobaczyć wpływ tego przewodnika po stylu na pisanie makr GNU Autoconf, tam gdzie jest to zalecane poprzedzanie nazw zmiennych nazwą samego makra, co prowadzi do bardzo długich nazw zmiennych wyłącznie w celu zmniejszenia prawdopodobieństwa kolizji do akceptowalnie bliskiego zero.
Nawet C jest lepszy pod tym względem o milę. Większość programów w C jest napisana głównie ze zmiennymi lokalnymi dla funkcji, C obsługuje także zakresy bloków, umożliwiając wielu blokom w obrębie jednej funkcji ponowne użycie nazw zmiennych bez zanieczyszczenia krzyżowego.
Języki programowania powłoki nie mają standardowej biblioteki.
Można argumentować, że standardowa biblioteka języka skryptów powłoki jest zawartością PATH
, ale to po prostu mówi, że aby cokolwiek z tego wynikło, skrypt powłoki musi wywołać inny program, prawdopodobnie napisany w mocniejszym języku, aby zaczynać się.
Nie ma też powszechnie używanego archiwum bibliotek narzędzi powłoki, podobnie jak CPAN Perla . Bez dużej dostępnej biblioteki kodu narzędzia innej firmy programista musi pisać więcej kodu ręcznie, aby była mniej produktywna.
Nawet ignorując fakt, że większość skryptów powłoki opiera się na programach zewnętrznych zwykle napisanych w C, aby wykonać cokolwiek pożytecznego, istnieje narzut na wszystkie te pipe()
→ fork()
→ exec()
łańcuchy połączeń. Ten wzorzec jest dość wydajny w Uniksie, w porównaniu do IPC i uruchamiania procesów w innych systemach operacyjnych, ale tutaj skutecznie zastępuje to, co zrobiłbyś wywołaniem podprogramu w innym języku skryptowym, który jest o wiele bardziej wydajny. Powoduje to poważne ograniczenie górnej granicy szybkości wykonywania skryptu powłoki.
Skrypty powłoki mają niewiele wbudowanych możliwości zwiększania wydajności poprzez równoległe wykonywanie.
Powłoka Bourne'a mieć &
, wait
oraz rurociągi do tego, ale to w dużej mierze przydatny tylko do komponowania wielu programów, nie do osiągnięcia CPU lub I / O równoległości. Nie jesteś prawdopodobne, aby być w stanie peg rdzenie lub nasycać macierz RAID wyłącznie ze skryptów powłoki, a jeśli nie, prawdopodobnie można osiągnąć znacznie wyższą wydajność w innych językach.
W szczególności rurociągi są słabymi sposobami na zwiększenie wydajności poprzez równoległe wykonywanie. Pozwala tylko dwóm programom działać równolegle, a jeden z dwóch prawdopodobnie zostanie zablokowany na wejściu / wyjściu do lub z drugiego w dowolnym momencie.
Istnieje Dniach Ostatnich sposoby wokół tego, jak xargs -P
i GNUparallel
, ale to tylko nakładanych na punkcie 4 powyżej.
Ponieważ nie ma wbudowanej możliwości pełnego korzystania z systemów wieloprocesorowych, skrypty powłoki zawsze będą działały wolniej niż dobrze napisany program w języku, który może wykorzystywać wszystkie procesory w systemie. configure
Ponownie biorąc ten przykład skryptu GNU Autoconf , podwojenie liczby rdzeni w systemie niewiele zrobi, aby poprawić szybkość, z jaką działa.
Języki skryptów powłoki nie mają wskaźników ani referencji .
Zapobiega to robieniu wielu rzeczy, które można łatwo zrobić w innych językach programowania.
Po pierwsze, niemożność odniesienia się pośrednio do innej struktury danych w pamięci programu oznacza, że jesteś ograniczony do wbudowanych struktur danych . Twoja powłoka może mieć tablice asocjacyjne , ale jak są one realizowane? Istnieje kilka możliwości, każdy z różnych kompromisów: czerwono-czarnych drzew , AVL drzew i stoły hash są najczęściej, ale są inni. Jeśli potrzebujesz innego zestawu kompromisów, utkniesz, ponieważ bez referencji nie masz możliwości ręcznego zrolowania wielu typów zaawansowanych struktur danych. Utkniesz z tym, co otrzymałeś.
Lub może być tak, że potrzebujesz struktury danych, która nie ma nawet odpowiedniej alternatywy wbudowanej w interpreter skryptu powłoki, takiej jak ukierunkowany wykres acykliczny , który może być potrzebny do modelowania wykresu zależności . Programuję od dziesięcioleci, a jedynym sposobem na zrobienie tego w skrypcie powłoki jest nadużycie systemu plików , użycie dowiązań symbolicznych jako fałszywych referencji. Takie rozwiązanie uzyskujesz, gdy polegasz tylko na kompletności Turinga, która nie mówi ci nic o tym, czy rozwiązanie jest eleganckie, szybkie lub łatwe do zrozumienia.
Zaawansowane struktury danych są tylko jednym zastosowaniem wskaźników i referencji. Istnieje wiele innych aplikacji , których po prostu nie można łatwo zrobić w języku skryptowym powłoki rodziny Bourne.
Mógłbym tak dalej i dalej, ale myślę, że tutaj rozumiesz. Mówiąc najprościej, istnieje wiele potężniejszych języków programowania dla systemów typu Unix.
Jest to ogromna zaleta, która w niektórych przypadkach może zrekompensować przeciętność samego języka.
Jasne, i właśnie dlatego GNU Autoconf używa specjalnie ograniczonego podzbioru rodziny skryptowych języków skryptowych Bourne'a do swoich configure
wyników: tak, aby jego configure
skrypty działały prawie wszędzie.
Prawdopodobnie nie znajdziesz większej grupy wierzących w użyteczność pisania w bardzo przenośnym dialekcie powłoki Bourne'a niż twórcy GNU Autoconf, ale ich własne dzieło jest napisane głównie w Perlu, plus niektóre m4
i tylko trochę powłoki scenariusz; tylko wyjście Autoconf jest czystym skryptem powłoki Bourne'a. Jeśli to nie nasuwa pytania, jak użyteczna jest koncepcja „Bourne wszędzie”, nie wiem, co będzie.
Czy istnieje limit, jak złożone mogą być takie programy?
Technicznie rzecz biorąc, nie, jak sugerują obserwacje Turinga.
Ale to nie to samo, co stwierdzenie, że dowolnie duże skrypty powłoki są przyjemne w pisaniu, łatwe do debugowania lub szybkie do wykonania.
Czy można napisać, powiedzmy, kompresor / dekompresor plików w czystym bashu?
„Pure” Bash, bez żadnych wezwań do rzeczy w PATH
? Kompresor jest prawdopodobnie wykonalny przy użyciu echo
sekwencji ucieczki heksadecymalnej, ale byłoby to dość bolesne. Dekompresor może być niemożliwy do zapisu w ten sposób z powodu niemożności obsługi danych binarnych w powłoce . Skończyło się to na wołaniu od
i tłumaczeniu danych binarnych na format tekstowy, natywny sposób obsługi danych przez powłokę.
Kiedy zaczniesz mówić o używaniu skryptów powłoki w sposób, w jaki był przeznaczony, jako kleju do uruchamiania innych programów PATH
, drzwi się otwierają, ponieważ teraz jesteś ograniczony tylko do tego, co można zrobić w innych językach programowania, to znaczy nie mam żadnych ograniczeń. Skrypt powłoki, który wykorzystuje całą swoją moc, wywołując inne programy w PATH
, nie działa tak szybko, jak programy monolityczne napisane w mocniejszych językach, ale działa .
I o to chodzi. Jeśli potrzebujesz szybkiego działania programu lub jeśli musi on być potężny sam w sobie, a nie pożyczać moc od innych, nie piszesz go w powłoce.
Prosta gra wideo?
Oto Tetris w skorupce . Inne tego typu gry są dostępne, jeśli szukasz.
istnieją tylko bardzo ograniczone narzędzia do debugowania
Umieściłbym narzędzie do debugowania na około 20 miejscu na liście funkcji niezbędnych do wsparcia programowania w dużej części. Wiele programistów polega bardziej na printf()
debugowaniu niż na odpowiednich debuggerach, niezależnie od języka.
W powłoce masz echo
i set -x
, które razem wystarczają do debugowania bardzo wielu problemów.
sh
Skryptconfigure
, który jest używany jako część procesu kompilacji dla bardzo wielu UN * X pakietów nie jest „stosunkowo proste”.