Циклы и массивы в Bash Linux с Задачей

Разбирались для задачи

Задача 3: Создайте реальный скрипт
text

Используя heredoc, создайте скрипт /tmp/cleanup.sh который:
1. Удаляет временные файлы старше 7 дней из /tmp
2. Очищает логи apache старше 30 дней
3. Проверяет свободное место на диске
4. Логирует свои действия в /var/log/cleanup.log

Решение тут

Массив какие бывают

Как посмотреть что внутри массива сразу все

echo ${my_array[@]}      # Все элементы массива

И по очереди (это скрипт)

for item in "${my_array[@]}";do
echo "$item"
done

На выходе мы получим если масив состоял из my_array=("apple" "banana" "cherry") или 
declare -A user_shells

user_shells=( ["root"]="/bin/bash" ["guest"]="/bin/sh" )
то на выходе

в первом случае 
будет так 
apple
banana
cherry
во втором случае с объявлением
будет вывод
/bin/bash
/bin/sh

как во втором случае добавить в масив вот так 
user_shells["admin"]="/usr/bin/zsh"

Как узнать размер массива

echo ${#my_array[@]}
вот этот символ # это он

Как пропарить массив 2 варианта и чтобы вывелось на экран или в команду

# Явное объявление (необязательно, но желательно)
declare -a my_array

# Присваивание значений
my_array=("apple" "banana" "cherry")

# Добавление элемента
my_array+=("date")
  • Сделали скрипт

#!/bin/bash
my_file=("file1.sh" "file2.sh" "sc.sh")

for file in "${!my_file[@]}"; do
        echo "$file"
        echo ${my_file[$file]}
        echo $file
        #echo "Сейчас был в цикле элемент массива "
done

5555

bash

echo ${my_array[0]}      # Первый элемент (apple)
echo ${my_array[@]}      # Все элементы массива
echo ${#my_array[@]}     # Количество элементов в массиве
echo ${!my_array[@]}     # Все индексы (0 1 2 3)

цвроыл

Вырезать часть массива (пропустить 1, взять 2):

bash

echo ${fruits[@]:1:2}  # banana cherry

Решение первого пункта задачи

#!/bin/bash

mapfile -t my_array < <(find . -type f -ctime -2 ! -name "clear.sh" -printf "%f\n")

for item in "${my_array[@]}";do
        echo "delete $item"
        rm "$item"
        echo "Udalili ++"
done

вот такой же но доработанный скрипт

#!/bin/bash

# 1. Оставляем полные пути (убираем -printf %f)
# 2. Используем -print0 для защиты от пробелов в именах
mapfile -d $'\0' -t my_array < <(find . -type f -ctime -2 ! -name "clear.sh" -print0)

# Проверка, есть ли что удалять
if [ ${#my_array[@]} -eq 0 ]; then
    echo "Свежих файлов для удаления не найдено."
    exit 0
fi

for item in "${my_array[@]}"; do
    # Проверяем, существует ли файл (на всякий случай)
    if [ -f "$item" ]; then
        echo "Удаляю: $item"
        rm "$item"
    fi
done

echo "Готово. Удалено файлов: ${#my_array[@]}"

Вопрос

что делают дополнительные атрибуты, почему нельзя просто -t а тут стоит mapfile -d $’\0′ и вот второй вопрос что делает -print0 именно 0

Ответ

1. Почему нельзя просто mapfile -t, а нужно -d $'\0'?

По умолчанию mapfile -t считает, что один элемент массива заканчивается там, где стоит перенос строки (\n).

В чем подвох:
В Linux (хоть это и плохая практика) имя файла может содержать перенос строки. Если у вас есть файл с именем "My\nFile.txt", то mapfile -t создаст два элемента массива: My и File.txt. Скрипт попытается удалить их по отдельности и выдаст ошибку, либо (что хуже) удалит не то.

  • -d — это флаг разделителя (delimiter).
  • $'\0' — это нулевой байт (null terminator).

В Linux нулевой байт — это единственный символ, который физически запрещен в именах файлов. Поэтому это единственный на 100% надежный разделитель. Мы говорим массиву: «Считай, что файл закончился только тогда, когда встретишь \0, а не обычный Enter».


2. Что делает -print0 (и именно цифра 0)?

По умолчанию find после каждого найденного имени печатает невидимый символ \n (перенос строки).

  • -print0 заставляет find вместо переноса строки лепить в конце каждого имени тот самый нулевой байт (\0).

Как это работает в связке:

  1. find -print0 выплевывает: file1.txt\0file2.txt\0file с пробелом.txt\0
  2. mapfile -d $'\0' ловит этот поток и аккуратно разрезает его строго по этим \0.

Почему именно 0?
Это стандарт в программировании (C, Bash, Python). Нулевой байт (ASCII 0) используется как «стоп-сигнал». Он не является буквой, цифрой или пробелом. Его невозможно напечатать на клавиатуре, и его не может быть внутри имени файла.

Итог:

Если вы на 200% уверены, что в ваших именах файлов никогда не будет пробелов и странных символов, можно использовать mapfile -t.

Но если вы пишете скрипт, который должен работать всегда и везде (best practice), то связка find -print0 + mapfile -d $'\0' — это единственный способ гарантировать, что скрипт не сойдет с ума, встретив файл "отчет за 2024 год.pdf".

Разбор

именно $

Шаг 1: Что такое $ в этом месте?

Здесь $ — это «ключ» или «команда» для баша, которая говорит:

«Эй, bash! То, что в кавычках — это не просто текст, а специальный код!»

Без $ bash думал бы, что '\0' — это просто текст из 3 символов:

  • символ '
  • символ \
  • символ 0
  • символ '

Шаг 2: Что делает $'\0'?

$'\0' превращает эти 4 символа в один специальный символ — НУЛЕВОЙ символ.

Представьте это так:

  • '\0' = просто текст (как написать на бумаге слово «ноль»)
  • $'\0' = реальный нулевой символ (как нажать кнопку «ноль» на калькуляторе)

Шаг 3: Как выглядит нулевой символ?

В компьютере нулевой символ:

  • Это символ с кодом 0
  • Он невидимый (как пробел, но еще более «ничего»)
  • Он используется как разделитель между именами файлов

Шаг 4: Зачем он нужен в -d $'\0'?

Команда mapfile читает список файлов и должна знать: «где заканчивается одно имя файла и начинается другое?»

Обычно используют:

  • Пробел — но если в имени файла есть пробел, все сломается
  • Перевод строки — но если в имени файла есть перевод строки (редко, но бывает), тоже сломается

Нулевой символ \0 — это идеальный разделитель, потому что:

  1. Он НИКОГДА не может быть в имени файла (в Linux запрещено)
  2. Он абсолютно надежен

Шаг 5: Аналог из реальной жизни

Представьте, что у вас есть список продуктов для покупки:

Обычный способ (с пробелами):

text

молоко яйца хлеб с маслом сыр

Проблема: «хлеб с маслом» — это один товар или два? Непонятно!

Способ с нулевыми символами:

text

молоко\0яйца\0хлеб с маслом\0сыр\0

Компьютер точно знает: после каждого \0 — новый товар!

Шаг 6: Что делает вся строка?

bash

mapfile -d $'\0' -t my_array < <(find ... -print0)

Разберем по кусочкам:

  1. find ... -print0 — команда find ищет файлы и выводит их имена, разделяя нулевыми символами (\0)
    Пример вывода: ./file1\0./doc file.txt\0./photo.jpg\0
  2. -d $'\0' — говорит mapfile:
    «Читай данные, разделяя их по нулевому символу (\0), а не по переводу строки»
  3. -t — «обрезай переводы строк в конце каждого имени файла»
  4. my_array — складывай все имена файлов в массив с этим именем

Шаг 7: Что было бы БЕЗ $'\0'?

Без $ bash не поймет, что '\0' — это нулевой символ:

bash

# Так НЕ РАБОТАЕТ:
mapfile -d '\0' -t my_array < <(find ... -print0)

mapfile будет искать разделителем текст \0 (символ \ и символ 0), а не нулевой символ. Команда find выводит нулевые символы, а mapfile ищет текст \0 — они не совпадают!

Шаг 8: Простой тест для понимания

Создайте файл и попробуйте:

bash

# Создаем файл с нулевыми символами
echo -e "первый\0второй с пробелом\0третий" > test.txt

# Смотрим содержимое (нули не видны)
cat test.txt
# Вывод: первыйвторой с пробеломтретий (все слитно!)

# Теперь читаем правильно:
mapfile -d $'\0' -t arr < test.txt
echo "Элементов: ${#arr[@]}"
# Вывод: Элементов: 3 (ура! три отдельных элемента)

📌 Итог простыми словами:

$ в $'\0' — это «переводчик» или «преобразователь»:

  • Без $'\0' = просто текст «обратный слэш-ноль»
  • С $$'\0' = реальный нулевой символ (ASCII код 0)

Это нужно, чтобы:

  1. find (с флагом -print0) выводил имена файлов, разделяя их нулями
  2. mapfile (с флагом -d $'\0') понимал, что разделитель — нули, а не переводы строк
  3. Скрипт корректно работал с любыми именами файлов, даже с пробелами или спецсимволами

Короче: $ превращает текст '\0' в настоящий нулевой символ, который безопасно разделяет

Оставьте комментарий