Jak skomplikowany może być program napisany w czystej wersji Bash? [Zamknięte]

17

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?

Bregalad
źródło
2
shSkrypt configure, który jest używany jako część procesu kompilacji dla bardzo wielu UN * X pakietów nie jest „stosunkowo proste”.
user4556274
@ user4556274 Nie jest, ale zwykle nie jest pisany ręcznie, ale z obszernego zestawu m4makr.
Kusalananda
2
W Bash znajduje się asembler x86 , więc tak, Bash jest czasami używany do pisania skomplikowanych programów. Dlaczego ludzie nie robią tego częściej? Być może dlatego, że interpreter jest również powolny, gówniany i podatny na „ciekawe” błędy (patrz fi Shellshock ). Ponadto skrypty Bash stają się coraz trudniejsze do utrzymania wraz z rozmiarem. Spójrz na asembler powyżej; czy możesz powiedzieć ze źródła, czy jest zgodne ze składnią AT&T czy Intel?
Satō Katsura
configureskrypty 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 ...
ilkkachu
Czy na to pytanie można odpowiedzieć, czy nie? Odkładają go i mówią, że nie da się odpowiedzieć, ale dostaję kilka świetnych odpowiedzi. Byłoby miło być spójnym, ponieważ jestem nowy w tym SE, aby skierować mnie do tego, jakie pytania są i nie są pożądane w tym SE.
Bregalad,

Odpowiedzi:

30

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 configureskrypty generowane przez GNU Autoconf - są nietypowe z wielu powodów:

  1. 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 exprlubbc 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, zshi ksh93wszystkie 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” ksh93lub cokolwiek innego, to dlaczego nie „po prostu” zainstalować Perla, Pythona lub Ruby? W wielu przypadkach jest to niedopuszczalne; wartości domyślne mają znaczenie.

  2. Ż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 sourcew 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 #includeinstrukcje. Taki dialekt C-lite nie potrzebuje słów kluczowych takich jak externlub static. Te funkcje istnieją, aby umożliwić modułowość.

  3. 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, ksh93izsh 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.

  4. 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.

  5. Skrypty powłoki mają niewiele wbudowanych możliwości zwiększania wydajności poprzez równoległe wykonywanie.

    Powłoka Bourne'a mieć &, waitoraz 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 -Pi 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. configurePonownie biorąc ten przykład skryptu GNU Autoconf , podwojenie liczby rdzeni w systemie niewiele zrobi, aby poprawić szybkość, z jaką działa.

  6. 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 configurewyników: tak, aby jego configureskrypty 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 m4i 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 echosekwencji 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 odi 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 echoi set -x, które razem wystarczają do debugowania bardzo wielu problemów.

Warren Young
źródło
2
„Skrypty powłoki mają niewiele wbudowanych możliwości wykonywania równoległego”. Moim zdaniem powłoka ma lepszą obsługę przetwarzania równoległego niż większość innych języków. Za pomocą jednego znaku &możesz uruchamiać procesy równolegle. Możesz wykonać waitprocesy 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.
Sam Watkins,
@SamWatkins: Zaktualizowałem punkt 5 powyżej, aby odpowiedzieć na twoją odpowiedź. Chociaż ja też jestem fanem przekazywania komunikatów między oddzielnymi procesami, aby uniknąć wielu problemów związanych z równoległością pamięci współużytkowanej, chodzi mi tutaj o zwiększenie wydajności, a nie o kompozycyjność itp. często wymaga równoległości pamięci współużytkowanej.
Warren Young,
Skrypty powłoki nadają się do tworzenia prototypów - ale ostatecznie projekt powinien przejść do właściwego języka programowania, a najlepiej języka skompilowanego. Następnie w skrajnych przypadkach montaż, tak jak w przypadku projektu FFmpeg. Cmake jest dobrym przykładem tego, co powinno się stać z Autotools - jest napisany w C i nie wymaga Perla, Texinfo lub M4. To trochę zawstydzające, że Autotools nadal tak bardzo polega na skryptach powłoki po 30 latach wikipedia.org/wiki/GNU_Build_System#Criticism
Steven Penny
9

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.

Gilles „SO- przestań być zły”
źródło
1
Uważam, że o przenośności mówi się dość często, nie jestem pewien, czy użyłbym tego konkretnego elementu jako negatywnego, ponieważ dotyczy on także większości innych języków, w tym Java. Nawet PHP działające na serwerze Windows w porównaniu z serwerem * nix ma pewne małe różnice, o których zawsze musisz pamiętać, jeśli jesteś wystarczająco głupi, aby uruchomić cokolwiek na serwerze Windows, to znaczy. Wiele rzeczy nie działa na Androidzie i iOS, więc nie jestem pewien, jak to może być poprawny komentarz.
Lizardx
7

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:

  • Większość funkcji jest wykonywana przez rozwiązywanie zewnętrznych poleceń, co jest powolne. Natomiast języki programowania, takie jak Perl, mogą działać w sposób równoważny mkdirlub grepwewnętrznie.
  • Nie ma łatwego sposobu na uzyskanie dostępu do bibliotek C lub wykonywanie bezpośrednich wywołań systemowych, co oznacza, że ​​np. Trudno byłoby stworzyć grę wideo
  • Właściwe języki programowania mają lepszą obsługę złożonych struktur danych. Chociaż Bash ma tablice i tablice asocjacyjne, ale nie chciałbym myśleć o połączonej liście lub drzewie.
  • Powłoka służy do przetwarzania poleceń wykonanych w przypadku tekstu. Dane binarne (to znaczy zmienne zawierające bajty NUL (bajty o wartości zero)) są trudne do obsługi. Zależy trochę od powłoki, zshma pewne wsparcie. Dzieje się tak również dlatego, że interfejs programów zewnętrznych jest w większości oparty na tekście i \0służy jako separator.
  • Również z powodu poleceń zewnętrznych oddzielenie kodu od danych jest nieco trudne. Zobacz wszystkie problemy, jakie występują podczas cytowania danych w innej powłoce (np. Podczas uruchamiania bash -c ...lub ssh -c ...)
ilkkachu
źródło
Jest to dla mnie najdokładniejszy zestaw negatywów, ponieważ ktoś, kto wykonuje wiele dużych skryptów bash, byłyby to z grubsza to, co wymieniałbym również jako negatywy. Jednak jedną rzeczą, którą znalazłem, jest to, że Bash nie jest tak dużo wolniejszy niż inne skompilowane języki przy porównywaniu podobnych funkcji. Mam podejrzane podejrzenia, że ​​gdybym próbował napisać niektóre z bardziej skomplikowanych rzeczy, które mam w boksie w pythonie, różnica prędkości nie sprawiłaby, że ta monstrualna praca była tego warta. Jednak sam Bash uważam za zbyt ograniczony, ale Bash + gawk działa dobrze, gawk jest prawie prawdziwy.
Lizardx