Bash(ujący) w zbożu cz. 2 — automatyzacja użytkownika

Chociażbym chodził ciemną doliną, zła się nie ulęknę...

Witajcie. W poprzednim moim wpisie Bash(ującego) w zbożu cz. 1 stworzyliśmy kilka prostych skryptów które, pokazały w prosty sposób funkcjonowanie języka skryptowego bash. Kilka bardzo spostrzegawczych par oczu wypatrzyło różnicę nie omówione w kilku skryptach. Chodzi o sekcję Shebang

#!/usr/bin/env bash
#!/usr/bin/bash
#!/bin/bash

Różnice faktycznie są dość ważne i mogą powodować rożne działanie skryptu na innych dystrybucjach, lub nawet tych samych z inną wersją / lokalizacją bash. Zastosowanie pierwszej z wyżej wymienionej opcji shebang (#!/usr/bin/env bash) powoduje odwołanie się do środowiska (environment). Unika się wtedy sytuacji w której, to w /bin/bash nie było by bash (ścieżka ta by nie istniała). Z drugiej strony shebang z linii drugiej i trzeciej z powyższego przykładu jest bardziej bezpieczniejsza formą. Już tłumaczę czemu. Ścieżka bezpośrednia daje nam pełną kontrolę nad wykonaniem kodu i pliku wykonywalnego, gdyż odnosisz się do konkretnej wersji, czy lokalizacji bash. Nie zdajesz się na łaskę otagowanej zmiennej ze ścieżką do bash. Łatwo sobie wyobrazić, że w przypadku odnoszenia się do env o podmianę pliku bash nie trudno. Podmianę na własny złośliwy po przez przekierowanie zmiennych czy nadpisanie zmiennej $PATH.

Podsumowując zyskujemy pewną elastyczność kosztem bezpieczeństwa i kontroli lub zyskujemy kontrolę tracąc elastyczność. Wybór pozostawiam wam :) ;)

Im dalej w las tym więcej drzew.

Bash posiada też swoją odpowiednia sekwencję którą, kieruje się podczas interpretacji poleceń zawartych w plikach skryptowych *.sh (choć samo rozszerzenie nie jest wymagane dla pliku by był on plikiem skryptowym bash). O to sekwencja:

  1. Zanim zostanie wykonana komenda następują wszelkie przekierowania używane przez shell.
  2. Aliasy.
  3. Rozbudowane parametry, podstawowe polecenia rozszerzenia arytmetyczne i usuwanie cudzysłowów (podwójnych apostrofów) przed przypisaniem do zmiennej.
  4. Wbudowane funkcje powłoki jak i własne.
  5. Wbudowane komendy.
  6. Hash tabele
  7. Zmienna $PATH
  8. /home/$username/bin (jeżeli istnieje)
  9. Jeżeli nic nie znajduje wypluwa komunikat "Nie znaleziono polecenia"

Funkcji w bash napisaliśmy trochę w poprzednim kursie. Spróbuj zatem nasz poniższy skrypt uruchomić. Umieści on nam w ścieżce /home/$USER/bin plik hello.sh. Po wykonaniu poniższego skryptu wydajmy polecenie hello.sh - oczywiście nie będąc w katalogu /home/$USER/bin.

mkdir /home/$USER/bin
cat >> /home/$USER/bin/hello.sh <<EOF
#!/bin/bash
echo "Witaj serdecznie \${USER}. Dzisaj jest \$(date)"
EOF
chmod +x /home/$USER/bin/hello.sh

Jak widać od razu po wywołaniu powyższego skryptu (bash your_script_name.sh) możemy wydać polecenie hello.sh.

Oczywiście jeżeli ścieżka /home/$user/bin nie znajdzie się w zmiennej $PATH nasze skrypty będą musiały być wywoływane przez podanie ścieżki odpowiednio względnej (ang. relative) lub bezwzględnej (ang. absolute). Ścieżki odpowiednio względne i bezwzględne były już poruszane na materiałach wideo do przedniej cześć wpisu. Dla przypomnienia ścieżka bezwzględna to taka która, odwołuje się bezpośrednio do roota struktury katalogowej w Linux (czyli / ) a względna to taka która jest podawana względem aktualnej pozycji dla przykładu ./bin/hello.sh

Metod na dodanie naszej ścieżki lub interesującej nas ścieżki do zmiennej $PATH jest kilka. 

Exporting PATH variable to /etc/environment

PATH = /usr/local/sbin:/usr/local/bin:/sciezka/do/miejsca/z/skryptem
source /etc/environment && export PATH

oczywiście musimy pamiętać o tym by nie nadpisać zmiennej PATH dlatego używamy takiego zapisu:

PATH = $PATH:/usr/local/sbin:/usr/local/bin:/pathToMyDirectory
source /etc/environment && export PATH

lub używamy lokacji która, jest przeglądana podczas uruchamiania systemu i logowania na użytkownika. 

# add this command to `~/.profile` file
$ export PATH=$PATH:/myNewDir
# then run the source command
$ source ~/.profile

Jednak jak podejrzymy sobie nasz folder domowy zobaczymy tam znacznie więcej plików niż tylko ~/.profile

Musimy pamiętać, że istnieje reguła której należy się trzymać by nie mieć problemów. Przyjrzyjmy się poniższej tabeli.

Literki A,B, C mówią nam o kolejności uruchamiania plików. B1, B2, B3 o kolejności w przypadku gdy pozostałe B nie występują. Mamy zatem trzy mozliwości zalogowanie się na "Interactive login", "Interactive non-login oraz script. 

Zazwyczaj większość użytkowników napotka powłokę logowania tylko wtedy, gdy:

  • zalogowali się z tty, nie poprzez GUI
  • zalogowali się zdalnie, na przykład przez ssh.

Jeśli powłoka została uruchomiona w inny sposób, na przykład poprzez GNOME, gnome-terminal lub konsolę KDE, to zazwyczaj nie jest to powłoka logowania - powłoka logowania była tym, co uruchomiło GNOME lub KDE za plecami po zalogowaniu. Liczy się kto pierwszy ten lepszy. Nowe terminale lub otwierane okna ekranowe również nie są powłokami logowania.

Zatem gdy chcemy umieścić zmienna dla wszystkich użytkowników powinniśmy zacząć od pliku /etc/profile. Gdy chcemy by zmienna lub komunikat był bardziej spersonalizowany umieszczamy w plikach w katalogu użytkownika oczywiście pamiętając o tabeli powyżej.

Wspomnieliśmy też w niniejszym wpisie dodatkowo o HASH tablicy. Warto zapamiętać, że to nic innego jak mechanizm sortowania naszego chace przyspieszający prace ze zmiennymi środowiskowymi. Podejrzeć go możemy:

hash

a w razie problemów wymusić swoiste posłuszeństwo:

hash -r

I tak dnia siódmego... stworzył użytkownika.

Zmusimy teraz nasz skrypt do stworzenia naszego użytkownika trochę inaczej niż zwykłym poleceniem adduser. Nie uważam, że to polecenie jest złe, po prostu chwiałbym mieć choć trochę więcej władzy nad tym co się dzieje. Dodatkowo przyjmijmy pewne założenia:

  • Użytkownika może zostać stworzony na podstawie danych z pliku (wielu użytkowników) - jako parametr do skryptu. Przyjmiemy dla ułatwienia, że dane w pliku juz są odpowiednio przygotowane. Czyli pierwsza litera imienia i reszta nazwisko. Każda nowa linia pliku to użytkownik. Plik może zawierać tylko literyQWERTYUIOPASDFGHJKLZXCVBNM.
  • Użytkownika możemy stworzyć po przez dodanie IMIENIA i NAZWISKA jako parametr do skryptu. Parametry mogą zawierać tylko litery QWERTYUIOPASDFGHJKLZXCVBNM
  • Użytkownika można stworzyć na podstawie danych wprowadzonych ręcznie po przez wywołanie skryptu bez parametrów (opcja z menu dodaj użytkownika).
  • Dodatkowe założenia to gdy brak przy skrypcie opcji uruchom menu wyboru. Gdy podana tylko jedna opcja jest ona nazwa pliku gdzie jest lista użytkowników. Gdy podane dwie opcje lub więcej traktuj to jako IMIĘ i NAZWISKO w tej kolejności, inne odrzuć.

To zaczynamy. Pojawi się tu trochę nowych rzeczy ale i dobrze nam już poznanych na początku naszej przygody rozpoczętej w części pierwszej. Gotowi?


[...]Wszyscy gotowi? Można zaczynać? Zatem otwieram nasz program już! [...]

Zaczniemy od dobrze poznanego nam select. Pętli która, bez wyraźnego break może działać w nieskończoność. Na jej podstawie zbudujemy menu. 

Spójrzmy na kod:

function create_special_user()
{
options="\"Dodaj użytkownika\" \"Pokaż użytkowników\" \"Wyjście\""
PS3='Wybierz opcję: '

eval set $options

select option in "$@"
do
    if [ $option == "Wyjście" ]; then
        echo
        break
    fi
    echo "Wybrałeś opcję $option"
done
echo "Pa Pa"
}

Prawda, że proste. Oczywiście bez wykorzystania pewnego podstępu, opcji mielibyśmy znacznie więcej. Bash bardzo lubi spacje i po prostu wiele z nich zamienia zawsze w jedna a domyślnie spację wykorzystuje jako podział. Pamiętacie może poprzedni przykład z pętla for?

function example_for_01()
{
    # Lista iteracyjnych elementów zapisana w jednej zmienej, odstep miedzy znakami w postaci spacji pozwala na potraktowanie ich jako osobny element. Wykonac iteracje.
    # List of iterative elements written in one variable, the space between spars in the form of spacti allows you to treat them as a separate element. Perform iterations.
    words="A B C D E F G H I J K L M N O P R S T U Q W Y X Z"
    echo "Nasz alfabet"
    for word in $words
    do
        echo -n "${word}."
    done
    echo ""
}

function example_for_02()
{
    # Lista iteracyjnych elementów tutaj zastosowanie cudzysłowia powoduje potraktowanie wszystkich elemantów jako całość.
    # List of iterative elements here, the use of inverted commas results in all elemantas as a whole.
    words="A B C D E F G H I J K L M N O P R S T U Q W Y X Z"
    echo "Nasz alfabet"
    for word in "$words"
    do
        echo -n "${word}."
    done
    echo ""
}

Spróbujcie za komentować linijkę z eval set $options

Zbudujmy naszą podstawę czyli zróbmy wyjście dla naszych założeń

#!/bin/bash

function show_menu()
{
options="\"Dodaj użytkownika\" \"Pokaż użytkowników\" \"Wyjście\""
PS3='Wybierz opcję: '

eval set $options

select option in "$@"
do
    case "$option" in
        "Dodaj użytkownika")
            echo 1
            ;;
        "Pokaż użytkowników")
            echo "Lista aktualnych użytkowników:"
            awk -F: '($3 >= 1000) {printf "%s:\n",$1}' /etc/passwd
            read -n 1 -s -r -p "Naciśnij dowolny klawisz by kontynuować..."
            ;;
        "Wyjście")
            break
            ;;
    esac
done
echo "Wyjście ze skryptu."
}

function create_user_now()
{
    echo "Narazie nie robie nic"
}

function create_special_user()
{
if [ $# -ge 2 ]; then
    echo ""
    # Imię i Nazwisko jako parametr.
elif [ $# -eq 1 ]; then
    # Jedna opcja zatem traktuj jako plik
    if [ -e "$1" ]; then
        # Plik istnieje.
        if file "$1" | grep -q text$; then
            # Jest to tekst.
            while IFS='' read -r line || [[ -n "$line" ]]; do
                # Przejdź do działania na każdej lini $line
            done < $1
        else
            # File nie uznaje tego za tekst.
            echo "Nie jest to typowy plik tekstowy!"
            break
        fi
    else
        # Plik nie istnieje.
        echo "Plik o podanej nazwie ${1} nie instanieje!"
        break
    fi
else
    # Jak nie większe lub równe od dwóch i nie równe jeden czyli zero - wyswietlamy opcje.
    show_menu
fi
}

main()
{
    create_special_user
}

main

Mamy już przygotowane podwaliny pod nasz docelowy skrypt. Dla tych którzy, śpieszą się z kopiowaniem "gotowca" bez dokładnej analizy mam małe rozczarowanie. To jeszcze nie działa... ale spokojnie będzie :). Przyjrzyjmy się zatem szkicowi.

Funkcja show_menu() ma za zadanie zaprezentować na menu wyboru. Wykorzystujemy do tego celu nieskończona pętlę select (nie do końca nieskończona bo przecież kiedyś z tego trzeba wyjść, jednak konstrukcja select jest taka, że bez ewidentnego wywołania break z niej nie wyjdziemy). Wartości które, wybiorę i przekaże do zmiennej $option przekazuje dalej do case. Konstrukcję case poznaliśmy poprzednio, więc nie powinno tu być dla was żadnej magii. Pozycja "Pokaż użytkowników" można powiedzieć, że jest w swej finalnej wersji i świetnie realizuje swoje zadanie.

Funkcja create_special_user() będzie robić za nas lwią cześć roboty. Podejmie decyzje kluczowe dla dalszego działania skryptu. Pozwoli nam dodać użytkownika jako parametry do skryptu. Doda kilku z pliku lub pozwoli utworzyć go ręcznie po przez wpisanie danych w oknie terminala. Przyjrzyjmy się nowością w tej funkcji.

if [ -e "$1" ]; then
        # Plik istnieje.
        if file "$1" | grep -q text$; then
            # Jest to tekst.
            while IFS='' read -r line || [[ -n "$line" ]]; do
                # Przejdź do działania na każdej lini $line
            done < $1
        else
            # File nie uznaje tego za tekst.
            echo "Nie jest to typowy plik tekstowy!"
            break
        fi
    else
        # Plik nie istnieje.
        echo "Plik o podanej nazwie ${1} nie instanieje!"
        break
    fi

Po pierwsze sprawdzamy czy plik istnieje dzięki opcji -e. Potem sprawdzamy czy plik jest plikiem tekstowym. Opcja IFS='' zabezpiecza nas przed sytuacją gdy w linii znajdą się spacje. Bash nie będzie próbował jednej linii podzielić na dwie zmienne. Opcją -r wyłączmy symbol ucieczki do wyświetlania specjalnych znaków. Ostatnią opcją || [[ -n "$line" ]] zabezpieczamy się przed pomijaniem ostatniej linii z pliku.

Mamy zatem kod odpowiedzialny za wyświetlenie menu za rozpoznanie danych wejściowych. Teraz przyszła kolej na kod który, zajmie się stworzeniem dla nas użytkownika.

Użytkownika będziemy tworzyć na podstawie imienia i nazwiska. Dokładniej rzecz ujmując wykorzystamy pierwsza literę imienia i połączymy ją z nazwiskiem. Zabezpieczmy też trochę nasz skrypt przed osobami nie znającymi zasad panujących w kontach linuksowych i tym że konto musi być pisane małymi literami. Tez macie odruch pisania Imion i Nazwisk zaczynając od wielkich liter? Właśnie dlatego to ustawienie. Ale dosyć gadania zobaczmy o czym mowa:

# Imię małymi literami.
        user_name=$(echo $1 | tr [:upper:] [:lower:])
        # Nazwisko małymi.
        user_surname=$(echo $2 | tr [:upper:] [:lower:])
        # Wydobywamy pierwszą literę z imienia.
        oneletter=$(echo $user_name | cut -c 1)
        # Tworzymy konto
        account=$oneletter$user_surname

Teraz tylko musimy obsłużyć tylko użytkowników z pliku tekstowego. Tu już umówiliśmy się, że dane będziemy mieli obrobione i w postaci inazwisko. Dlatego też po prostu przypiszemy dane do zmiennej.

account=$1

Finalna postać jest już przewidywalna

# Sprawdzanie ile argumentów zostało przekazanych do sunkcji.
    if [ $# -eq 2 ]; then
        # Imię małymi literami.
        user_name=$(echo $1 | tr [:upper:] [:lower:])
        # Nazwisko małymi.
        user_surname=$(echo $2 | tr [:upper:] [:lower:])
        # Wydobywamy pierwszą literę z imienia.
        oneletter=$(echo $user_name | cut -c 1)
        # Tworzymy konto
        account=$oneletter$user_surname
    else
        # Jeden argument, dane z pliku już sformatowane.
        account=$1
    fi

Tak. Będziemy sprawdzać ilość parametrów. W obu przypadkach wpisania i podania imion oraz nazwisk w postaci opcji do skryptu czy argumentu do funkcji (dane pobrane od nas w konsoli jako pytanie). Pierwszy warunek będzie identyczny otrzymamy dwa argumenty. Jeden argument będzie świadczył o pliku. Decyzję finalna czy jest to plik tekstowy podejmie inny skrypt. My w tym miejscu już to będziemy wiedzieli.

Musimy jeszcze tylko sprawdzić czy dany użytkownik w systemie przypadkiem nie istnieje oraz w razie czego z inkrementować jego nazwę dodając cyfrę na końcu jego loginu. Potem wykorzystamy polecenie adduser z odpowiednimi parametrami które, utworzy nam użytkownika wraz z wskazanym domowym folderem. Bez zbędnych pytań o pokój numer telefonu czy inne dane podawane podczas normalnego wykonania polecenia adduser.

adduser $account --home $path_account --gecos "$name $surname,,,," --disabled-password

Hasło ustawimy analogiczne do nazwy i wymusimy zmianę jego zaraz po zalogowaniu się do systemu.

echo "$account:$account" | sudo chpasswd >/dev/null
    chage -d 0 $account

By otrzymać

# Dwóch użytkowników o takiej samej nazwie nie stworzę więc trzeba to sprawdzić.
    egrep "^$account" /etc/passwd >/dev/null

    if [ $? -eq 0 ]; then
        # Użytkownik taki już istnieje. Nie możemy utworzyć otakiej samej nazwie. Musimy utworzyć troche inne konto dodając liczbę na koncu.
        echo "Użytkownik: $account istnieje."
        echo "Nie można dodać konta. Generacja nowej nazwy!"
        addnumber=1
        egrep "^$account" /etc/passwd >/dev/null
        while [ $? -eq 0 ]; do
            addnumber=$((addnumber+1))
            tmp_account=$account$addnumber
            egrep "^$tmp_account" /etc/passwd >/dev/null
        done
        account=$tmp_account
    fi

    path_account=/home/$account
    adduser $account --home $path_account --gecos "$name $surname,,,," --disabled-password
    echo "$account:$account" | sudo chpasswd >/dev/null
    chage -d 0 $account

    egrep "^$account" /etc/passwd >/dev/null
    if [ $? -eq 0 ]; then
        echo "Użytkownik został poprawnie założony."
    fi

    echo "${account}:${account}" | tee -a list_of_user.txt>/dev/null

Nadajmy naszemu skryptowi wyjściowy kształt. Stwórzmy użytkownika.

Tak działa gotowy skrypt. Spójrzmy na kod.

Funkcja show_menu()

function show_menu()
{
# Opcje dla pętli select.
options="\"Dodaj użytkownika\" \"Pokaż użytkowników\" \"Wyjście\""
# Nadajmy troche wyrazu naszemu select.
PS3='Wybierz opcję: '

# By uniknąć łamania spacji i tworzenia innych zmiennych.
eval set $options

# Nasz select.
select option in "$@"
do
    # case
    case "$option" in
        # Opcja dla wartości "Dodaj użytkownika".
        "Dodaj użytkownika")
            read -p "Podaj swoje imię: " name
            read -p "Podaj swoje nazwisko: " surname
            create_user_now $name $surname
            ;;
        # Opcja dla wartości "Pokaż użytkowników".
        "Pokaż użytkowników")
            echo "Lista aktualnych użytkowników:"
            awk -F: '($3 >= 1000) {printf "%s:\n",$1}' /etc/passwd
            read -n 1 -s -r -p "Naciśnij dowolny klawisz by kontynuować..."
            ;;
        # Po Prostu wyjście.
        "Wyjście")
            exit 0
            ;;
    esac
done
# Komunikat końcowy.
echo "Wyjście ze skryptu."
}

Funkcja create_special_user()

function create_special_user()
{
if [ $# -ge 2 ]; then
    # Imię i Nazwisko jako parametr.
    create_user_now $1 $2
elif [ $# -eq 1 ]; then
    # Jedna opcja zatem traktuj jako plik
    if [ -e "$1" ]; then
        # Plik istnieje.
        if file "$1" | grep -q text$; then
            # Jest to tekst.
            filename="$1"
            while IFS='' read -r line || [[ -n "$line" ]]; do
                # Przejdź do działania na każdej lini $line
                username="$line"
                create_user_now $username
            done < $filename
        else
            # File nie uznaje tego za tekst.
            echo "Nie jest to typowy plik tekstowy!"
            break
        fi
    else
        # Plik nie istnieje.
        echo "Plik o podanej nazwie ${1} nie instanieje!"
        break
    fi
else
    # Jak nie większe lub równe od dwóch i nie równe jeden czyli zero - wyswietlamy opcje.
    show_menu
fi
}

Funkcja create_user_now()

function create_user_now()
{
    # Sprawdzanie ile argumentów zostało przekazanych do sunkcji.
    if [ $# -eq 2 ]; then
        # Imię małymi literami.
        user_name=$(echo $1 | tr [:upper:] [:lower:])
        # Nazwisko małymi.
        user_surname=$(echo $2 | tr [:upper:] [:lower:])
        # Wydobywamy pierwszą literę z imienia.
        oneletter=$(echo $user_name | cut -c 1)
        # Tworzymy konto
        account=$oneletter$user_surname
    else
        # Jeden argument, dane z pliku już sformatowane.
        account=$1
    fi

    # Dwóch użytkowników o takiej samej nazwie nie stworzę więc trzeba to sprawdzić.
    egrep "^$account" /etc/passwd >/dev/null

    if [ $? -eq 0 ]; then
        # Użytkownik taki już istnieje. Nie możemy utworzyć otakiej samej nazwie. Musimy utworzyć troche inne konto dodając liczbę na koncu.
        echo "Użytkownik: $account istnieje."
        echo "Nie można dodać konta. Generacja nowej nazwy!"
        addnumber=1
        egrep "^$account" /etc/passwd >/dev/null
        while [ $? -eq 0 ]; do
            addnumber=$((addnumber+1))
            tmp_account=$account$addnumber
            egrep "^$tmp_account" /etc/passwd >/dev/null
        done
        account=$tmp_account
    fi

    path_account=/home/$account
    adduser $account --home $path_account --gecos "$name $surname,,,," --disabled-password
    echo "$account:$account" | sudo chpasswd >/dev/null
    chage -d 0 $account

    egrep "^$account" /etc/passwd >/dev/null
    if [ $? -eq 0 ]; then
        echo "Użytkownik został poprawnie założony."
    fi

    echo "${account}:${account}" | tee -a list_of_user.txt>/dev/null 

}

Funkcja main()

main()
{
    create_special_user $1 $2
}

Samo wywołanie już tylko po przez main $1 $2

Tak prezentuje się cały kod. Dla bardziej dociekliwych kod można pobrać z GitHub lekcja 07.

#!/bin/bash

function show_menu()
{
# Opcje dla pętli select.
options="\"Dodaj użytkownika\" \"Pokaż użytkowników\" \"Wyjście\""
# Nadajmy troche wyrazu naszemu select.
PS3='Wybierz opcję: '

# By uniknąć łamania spacji i tworzenia innych zmiennych.
eval set $options

# Nasz select.
select option in "$@"
do
    # case
    case "$option" in
        # Opcja dla wartości "Dodaj użytkownika".
        "Dodaj użytkownika")
            read -p "Podaj swoje imię: " name
            read -p "Podaj swoje nazwisko: " surname
            create_user_now $name $surname
            ;;
        # Opcja dla wartości "Pokaż użytkowników".
        "Pokaż użytkowników")
            echo "Lista aktualnych użytkowników:"
            awk -F: '($3 >= 1000) {printf "%s:\n",$1}' /etc/passwd
            read -n 1 -s -r -p "Naciśnij dowolny klawisz by kontynuować..."
            ;;
        # Po Prostu wyjście.
        "Wyjście")
            exit 0
            ;;
    esac
done
# Komunikat końcowy.
echo "Wyjście ze skryptu."
}

function create_user_now()
{
    # Sprawdzanie ile argumentów zostało przekazanych do sunkcji.
    if [ $# -eq 2 ]; then
        # Imię małymi literami.
        user_name=$(echo $1 | tr [:upper:] [:lower:])
        # Nazwisko małymi.
        user_surname=$(echo $2 | tr [:upper:] [:lower:])
        # Wydobywamy pierwszą literę z imienia.
        oneletter=$(echo $user_name | cut -c 1)
        # Tworzymy konto
        account=$oneletter$user_surname
    else
        # Jeden argument, dane z pliku już sformatowane.
        account=$1
    fi

    # Dwóch użytkowników o takiej samej nazwie nie stworzę więc trzeba to sprawdzić.
    egrep "^$account" /etc/passwd >/dev/null

    if [ $? -eq 0 ]; then
        # Użytkownik taki już istnieje. Nie możemy utworzyć otakiej samej nazwie. Musimy utworzyć troche inne konto dodając liczbę na koncu.
        echo "Użytkownik: $account istnieje."
        echo "Nie można dodać konta. Generacja nowej nazwy!"
        addnumber=1
        egrep "^$account" /etc/passwd >/dev/null
        while [ $? -eq 0 ]; do
            addnumber=$((addnumber+1))
            tmp_account=$account$addnumber
            egrep "^$tmp_account" /etc/passwd >/dev/null
        done
        account=$tmp_account
    fi

    path_account=/home/$account
    adduser $account --home $path_account --gecos "$name $surname,,,," --disabled-password
    echo "$account:$account" | sudo chpasswd >/dev/null
    chage -d 0 $account

    egrep "^$account" /etc/passwd >/dev/null
    if [ $? -eq 0 ]; then
        echo "Użytkownik został poprawnie założony."
    fi

    echo "${account}:${account}" | tee -a list_of_user.txt>/dev/null 

}

function create_special_user()
{
if [ $# -ge 2 ]; then
    # Imię i Nazwisko jako parametr.
    create_user_now $1 $2
elif [ $# -eq 1 ]; then
    # Jedna opcja zatem traktuj jako plik
    if [ -e "$1" ]; then
        # Plik istnieje.
        if file "$1" | grep -q text$; then
            # Jest to tekst.
            filename="$1"
            while IFS='' read -r line || [[ -n "$line" ]]; do
                # Przejdź do działania na każdej lini $line
                username="$line"
                create_user_now $username
            done < $filename
        else
            # File nie uznaje tego za tekst.
            echo "Nie jest to typowy plik tekstowy!"
            exit 1
        fi
    else
        # Plik nie istnieje.
        echo "Plik o podanej nazwie ${1} nie instanieje!"
        exit 2
    fi
else
    # Jak nie większe lub równe od dwóch i nie równe jeden czyli zero - wyswietlamy opcje.
    show_menu
fi
}

main()
{
    create_special_user $1 $2
}

main $1 $2

Oczywiście zachęcam do samodzielnego zmierzenia się z tematem i spróbowania stworzenia własnego kodu.

A dla lubiących słuchać materiał wideo :). Link z całej siódmej lekcji. Pozdrawiam.

Bibliografia

Przystępna i łatwa do zrozumienia :)

  1. https://www.cyberciti.biz/tips/how-linux-or-unix-understand-which-prog...
  2. http://aplawrence.com/Linux/kernel_shebang.html
  3. https://www.cyberciti.biz/tips/an-example-how-shell-understand-which-p...
  4. https://stackoverflow.com/questions/16365130/the-difference-between-us...
  5. https://pl.wikipedia.org/wiki/Shebang
  6. https://savage.net.au/Linux/html/bash.files.html
  7. https://hackprogramming.com/2-ways-to-permanently-set-path-variable-in...
  8. https://medium.com/@rajsek/zsh-bash-startup-files-loading-order-bashrc...
  9. https://savage.net.au/Linux/html/bash.files.html
  10. https://www.cyberciti.biz/faq/how-to-add-to-bash-path-permanently-on-l...
  11. https://www.cyberciti.biz/tips/an-example-how-shell-understand-which-p...
  12. https://www.cyberciti.biz/tips/how-linux-or-unix-understand-which-prog...
  13. http://www.linuxjournal.com/content/bash-preserving-whitespace-using-s...
  14. http://ccm.net/faq/1757-how-to-read-a-linux-file-line-by-line
  15. https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  16. https://www.cyberciti.biz/faq/rhel-debian-force-users-to-change-passwo...
  17. http://www.elinuxbook.com/create-manage-users-using-useradd-linux-comm...
  18. https://stackoverflow.com/questions/6949667/what-are-the-real-rules-fo...