W pracy często piszę skrypty bash. Mój przełożony zasugerował, aby cały skrypt był podzielony na funkcje, podobnie jak w poniższym przykładzie:
#!/bin/bash
# Configure variables
declare_variables() {
noun=geese
count=three
}
# Announce something
i_am_foo() {
echo "I am foo"
sleep 0.5
echo "hear me roar!"
}
# Tell a joke
walk_into_bar() {
echo "So these ${count} ${noun} walk into a bar..."
}
# Emulate a pendulum clock for a bit
do_baz() {
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
}
# Establish run order
main() {
declare_variables
i_am_foo
walk_into_bar
do_baz
}
main
Czy istnieje jakiś inny powód, niż „czytelność”, który moim zdaniem mógłby być równie dobrze ustalony z kilkoma dodatkowymi komentarzami i pewnymi odstępami między wierszami?
Czy sprawia, że skrypt działa wydajniej (w rzeczywistości spodziewałbym się czegoś przeciwnego), czy też ułatwia modyfikację kodu poza wyżej wymienionym potencjałem czytelności? Czy to naprawdę tylko preferencje stylistyczne?
Należy pamiętać, że chociaż skrypt nie pokazuje tego dobrze, „kolejność uruchamiania” funkcji w naszych rzeczywistych skryptach jest zwykle bardzo liniowa - walk_into_bar
zależy od rzeczy, które i_am_foo
zostały wykonane, i do_baz
działa na podstawie rzeczy skonfigurowanych przez walk_into_bar
- tak więc możliwość arbitralnej zamiany kolejności uruchamiania nie jest czymś, co ogólnie robilibyśmy. Na przykład, nie będzie nagle chcesz umieścić declare_variables
po walk_into_bar
, to by złamać rzeczy.
Przykładem tego, jak napisałbym powyższy skrypt, jest:
#!/bin/bash
# Configure variables
noun=geese
count=three
# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"
# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."
# Emulate a pendulum clock for a bit
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
źródło
main()
na górze i dodajęmain "$@"
na dole, aby to nazwać. Dzięki temu zobaczysz logikę skryptów wysokiego poziomu po jej otwarciu.local
- zapewnia to zakres zmiennych, co jest niezwykle ważne w każdym nietrywialnym skrypcie.Odpowiedzi:
Zacząłem używać tego samego stylu programowania bash po przeczytaniu wpisu na blogu Kfir Lavi „Defensive Bash Programming” . Podaje kilka dobrych powodów, ale osobiście uważam te najważniejsze:
procedury stają się opisowe: o wiele łatwiej jest dowiedzieć się, co ma robić konkretna część kodu. Zamiast ściany kodu widzisz „Och,
find_log_errors
funkcja odczytuje ten plik dziennika pod kątem błędów”. Porównaj to ze znalezieniem wielu linii awk / grep / sed, które używają boga, wie, jaki typ wyrażenia regularnego w środku długiego skryptu - nie masz pojęcia, co on tam robi, chyba że są komentarze.możesz debugować funkcje, dołączając do
set -x
iset +x
. Gdy już wiesz, że reszta kodu działa poprawnie, możesz użyć tej sztuczki, aby skupić się na debugowaniu tylko tej konkretnej funkcji. Jasne, możesz załączyć części skryptu, ale co, jeśli jest to długa część? Łatwiej jest zrobić coś takiego:użycie drukowania za pomocą
cat <<- EOF . . . EOF
. Użyłem go kilka razy, aby mój kod był bardziej profesjonalny. Ponadtoparse_args()
zgetopts
funkcją jest dość wygodny. Ponownie, pomaga to w czytelności, zamiast popychać wszystko do skryptu jako gigantyczną ścianę tekstu. Wygodne jest również ich ponowne użycie.I oczywiście jest to o wiele bardziej czytelne dla kogoś, kto zna C, Javę lub Vala, ale ma ograniczone doświadczenie bash. Jeśli chodzi o wydajność, nie ma wiele do zrobienia - samo bash nie jest najbardziej wydajnym językiem, a ludzie wolą perl i python, jeśli chodzi o szybkość i wydajność. Możesz
nice
jednak funkcję:W porównaniu do miłego wywoływania w każdym wierszu kodu, zmniejsza to całe pisanie ORAZ może być wygodnie używane, gdy chcesz, aby tylko część skryptu działała z niższym priorytetem.
Uruchamianie funkcji w tle, moim zdaniem, pomaga również, gdy chcesz mieć całą grupę instrukcji do działania w tle.
Niektóre przykłady, w których użyłem tego stylu:
źródło
local
i wywołując wszystko przezmain()
funkcję. To znacznie ułatwia zarządzanie i pozwala uniknąć potencjalnie niechlujnej sytuacji.Czytelność to jedno. Ale modularyzacja to coś więcej niż tylko to. ( Semi-modularyzacja może być bardziej poprawna dla funkcji.)
W funkcjach możesz zachować niektóre zmienne lokalne, co zwiększa niezawodność , zmniejszając ryzyko pomyłki.
Kolejną zaletą funkcji jest możliwość ponownego użycia . Po zakodowaniu funkcji można ją zastosować wiele razy w skrypcie. Możesz także przenieść go na inny skrypt.
Twój kod może być teraz liniowy, ale w przyszłości możesz wejść w sferę wielowątkowości lub wieloprzetwarzania w świecie Bash. Gdy nauczysz się robić rzeczy w funkcjach, będziesz dobrze przygotowany do przejścia do równoległości.
Jeszcze jeden punkt do dodania. Jak zauważa Etsitpab Nioliv w komentarzu poniżej, łatwo jest przekierować z funkcji jako spójnej jednostki. Ale jest jeszcze jeden aspekt przekierowań z funkcjami. Mianowicie przekierowania można ustawić wzdłuż definicji funkcji. Na przykład.:
Teraz wywołania funkcji nie wymagają wyraźnych przekierowań.
Może to oszczędzić wiele powtórzeń, co ponownie zwiększa niezawodność i pomaga utrzymać porządek.
Zobacz też
źródło
source
lub. scriptname.sh
i używać tych funkcji tak, jakby były w nowym skrypcie.W moim komentarzu wspomniałem o trzech zaletach funkcji:
Łatwiej je przetestować i zweryfikować poprawność.
Funkcje mogą być łatwo ponownie wykorzystane (pozyskane) w przyszłych skryptach
Twój szef je lubi.
I nigdy nie lekceważ znaczenia liczby 3.
Chciałbym rozwiązać jeszcze jeden problem:
Aby skorzystać z podziału kodu na funkcje, należy starać się, aby funkcje były jak najbardziej niezależne. Jeśli
walk_into_bar
wymaga zmiennej, która nie jest używana gdzie indziej, to zmienna ta powinna zostać zdefiniowana w lokalizacji lokalnejwalk_into_bar
. Proces dzielenia kodu na funkcje i minimalizowania ich wzajemnych zależności powinien sprawić, że kod będzie wyraźniejszy i prostszy.Idealnie byłoby, gdyby funkcje były łatwe do przetestowania indywidualnie. Jeśli z powodu interakcji nie są one łatwe do przetestowania, oznacza to, że mogą skorzystać z refaktoryzacji.
źródło
;-)
Dzielisz kod na funkcje z tego samego powodu, dla którego robisz to dla C / C ++, Pythona, Perla, Ruby lub innego kodu języka programowania. Głębszym powodem jest abstrakcja - hermetyzujesz zadania niższego poziomu w operacje podstawowe wyższego poziomu (funkcje), abyś nie musiał niepokoić się tym, jak się to robi. W tym samym czasie kod staje się bardziej czytelny (i łatwy do utrzymania), a logika programu staje się bardziej przejrzysta.
Jednak patrząc na twój kod, wydaje mi się dość dziwne mieć funkcję deklarowania zmiennych; to naprawdę sprawia, że podnoszę brew.
źródło
main
funkcji / metodzie?Chociaż całkowicie zgadzam się z możliwością ponownego użycia , czytelnością i delikatnym całowaniem szefów, ale jest jeszcze jedna zaleta funkcji w bash : zmienny zakres . Jak pokazuje LDP :
Nie widzę tego bardzo często w skryptach powłoki w świecie rzeczywistym, ale wydaje się, że to dobry pomysł na bardziej złożone skrypty. Zmniejszenie spójności pomaga uniknąć błędów, gdy blokujesz zmienną oczekiwaną w innej części kodu.
Wielokrotnego użytku często oznacza utworzenie wspólnej biblioteki funkcji i umieszczenie
source
tej biblioteki we wszystkich skryptach. To nie pomoże im biegać szybciej, ale pomoże ci je pisać szybciej.źródło
local
, ale myślę, że większość ludzi piszących skrypty podzielone na funkcje nadal przestrzega zasady projektowania. Usignlocal
tylko utrudnia wprowadzanie błędów.local
udostępnia zmienne dla funkcji i jej potomków, więc naprawdę miło jest mieć zmienną, która może być przekazywana z funkcji A, ale niedostępna dla funkcji B, która może chcieć mieć zmienną o tej samej nazwie, ale o innym celu. To dobrze, aby zdefiniować zakres i, jak powiedział Voo - mniej błędówZupełnie inny powód niż te, które podano już w innych odpowiedziach: jednym z powodów, dla których czasami stosuje się tę technikę, w której jedynym wezwaniem do braku definicji funkcji na najwyższym poziomie jest wywołanie
main
, aby upewnić się, że skrypt nie zrobi nic nieprzyjemnego jeśli skrypt jest obcięty. Skrypt może zostać obcięty, jeśli jest przesyłany strumieniowo z procesu A do procesu B (powłoki), a proces A kończy się z dowolnego powodu, zanim zakończy pisanie całego skryptu. Jest to szczególnie prawdopodobne, jeśli proces A pobierze skrypt ze zdalnego zasobu. Chociaż ze względów bezpieczeństwa nie jest to dobry pomysł, jest to coś, co zostało zrobione, a niektóre skrypty zostały zmodyfikowane, aby przewidzieć problem.źródło
main()
wzorzec jest zwykle w Pythonie, gdzie używa się goif __name__ == '__main__': main()
na końcu pliku.import
na bieżący skrypt bez uruchamianiamain
. Podejrzewam, że podobny strażnik mógłby zostać umieszczony w skrypcie bash.Proces wymaga sekwencji. Większość zadań jest sekwencyjna. Nie ma sensu mieszać się z zamówieniem.
Ale najważniejszą rzeczą w programowaniu - w tym w skryptach - jest testowanie. Testowanie, testowanie, testowanie. Jakie skrypty testowe musisz obecnie zweryfikować poprawność swoich skryptów?
Twój szef próbuje cię od bycia skrypciarzem do bycia programistą. To dobry kierunek, aby wejść. Ludzie, którzy przyjdą po tobie, polubią cię.
ALE. Zawsze pamiętaj o zorientowanych na proces korzeniach. Jeśli sensowne jest uporządkowanie funkcji w kolejności, w jakiej są one zwykle wykonywane, zrób to, przynajmniej w pierwszym przejściu.
Później zobaczysz, że niektóre funkcje obsługują dane wejściowe, inne dane wyjściowe, inne przetwarzają, inne modelują dane i inne manipulują danymi, więc może być mądre grupowanie podobnych metod, być może nawet przenoszenie ich do osobnych plików .
Później możesz zdać sobie sprawę, że napisałeś biblioteki małych funkcji pomocniczych, których używasz w wielu swoich skryptach.
źródło
Komentarze i odstępy nie mogą zbliżyć się do czytelności, jaką mogą pełnić funkcje, co pokażę. Bez funkcji nie widać lasu dla drzew - duże problemy kryją się w wielu liniach szczegółów. Innymi słowy, ludzie nie mogą jednocześnie skupiać się na drobnych szczegółach i dużym obrazie. To może nie być oczywiste w krótkim skrypcie; dopóki jest krótki, może być wystarczająco czytelny. Oprogramowanie staje się jednak większe, a nie mniejsze, a na pewno jest częścią całego systemu oprogramowania Twojej firmy, który z pewnością jest znacznie większy, prawdopodobnie miliony linii.
Zastanów się, czy dałem ci takie instrukcje:
Gdy dotrzesz do połowy, a nawet 5%, zapomnisz, jakie były pierwsze kroki. Nie można było dostrzec większości problemów, ponieważ nie można było zobaczyć lasu dla drzew. Porównaj z funkcjami:
Jest to z pewnością dużo bardziej zrozumiałe, bez względu na to, ile komentarzy możesz umieścić w kolejnej wersji linia po linii. Dzięki temu znacznie bardziej prawdopodobne jest, że zauważysz, że zapomniałeś zrobić kawę i prawdopodobnie zapomniałeś sit_down () na końcu. Kiedy myślisz o szczegółach wyrażeń grep i awk, nie możesz myśleć o dużym obrazie - „co, jeśli nie zrobi się kawy”?
Funkcje przede wszystkim pozwalają zobaczyć duży obraz i zauważyć, że zapomniałeś zrobić kawę (lub że ktoś może preferować herbatę). Innym razem, mając inne zdanie, martwisz się szczegółową implementacją.
Oczywiście są też inne korzyści omówione w innych odpowiedziach. Kolejną korzyścią, która nie została wyraźnie wymieniona w innych odpowiedziach, jest to, że funkcje dają gwarancję, która jest ważna w zapobieganiu i naprawianiu błędów. Jeśli odkryjesz, że jakaś zmienna $ foo w odpowiedniej funkcji walk_to () była błędna, wiesz, że wystarczy spojrzeć na pozostałe 6 wierszy tej funkcji, aby znaleźć wszystko, na co mógł mieć wpływ ten problem, i wszystko, co mogło spowodował, że to źle. Bez (właściwych) funkcji wszystko i wszystko w całym systemie może być przyczyną niepoprawności $ foo, a wszystko i wszystko może mieć wpływ na $ foo. Dlatego nie możesz bezpiecznie naprawić $ foo bez ponownego zbadania każdej linii programu. Jeśli $ foo jest lokalny dla funkcji,
źródło
bash
składnia. Szkoda; Nie sądzę, że istnieje sposób na przekazanie danych wejściowych do takich funkcji. (tj.pour();
<coffee
). Wygląda bardziej jakc++
lubphp
(myślę).Kilka istotnych truizmów dotyczących programowania:
Komentarze zaczynają się od przerwy, ponieważ nie są w stanie jasno wyrazić swoich pomysłów w kodzie *, a wraz ze zmianą stają się gorzej (lub po prostu źle). Dlatego, jeśli to w ogóle możliwe, wyrażaj pojęcia, struktury, rozumowanie, semantykę, przepływ, obsługę błędów i wszystko inne, co ma znaczenie dla zrozumienia kodu jako kodu.
To powiedziawszy, funkcje Bash mają pewne problemy, których nie znaleziono w większości języków:
local
słowa kluczowego powoduje zanieczyszczenie globalnej przestrzeni nazw.local foo="$(bar)"
powoduje utratę kodu wyjściabar
."$@"
oznacza w różnych kontekstach.* Przepraszam, jeśli to obraża, ale po użyciu komentarzy przez kilka lat i rozwijaniu się bez nich ** przez kolejne lata jest całkiem jasne, co jest lepsze.
** Używanie komentarzy do licencjonowania, dokumentacji API i tym podobnych jest nadal konieczne.
źródło
local foo=""
Potem ustawiając je za pomocą wykonanie polecenia do działania w wyniku ...foo="$(bar)" || { echo "bar() failed"; return 1; }
. To szybko wyciąga nas z funkcji, gdy nie można ustawić wymaganej wartości. Nawiasy klamrowe są konieczne, aby zapewnić, żereturn 1
jest wykonywany tylko w przypadku awarii.Czas to pieniądz
Istnieją inne dobre odpowiedzi, które rzucają światło na techniczne przyczyny napisania modułowego skryptu, potencjalnie długiego, opracowanego w środowisku pracy, opracowanego do użytku przez grupę osób i nie tylko na własny użytek.
Chcę się skupić na jednym oczekiwaniu: w środowisku pracy „czas to pieniądz” . Zatem brak błędów i wydajność twojego kodu są oceniane wraz z czytelnością, testowalnością, łatwością konserwacji , refaktowalności , możliwości ponownego użycia ...
Napisanie w „modułach” kodu skróci czas czytania potrzebny nie tylko samemu programistowi , ale nawet czasowi używanemu przez testerów lub szefa. Ponadto zauważ, że czas szefa jest zwykle płacony więcej niż czas programisty i że szef oceni jakość twojej pracy.
Ponadto pisanie w niezależnych „modułach” kodu (nawet skryptu bash) pozwoli ci pracować „równolegle” z innym komponentem twojego zespołu, skracając całkowity czas produkcji i wykorzystując, w najlepszym wypadku, wiedzę specjalistyczną singla, do przeglądu lub przepisania części brak efektu ubocznego na innych, aby przetworzyć kod, który właśnie napisałeś „tak, jak jest”dla innego programu / skryptu, aby utworzyć biblioteki (lub biblioteki fragmentów), aby zmniejszyć ogólny rozmiar i powiązane prawdopodobieństwo błędów, aby debugować i dokładnie przetestować każdą część ... i oczywiście zorganizuje w logicznej sekcji twój program / skrypt i zwiększ jego czytelność. Wszystkie rzeczy, które pozwolą zaoszczędzić czas i pieniądze. Wadą jest to, że musisz trzymać się standardów i komentować swoje funkcje (które jednak musisz robić w środowisku pracy).
Przestrzeganie standardu spowolni pracę na początku, ale później przyspieszy pracę wszystkich innych (i ciebie). Rzeczywiście, gdy współpraca wzrasta, liczba zaangażowanych osób staje się nieuniknioną potrzebą. Na przykład, nawet jeśli uważam, że zmienne globalne muszą być zdefiniowane globalnie, a nie w funkcji, rozumiem standard, który inicjuje je w funkcji o nazwie
declare_variables()
wywoływanej zawsze w pierwszym wierszumain()
jednej ...Na koniec, nie lekceważ możliwości współczesnych edytorów kodu źródłowego do pokazywania lub ukrywania selektywnie oddzielnych procedur ( zwijanie kodu ). Pozwoli to zachować kompaktowy kod i skoncentrować użytkownika na oszczędności czasu.
Powyżej widać, jak rozkłada się tylko
walk_into_bar()
funkcja. Nawet pozostałe z nich miały po 1000 linii każda, nadal można było kontrolować cały kod na jednej stronie. Zauważ, że jest on złożony nawet w sekcji, w której udajesz się zadeklarować / zainicjować zmienne.źródło
Oprócz powodów podanych w innych odpowiedziach:
źródło
Innym często pomijanym powodem jest analiza składni bash:
Ten skrypt oczywiście zawiera błąd składniowy i bash nie powinien go wcale uruchamiać, prawda? Źle.
Gdybyśmy owinęli kod w funkcję, tak by się nie stało:
źródło