Основи скриптінгу Bash

Поміркуйте! Як поведе себе bash при виконанні скрипта, у якого пропущена команда fi? Відповідь додайте текстом до матеріалів роботи.

  1. Bash не зможе розпізнати де кінець умовного рядка, відповідно видасть синтаксичну помилку, вказуючи, що не знайден закриваючий оператор fi.
  2. Скрипт зупине виконання

Завдання

Виконайте завдання і задокументуйте результати. Оскільки завдань в цій роботі багато, пронумеруйте файли з відповідями відповідно до номеру завдання.

  1. Створіть скрипт high_art.sh. Він повинен перевіряти чи присутня в директорії поряд з ним директорія movie_masterpieces. Якщо присутня, то перевірте чи присутній в ній файл bee_movie.txt. Якщо цього файлу немає, то створіть новий файл з повним сценарієм фільму “Bee Movie”. Його ви можете знайти тут: https://pastebin.com/UBVrjrYb. Документ опублікований без кінцевого терміну зберігання, тому можете використати знання з попередніх модулів і завантажити його напряму.

high_art.sh

#!/bin/bash

folder=movie_masterpieces
file=bee_movie.txt
if [[ -d ./$folder ]]; then
	if [ ! -f "./$folder/$file" ]; then 
		curl "https://pastebin.com/raw/UBVrjrYb" > "./$folder/$file" 
		echo "Файл $file створено в $folder." 
	else 
		echo "Файл $file вже існує в $folder." 
	fi
else
	echo "Директорія $folder не знайдена"
fi

Наступне завдання - незвичайне. Воно є одночасно дитячою грою і типовим завданням на співбесідах для розробників.

  1. FizzBuzz. Створіть скрипт, який буде лічити від 1 до 100. При цьому, щоразу як у скрипті зустрічається число, яке ділиться на 3 без остачі, замість числа скрипт має написати “Fizz”. Якщо число ділиться на 5 - то “Buzz”. І, насамкінець, якщо число ділиться і на 3, і на 5 то замість нього слід написати “FizzBuzz”. (case не можна 😈)

FizzBuzz.sh

#!/bin/bash

for i in {1..100}; do
	if (( i % 3 == 0)) && (( i % 5 == 0)); then
		echo "FizzBuzz"
	elif (( i % 3 == 0 )); then 
		echo "Fizz" 
	elif (( i % 5 == 0 )); then 
		echo "Buzz" 
	else echo "$i"
	fi 

Існує легенда, що цю задачу можна вирішити лише двома перевірками if. Подужаєте?

  1. Всім відомо що мережевих портів 69420*. Напишіть такий скрипт, який би перераховував усі порти, окрім загальновідомих: 22, 25, 80, 443, 993
#!/bin/bash

exclude=(22 25 80 443 993)

for i in {1..65535}; do
  excluded=false
  for port in "${exclude[@]}"; do
    if [[ "$i" -eq "$port" ]]; then
      excluded=true
      break
    fi
  done
  if [[ "$excluded" == false ]]; then
    echo "$i"
  fi
done
  1. Напишіть скрипт, який буде моніторити використання якогось ресурсу (процесора, ОЗП, мережі) кожні 10 секунд та у випадку якщо значення перевищує певний поріг (наприклад, 90%) буде сповіщати про це повідомленням у лог-файл. Для додаткових очок загорніть його в systemd-юніт і змусьте логувати в журнал systemd.

Наприклад , візмемо за ресурс, який будемо моніторити вільну ОЗУ, з порогом у 15%, щоб додатково не забивати памґять на сервері і перевірити працездатність

ram_monitor.sh

#!/bin/bash

THRESHOLD=15
# LOG_FILE="/var/log/ram_monitor.log" # Розкоментувати для логування в файл

while true; do
  used_percent=$(free | awk '/Mem:/ {printf "%.0f", ($3/$2) * 100}')
  if (( $used_percent > $THRESHOLD )); then
    # echo "$(date) - RAM usage exceeded ($used_percent%)/$THRESHOLD%" >> $LOG_FILE # Для логування в файл
    logger -p daemon.warning "RAM usage exceeded ($used_percent%)/$THRESHOLD%"
  fi
  sleep 10
done

Створимо сервіс для systemd у каталозі з системними юнітами /etc/systemd/system/ram-monitor.service

[Unit]
Description=RAM usage monitor

[Service]
ExecStart=/home/pervent/ram_monitor.sh
Restart=always
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && \
sudo systemctl enable ram-monitor.service && \
sudo systemctl start ram-monitor.service

Вивід нашого скрипта в systemd-journald

  1. Напишіть скрипт пошуку і заміни тексту в файлі. Він повинен приймати іменовані аргументи у вигляді шляху до файлу, тексту який знайти і тексту, на який замінити.
#!/bin/bash

help() {
  echo "Help: $0 -f <file> -s <search_text> -r <replace_text>"
  exit 1
}

file=""
search_text=""
replace_text=""

while [[ $# -gt 0 ]]; do
  key="$1"
  case $key in
    -f)
      file="$2"
      shift
      shift
      ;;
    -s)
      search_text="$2"
      shift
      shift
      ;;
    -r)
      replace_text="$2"
      shift
      shift
      ;;
    -h|--help)
      help
      ;;
    *)
      echo "Unknown argument: $key"
      *help*
      ;;
  esac
done

if [[ -z "$file" || -z "$search_text" || -z "$replace_text" ]]; then
  echo "Error: Missing arguments" >&2
  help
fi

if [[ ! -f "$file" ]]; then
  echo "Error: File $file not found" >&2
  exit 1
fi

sed -i "s/$search_text/$replace_text/g" "$file"

echo "Text replaced successfully in $file"   

![[./attachments/replace.sh]]

Перевіряємо як це працює:

  1. Зневадьте скрипт. Надайте виправлений код і опишіть знайдені помилки
#!/bin/bash

count_lines() {
    # Function to count lines in a file
    line_count=$(cat $filename | wc -l)
    echo "The file $filename has $line_count lines."
}

# Main script
filename=$1

if [ $filename == "" ]; then
    echo "Error: No filename provided." >&2
    exit 1
fi

if [ ! -f $filename ]; then
    echo "Error: File does not exist." >&2
    exit 1
fi

count_lines

case $line_count in
    0)
        echo "The file is empty!"
        ;;
    [1-9]*)
        echo "The file has some content!"
        ;;
    *)
        echo "Unexpected line count."
        ;;
esac
  • Неправильне використання ==, в рядку if [ $filename == "" ]; then. Можна використати два варіанти виправлення:
    1. if [[ $filename == "" ]]; then - Використати подвійні дужки, які підтримують такий варіант порівняння
    2. if [ -z "$filename" ]; then Більш універсальний варіант, перевірка через заповненність рядка
  • if [ ! -f $filename ]; then Додати лапки, а то пробіли в назві файла вилізуть в помилку. Або можна використати [[ ]].
  • line_count=$(cat $filename | wc -l) - Не є помилкою, але можна не виводити вміст файлу через cat, а одразу передати додатку wc. Мінус одна зайва операція, особливо, якщо файл має великий вміст.
  • І я додав би ще справку по використанню, як хороший тон, хоча вона тут і не дуже то і потрібга ;)
  • А в якості оптимізації, щоб не плодити і if і case і були читабільніше я би залишив щось одне
#!/bin/bash


# Function to count lines in a file
count_lines() {
    line_count=$(wc -l < $filename)
    if [[ "$line_count" -eq 0 ]]; then 
		echo "The file is empty!" 
	elif [[ "$line_count" -gt 0 ]]; then 
	    echo "The file $filename has $line_count lines."
	else 
		echo "Unexpected line count." 
	fi
}

help() { 
	echo "How-to use: $0 <filename>" 
    echo " <filename> The path to the file." 
    echo " -h, --help Display this help message."
	exit 1 
}

# Main script
filename=$1

if [[ "$1" == "-h" || "$1" == "--help" ]]; then 
	help 
fi

if [[ -z "$filename" ]]; then
    echo "Error: No filename provided." >&2
    help
fi

if [[ ! -f "$filename" ]]; then
    echo "Error: File "$filename" does not exist." >&2
    exit 1
fi

count_lines
  • Це неправда. Скільки насправді мережевих портів ви вже знаєте з модуля про мережі. Не перезаписуйте правильні знання цим числом.

Завдання з зірочкою

  1. Напишіть скрипт, який способом перебору шукатиме прості числа у проміжку від 1 до 1000. Бонусні очки якщо зможете його оптимізувати і зробити швидшим ніж просто перебір.
#!/bin/bash

check_simple() {
  local number=$1
  if [[ $number -le 1 ]]; then
    return 1
  fi
  for ((i=2; i<number; i++)); do
    if [[ $((number % i)) -eq 0 ]]; then
      return 1
    fi
  done
  return 0
}

for i in {2..1000}; do
  if check_simple $i; then
    echo $i
  fi
done

Якось це складно 🤔, без ChatGPT не обійтись 🤣

  1. Напишіть функцію, яка була б найбільш пессимальним (протилежним оптимальному) способом перелічити вміст поточної директорії. Для пессимізації не можна використовувати штучні затримки як-от sleep.

    Як на мене одним з найповільніших варіантів може бути вивід вмісту директорії з перебором всіх літер, по принципу, як працюють регулярні вирази. Але реалізувати це щось не вийшло =(