Po kilku bardzo szybkich badaniach wydaje się, że Bash jest kompletnym językiem Turinga .
Zastanawiam się, dlaczego Bash jest używany prawie wyłącznie do pisania stosunkowo prostych skryptów? Ponieważ powłoka Bash jest dostarczana z Linuksem, możesz uruchamiać skrypty powłoki bez zewnętrznego interpretera lub kompilatora, zgodnie z wymaganiami innych popularnych języków komputerowych. Jest to ogromna zaleta, która w niektórych przypadkach może zrekompensować przeciętność samego języka.
Czy istnieje limit, jak złożone mogą być takie programy? Czy czysty Bash służy do pisania skomplikowanych programów? Czy można napisać, powiedzmy, kompresor / dekompresor plików w czystym Bash? Kompilator? Prosta gra wideo?
Czy jest tak rzadko używany tylko dlatego, że istnieją tylko bardzo ograniczone narzędzia do debugowania?
źródło
sh
Skryptconfigure
, który jest używany jako część procesu kompilacji dla bardzo wielu UN * X pakietów nie jest „stosunkowo proste”.m4
makr.configure
skrypty są również powolne, wykonują całą masę bezużytecznej pracy i są przedmiotem zabawnych rantów. Oczywiście powłoki można używać do dużych programów, ale znowu ludzie stworzyli komputery z Conway's Game of Life and Minecraft, a także języków programowania, takich jak Brainf ** k i Hexagony . Najwyraźniej niektórzy ludzie lubią budować coś z naprawdę małych i mylących atomów. Możesz nawet sprzedawać gry z tym pomysłem ...Odpowiedzi:
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.
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
iksh93
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 - inaczejsource
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 jakextern
lubstatic
. 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.
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 jegoconfigure
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.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.
„Pure” Bash, bez żadnych wezwań do rzeczy w
PATH
? Kompresor jest prawdopodobnie wykonalny przy użyciuecho
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łaniuod
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 wPATH
, 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.
Oto Tetris w skorupce . Inne tego typu gry są dostępne, jeśli szukasz.
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
iset -x
, które razem wystarczają do debugowania bardzo wielu problemów.źródło
&
możesz uruchamiać procesy równolegle. Możesz wykonaćwait
procesy potomne. Możesz skonfigurować potoki i bardziej złożone sieci potoków, używając nazwanych potoków. Co najważniejsze, przetwarzanie równoległe we właściwy sposób jest proste, z bardzo małym kodem wzorcowym i unikając ryzyka i trudności związanych z wielowątkowością pamięci współużytkowanej.Możemy chodzić lub pływać w dowolnym miejscu, więc dlaczego zawracamy sobie głowę rowerami, samochodami, pociągami, łodziami, samolotami i innymi pojazdami? Oczywiście chodzenie lub pływanie może być męczące, ale ogromną zaletą jest brak konieczności posiadania dodatkowego sprzętu.
Po pierwsze, chociaż bash jest kompletny w Turinga, nie jest dobry w manipulowaniu danymi innymi niż liczby całkowite (niezbyt duże), łańcuchy, (jednowymiarowe) tablice łańcuchów i skończone mapy od łańcuchów do łańcuchów. Każdy inny rodzaj danych wymaga uciążliwego kodowania, co utrudnia pisanie programu i często narzuca wydajność, która nie jest wystarczająco dobra w praktyce. Na przykład operacje zmiennoprzecinkowe w bash są trudne i powolne.
Ponadto bash ma bardzo niewiele sposobów interakcji ze środowiskiem. Może uruchamiać procesy, może wykonywać kilka prostych rodzajów dostępu do plików (poprzez przekierowanie) i to wszystko. Bash ma również klienta sieciowego po stronie klienta. Bash może łatwo emitować null bajty (
printf \\0
), ale nie może parsować bajtów null na wejściu, co czyni go nieodpowiednim do odczytu danych binarnych. Bash nie może bezpośrednio robić innych rzeczy: musi do tego wywoływać programy zewnętrzne. I to jest w porządku: powłoki są zaprojektowane głównie w celu uruchamiania zewnętrznych programów! Powłoki są językiem kleju do łączenia programów. Ale jeśli korzystasz z programu zewnętrznego, oznacza to, że program musi być dostępny - a następnie zmniejszasz zaletę przenośności:).Bash nie ma żadnej funkcji, która ułatwiałaby pisanie solidnych programów
set -e
. Nie ma (użytecznych) typów, przestrzeni nazw, modułów ani zagnieżdżonych struktur danych. Błędy są największą trudnością w programowaniu; podczas gdy łatwość pisania programów wolnych od błędów nie zawsze jest decydującym czynnikiem przy wyborze języka, bash jest pod tym względem źle oceniany. Bash również słabo plasuje się pod względem wydajności podczas robienia rzeczy innych niż łączenie programów razem.Przez długi czas bash nie działał w systemie Windows i nawet dzisiaj nie jest obecny w domyślnej instalacji systemu Windows i nie działa w pełni natywnie (nawet w WSL) w tym sensie, że nie ma interfejsów do Natywne funkcje systemu Windows. Bash nie działa na iOS i nie jest domyślnie instalowany na Androidzie. Więc jeśli nie piszesz aplikacji tylko dla Uniksa, bash wcale nie jest przenośny.
Wymaganie kompilatora nie stanowi problemu w zakresie przenośności. Kompilator działa na komputerze programisty. Wymaganie interpretera lub bibliotek stron trzecich może być problemem, ale pod Linuksem jest to rozwiązany problem dzięki pakietom dystrybucyjnym, a pod Windows, Android i iOS ludzie zazwyczaj pakują komponenty innych firm w pakiet aplikacji. Tak więc rozważane przez ciebie problemy związane z przenośnością nie są praktycznymi problemami w aplikacjach typu the-the-mill.
Moja odpowiedź dotyczy powłok innych niż bash. Kilka szczegółów różni się w zależności od powłoki, ale ogólny pomysł jest taki sam.
źródło
Niektóre powody, dla których nie należy używać skryptów powłoki dla dużych programów, tuż przy mojej głowie:
mkdir
lubgrep
wewnętrznie.zsh
ma pewne wsparcie. Dzieje się tak również dlatego, że interfejs programów zewnętrznych jest w większości oparty na tekście i\0
służy jako separator.bash -c ...
lubssh -c ...
)źródło