Kiedy wraca się do własnego skryptu napisanego kilka miesięcy wcześniej często nie pamięta się już ani jak on działa, ani jakich można użyć argumentów. Pozostaje wtedy otwarcie go w edytorze i przeanalizowanie. Dobrze, jeśli kod został skomentowany, a najlepiej byłoby mieć „help” – tekst pomocy, w którym opisane jest działanie skryptu i możliwe do zastosowania argumenty. Zwykle szkoda jest na to czasu – skrypty pisane są po to, by przyspieszyć pracę, a nie żeby jej sobie dokładać. Ale gdyby tak ten „help” robił się… sam?
Chciałbym zaproponować rozwiązanie, które bardzo mi pomogło przy pracy ze skryptami bashowymi. Jest proste i pozwala łatwo przygotować tekst pomocy dla użytkownika bez zbędnego nakładu pracy.
Swego czasu pisałem dużo skrytpów bashowych. Były różne – od prostych, wykonujących jedną czynność po kilkusetwierszowe, skomplikowane, z wieloma argumentami. Często okazywało się, że jeśli nie używałem ich kilka miesięcy, miałem problem z ich składnią, argumentami itp. Na szczęście mam zwyczaj komentowania kodu więc podgląd w edytorze pozwalał sobie przypomnieć jak działa skrypt i jakich użyć argumentów. Nie jest to jednak najwygodniejsze wyjście, wymaga otwarcia edytora i czasochłonnego szukania w kodzie komentarzy. Wtedy pojawiła się koncepcja autohelpa.
Czym jest autohelp
„Autohelp” – bo jest tworzony automatycznie.
„Autohelp” – bo skrypt w pewnym sensie sam siebie komentuje.
Koncepcja jest następująca: pisząc kod skryptu opatruje się go krótkimi (ale czytelnymi) komentarzami. Po wywołaniu skryptu z argumentem -h
lub --help
(co kto lubi) komentarze te są wyświetlane w postaci tekstu pomocy. Wadą rozwiązania jest to, że te komentarze wciąż trzeba wpisać. Nikt tego za programistę nie zrobi. Zaletą – że komentarze pozostają tam, gdzie są najbardziej potrzebne czyli w odpowiednich miejscach kodu, a jednocześnie tworzą zwarty tekst pomocy, który jest łatwo dostępny i czytelny bardziej niż teksty porozrzucane w wielu miejscach.
Komentarze
Autohelp musi mieć co wyświetlić więc kod skryptu należy opatrzyć komentarzami. Komentarze w skryptach bashowych są oznaczane znakiem # na początku linii. Na potrzeby autohelpa te z nich, które mają się pojawić w tekście pomocy oznacza się dwoma znakami ##
i spacją:
## to jest tekst pomocy
Powyższy tekst zostanie wyświetlony w tekście pomocy.
##to jest komentarz # to jest komentarz
Te teksty nie zostaną wyświetlone w tekście pomocy. Takie rozróżnienie pozwala na pozostawienie w kodzie zwykłych komentarzy (np. dotyczących zawartości zmiennych, todo itp.), które w tekście pomocy nie są potrzebne.
Parsowanie argumentów
Wywołanie tekstu pomocy odbywa się poprzez uruchomienie skryptu z argumentem -h
lub --help
. Wymaga to przetwarzania (parsowania) argumentów. Jeśli więc w skrypcie ma być zawarty autohelp to konieczne jest przetwarzanie argumentów nawet jeśli w samym skrypcie nie jest to potrzebne. Jest kilka sposobów, żeby to zrobić, na potrzeby autohelpa wybrałem pętlę z instrukcją case. Jest to rozwiązanie uniwersalne, pozwala używać argumentów zarówno w krótkiej formie (-h
), jak i w długiej (--help
), a nawet argumentów z podaną wartością. Ponieważ pętla przechodzi po wszystkich argumentach, odrzucając je kolejno, została zamknięta w funcji, dzięki czemu skrypt ma w dalszym ciągu dostęp do wszystkich podanych argumentów poprzez $1, $2
itd. Do funkcji jest przekazywany cały dostępny zestaw argumentów ($@
). Cała konstrukcja wygląda następująco:
# funkcja, w której parsowane są argumenty function aparse(){ # pętla pobierająca kolejne argumenty skryptu while [ ! -z $1 ] do case "$1" in ## -u | --upper – ustawia wielkie litery -u|--upper) UPPER=true ;; ## -w | --width SZER - określa docelową szerokość tekstu w znakach -w|--width) WIDTH=$2; shift; ;; esac # następny argument shift done } aparse $@
Szczegóły instrukcji case
można sobie przypomnieć np. z doskonałego podręcznika Advanced Bash-Scripting Guide.
W powyższym przykładzie widoczna jest funcja aparse, wewnątrz której jest pętla while, pobierająca kolejne argumenty skryptu. Za przechodzenie od argumentu do argumentu odpowiedzialna jest instrukcja shift
, która usuwa pierwszy argument i przesuwa kolejne – tak więc $2
staje się $1
i tak dalej.
Rozpoznawane argumenty są kolejnymi opcjami instrukcji case. Jak widać można podać równocześnie krótką i długą formę, oddzielając je znakiem |
– obie zadziałają.
Dla przykładu podałem jako drugi argument z wartością. Ponieważ aktualnie przetwarzany argument jest na pierwszej pozycji to wartość znajduje się na drugiej, może więc być odczytana jako $2
. Trzeba tylko pamiętać, żeby ją dodatkowo „przeskoczyć”, dodając shift
na końcu polecenia – jak to jest widoczne w przykładzie.
Obie opcje zostały skomentowane już w takim formacie, w jakim spodziewa się tego autohelp: dwa znaki #
i spacja. Jak widać zostały umieszczone w miejscach, do których się odnoszą, stanowiąc opis następujących po nich linii.
Parsowanie komentarzy
Jak więc z tych elementów zbudować tekst pomocy?
Trzeba pamiętać, że skrypt bashowy jest plikiem tekstowym i jako taki daje się przeszukiwać za pomocą polecenia grep
. A mając do dyspozycji wyróżnik w postaci ciągu „##
” łatwo już wyciągnąć potrzebne nam linie komentarza z kodu skrytpu. W moim rozwiązaniu polecenie wygląda następująco:
cat "$0" | grep "^\s*## " | sed 's/^\s*## / /'
Pierwsza część to wypisanie kodu skryptu, który następnie jest – w drugiej części – filtrowany pod kątem ciągu wyróżniającego tekst pomocy. Trzecia część usuwa początkowe ##
, bo w tekście pomocy wyświetlanym w konsoli brzydko to wygląda.
Szablon
Wypisywanie tego wszystkiego za każdym razem mijałoby się z celem – rozwiązanie ma przyspieszać pracę, a nie dawać jej dodatkowy narzut. Rzecz jasna najlepiej jest przygotować sobie szablon i zaczynać budowę skryptu od takiego modułu, który załatwia dwie sprawy od razu – parsowanie argumentów i szybkie tworzenie tekstu pomocy.
Oto gotowy szablon, którego ja sam używam:
#!/bin/bash #Na początek informacja co robi skrypt i jaka jest składnia ## Skrypt testowy ## składnia: helptest [argumenty] function aparse(){ while [ ! -z $1 ] do case "$1" in -h|--help) HELPTEXT=$(cat "$0" | grep "^\s*## " | sed 's/^\s*## / /') echo -e "${HELPTEXT}" exit 0 ;; ## -u | --upper – ustawia wielkie litery -u|--upper) UPPER=true ;; ## -w | --width SZER - określa docelową szerokość tekstu w znakach -w|--width) WIDTH=$2; shift; ;; esac shift done } aparse $@
Zapisany w pliku pod nazwą helptest i uruchomiony z argumentem --help
daje następujący wynik:
$ helptest –help Skrypt testowy użycie: helptest [argumenty] -u | --upper – ustawia wielkie litery -w | --width SZER - określa docelową szerokość tekstu w znakach
Szablon zawiera dwie opcje – jedną zwykłą, drugą z wartością. W mojej pracy najczęściej któraś mi się przydaje więc od razu są na miejscu, wystarczy je odpowiednio zmodyfikować. Szablon można zapisać w pliku np. w katalogu Szablony i mieć go zawsze pod ręką (a raczej pod prawym klawiszem myszy) bądź wpisać sobie do snippetów w ulubionym edytorze i wklejać szybko do nowo tworzonych skryptów.
Pomysł ten został sprawdzony w praktyce i zdał egzamin. Tworzyłem pakiet obejmujący prawie setkę skryptów dla kilkuosobowego zespołu. Pisanie odrębnych tekstów pomocy zajęłoby dużo czasu, dzięki temu rozwiązaniu taki tekst powstawał praktycznie w czasie pisania skryptu (zawsze komentuję kod, tym razem robiłem to tylko w specyficzny sposób). Polecam wypróbowanie tego sposobu – jeśli komuś się spodoba i wdroży go u siebie, będzie mi miło, że mój pomysł komuś pomógł w pracy.