27.04.20226 min

Meir GabayDevOps Engineer

Bash zawodowiec i pisanie skryptów - Przewodnik po stylach

Poznaj tajniki skryptów Basha, aplikację "Good Vibes" i paradygmaty programowania funkcyjnego.

Bash zawodowiec i pisanie skryptów - Przewodnik po stylach

Pisanie  skryptów Bash może być nieco wymagające, jeśli nie zna się jego różnych dziwactw i zalet. W moim ojczystym języku jidysz na określenie owych „dziwactw i zalet” mamy specjalne słowo; określamy to „Shtickim” (liczba mnoga od „Shtick”). Gotowy, aby dowiedzieć się więcej o „Shtickim” Basha?

Artykuł ten jest częścią serii, nad którą pracuję, ponieważ chciałbym zachować wiedzę dla przyszłych zapominalskich, pomóc nowym kolegom i dogodzić programistom takim jak Ty, którzy chcą pokochać Basha tak jak pokochałem go ja. A więc zaczynajmy!


Jest to język skryptowy

Należy pamiętać, że Bash jest językiem skryptowym, co oznacza, że nie zapewnia standardowych funkcji, które język programowania ma do zaoferowania:


Jak już się pewnie domyślacie, “programiści Basha” (jeśli coś takiego w ogóle istnieje) stoją przed wieloma wyzwaniami. Powyższa lista to zaledwie wierzchołek góry lodowej.


Poniżej znajdziecie świetne wpisy na blogach, z którymi mocno się utożsamiam:


Skoro już wiemy, że jestem zakochany w Bashu, to chciałbym, żebyście poczuli to samo co ja.


Konwencja nazewnictwa zmiennych

Tak nazywam zmienne w moich skryptach Basha.


W moim wcześniejszych skryptach Basha trudno było zrozumieć nazwy zmiennych. Zmiana tej konwencji nazewnictwa bardzo pomogła mi zrozumieć zakres zmiennych i ich przeznaczenie.


Aplikacja Good Vibes

Naturalnie, musimy też zobaczyć jakiś praktyczny przykład, więc tutaj pokazuję, jak zastosować powyższą konwencję nazewnictwa w mojej aplikacji good_vibes.sh.

good_vibes.sh

#!/usr/bin/env bash
# ^ This is called a Shebang
# I'll cover it in future blog posts


# Global variables are initialized by Env Vars.
# I'm setting a default value with "${VAR_NAME:-"DEFAULT_VALUE"}"
_USER_NAME="${USER_NAME:-"$USER"}"
_USER_AGE="${USER_AGE:-""}"


complement_name(){
  local name="$1"
  echo "Wow, ${name}, you have a beautiful name!"
}


complement_age(){
  local name="$1"
  local age="$2"
  if [[ "$age" -gt "30" ]]; then
    echo "Seriously ${name}? I thought you were $((age-7))"
  else
    echo "Such a weird age, are you sure it's a number?"
  fi
}


main(){
  # The only function that is not "pure"
  # This function is tightly coupled to the script
  complement_name "$_USER_NAME"
  complement_age "$_USER_NAME" "$_USER_AGE"
}


# Invokes the main function
main


good_vibes.sh - wykonanie i output

export USER_NAME="Julia" USER_AGE="36" && \
bash good_vibes.sh

# Output
Wow, Julia, you have a beautiful name!
Seriously Julia? I thought you were 29


Rozbierzmy aplikację good_vibes.sh na „zestaw reguł”, które można zaimplementować w skryptach.


Odstępy między blokami kodu

Dzięki dwóm (2) pustym wierszom między każdym blokiem kodu skrypt staje się bardziej czytelny.


Wcięcie

Ja używam dwóch (2) spacji do wcięć, ale jeśli użyjesz czterech (4) to też w porządku. Upewnij się tylko, że nie używasz ich naprzemiennie.


Nawiasy klamrowe

Jeśli jest to zmienna ${VARIABLE} concatenated with string, należy użyć nawiasów klamrowych, ponieważ ułatwiają one czytanie.

Jeśli jest to zmienna "$LONELY_VARIABLE", nie ma takiej potrzeby, ponieważ dzięki temu szybciej zorientujesz się, czy jest to zmienna „samotna” (lonely), czy też nie.

Podstawowym przeznaczeniem nawiasów klamrowych jest wykonywanie operacji rozszerzenia parametru shell, jak pokazano w części poświęconej inicjalizacji zmiennych globalnych.


Nawiasy kwadratowe

Użycie podwójnych nawiasów kwadratowych [[ ]] ułatwia czytanie warunkowego bloku kodu. Należy jednak pamiętać, że używanie podwójnych nawiasów kwadratowych nie jest obsługiwane w shell sh; należy więc używać pojedynczych nawiasów [ ].

Aby zilustrować czytelność, poniżej przedstawiam „złożony” warunkowy blok kodu:

if [[ "$USER_NAME" = "Julia" || "$USER_NAME" = "Willy" ]] \
   && [[ "$USER_AGE" -gt "30" ]]; then
  echo "Easy to read right?"
fi

# Mind that `||` is replaced with `-o`, see https://acloudguru.com/blog/engineering/conditions-in-bash-scripting-if-statements
# Thank you William Pursell
if [ "$USER_NAME" = "Julia" -o "$USER_NAME" = "Willy" ] \
   && [ "$USER_AGE" -gt "30" ]; then
  echo "No idea why but I feel lost with single brackets."
fi


Jeśli jeszcze nie zauważyłeś, to właśnie dowiedziałeś się, że || oznacza OR a && oznacza AND. A krótkie wyrażenie -gt oznacza greater than przy stosowaniu liczb. I wreszcie symbol \ pozwala na łamanie wierszy na rzecz zwiększenia czytelności kodu.

Shtick: Użycie znaku \ z dodatkową spacją \ <- extra space może prowadzić do pojawienia się dziwnych błędów. Upewnij się, że po \ nie ma żadnych końcowych spacji.


Zakładam, że używanie nawiasów [[ ]] jest bardziej intuicyjne, ponieważ większość poleceń warunkowych jest podwajana && ||.


Inicjalizacja zmiennych

Zmienne globalne są inicjalizowane za pomocą zmiennych środowiskowych i ustawiane na wartości domyślne w przypadku pustych zmiennych środowiskowych.

Jak wspomniano w komentarzach do good_vibes.sh, ustawiam wartość domyślną za pomocą

"${VAR_NAME:-"DEFAULT_VALUE"}"


W powyższym fragmencie tekst DEFAULT_VALUE jest zakodowany na stałe i można go zastąpić zmienną. Na przykład:

_USER_NAME="${USER_NAME:-"$USER"}"


Funkcje i zmienne funkcji lokalnych

Nazwy funkcji i nazwy zmiennych funkcji lokalnych są snake_cased. Być może zechcesz zmienić nazwy funkcji na lowerCamelCase, ale to oczywiście Twoja decyzja.

Sprzęganie funkcji ze skryptem jest częstym błędem, choć sam od czasu do czasu go popełniam, a w moich funkcjach znajdziesz zmienne globalne/środowiskowe. Dzieje się tak, jednak gdy wiem, że „ten fragment kodu nie zmieni się za bardzo”.

Aha, i upewnij się, że nie używasz $1 ani żadnego innego argumentu bezpośrednio; zawsze używaj local var_name="$1".

_USER_NAME="${USER_NAME:-"$USER"}"

# Bad - coupled
coupled_username(){
  echo "_USER_NAME = ${_USER_NAME}"
}

# Good - decoupled
decoupled_username(){
  local name="$1"
  echo "name = ${name}" 
}

# Usage
coupled_username  
decoupled_username "$_USER_NAME"


Programowanie funkcyjne

Tutaj temat dotyczy funkcji i zmiennych funkcji lokalnych, gdzie funkcje są tak „czyste”, jak to tylko możliwe. Jak widać w good_vibes.sh, prawie wszystko jest owinięte w funkcję, z wyjątkiem inicjalizacji zmiennych globalnych.

Nie widzę sensu pisania funkcji init_vars, której zadaniem jest poradzenie sobie ze zmiennymi globalnymi. Jednak od czasu do czasu zdarza mi się dodawać funkcję validate_vars, która sprawdza zmienne globalne i sprawdza poprawność ich wartości. Na pewno znajdzie się tutaj miejsce na dyskusję, więc zapraszam do komentowania i przedstawienia swojego punktu widzenia.


Przemyślenia końcowe

Aplikacja „Good Vibes” zawierała głównie informacje o tym, jak napisać czytelny skrypt Bash zgodnie z paradygmatem programowania funkcyjnego.

Jeśli uważasz, że istnieje potrzeba zmiany sposobu nazywania zmiennych i funkcji, zrób to! Jeśli tylko kod jest łatwy do zrozumienia i czytelny, to jesteś tutaj na dobrej drodze.

W kolejnych artykułach z tej serii zostaną omówione następujące zagadnienia:

  • Obsługa błędów.
  • Pobieranie danych JSON z punktu końcowego HTTP.
  • Zadania w tle i obserwowanie plików pod kątem zmian za pomocą fswatch.
  • Struktura repozytoriów Git — dodawanie skryptów Bash do istniejących repozytoriów lub tworzenie nowego repozytorium za pomocą aplikacji Bash CLI.
  • Publikowanie Bash CLI jako obrazu Dockera.


Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>