Linux для админа Windows 12. Bash-скрипты: от первой строки до надёжного скрипта
Note
Материал сверен с официальной документацией (Ubuntu Server, man-страницы и документация проектов; ссылки — в конце статьи). Примеры вывода — иллюстрации: на вашем сервере значения и формат могут отличаться. Перед работой на боевом сервере проверьте команды на тестовой машине.
Повторяющуюся работу администратор Windows пишет на PowerShell. В Linux для этого есть скрипт: текстовый файл с командами, который оболочка bash выполняет по порядку. Любую команду из статьи про командную строку можно вставить в скрипт. Сложность в другом: скрипт запускают без вас, по расписанию и от имени root, поэтому его ошибки дорого стоят. Пустая переменная в команде rm, недописанная копия или два запуска одновременно легко ломают сервер.
В статье вы научитесь:
- создавать скрипт и запускать его;
- работать с переменными, аргументами, условиями, циклами и функциями;
- включать защитные режимы
set -euo pipefailи знать, где они не срабатывают; - убирать за собой (
trap,mktemp), не запускать скрипт дважды (flock) и писать в журнал (logger); - проверять скрипты (
bash -n,bash -x,shellcheck).
Закрепим двумя скриптами: проверка свободного места и копия каталога с ротацией.
| Задача | PowerShell | bash |
|---|---|---|
| Переменная | $name = 'x' |
name=x (без пробелов вокруг =) |
| Значение переменной | $name |
"$name" (в кавычках) |
| Результат команды в переменную | $d = Get-Date |
d=$(date) |
| Аргументы скрипта | $args, param() |
$1, "$@", $# |
| Код возврата | $LASTEXITCODE |
$? |
| Условие | if ($a -eq 5) { } |
if [[ $a -eq 5 ]]; then … fi |
| Сравнение строк | -eq без учёта регистра |
== с учётом регистра |
| Остановка при ошибке | $ErrorActionPreference = 'Stop' |
set -e (по смыслу похоже, но не тождественно) |
| Запрет неизвестных переменных | Set-StrictMode |
set -u |
| Очистка при выходе | try … finally |
trap … EXIT |
| Между командами | объекты | текст |
Warning
Скрипты ниже создают и удаляют файлы. Отрабатывайте их на тестовой машине в своём домашнем каталоге (~/lab), а на боевом сервере запускайте только после проверки на копии данных.
Что нужно знать заранее
- Командная строка Linux: переменные, кавычки, перенаправление, конвейеры,
PATH. - Файловая система, диски и права доступа:
chmod,umask, владельцы. - Службы и процессы: systemd, cron: как запускать скрипты по расписанию.
Примеры рассчитаны на Ubuntu 24.04 LTS (bash 5.2.21); в 26.04 LTS пакет новее: проверьте apt policy bash и документацию выпуска.
Первый скрипт
Создайте файл hello.sh:
#!/bin/bash
name=${1:-мир}
echo "Привет, $name! Сегодня $(date +%F)."
Первая строка, шебанг (shebang), сообщает системе, чем выполнять файл. Допустимы два варианта. #!/bin/bash указывает путь прямо. #!/usr/bin/env bash ищет bash в PATH: так принято писать переносимые скрипты. В системных скриптах на Ubuntu обычно ставят #!/bin/bash: при запуске из cron и systemd окружение беднее, чем в вашем терминале, и результат не должен зависеть от PATH. Мы используем этот вариант.
Скрипт запускают двумя способами:
bash hello.sh
chmod +x hello.sh
./hello.sh Анна
Для ./hello.sh нужен бит выполнения, а имя пишут с путём, потому что текущего каталога в PATH нет. Готовые скрипты администратора принято класть в /usr/local/bin (по FHS, стандарту размещения файлов, это каталог для локально установленных программ), а нужные только root — в /usr/local/sbin. Скрипт, который запускает root, должен принадлежать root и не быть доступным для записи другим: иначе любой, кто его изменит, получит права root при следующем запуске.
sudo cp hello.sh /usr/local/bin/hello
sudo chown root:root /usr/local/bin/hello
sudo chmod 755 /usr/local/bin/hello
Переменные, кавычки, подстановки
Переменную задают как name=значение (без пробелов вокруг =; имя состоит из латинских букв, цифр и _), читают как $name. Главное правило: всегда пишите "$name" в кавычках. Без кавычек bash разбивает результат подстановки на слова по символам из переменной IFS (Internal Field Separator; по умолчанию это пробел, табуляция и перевод строки) и раскрывает в нём *, ? и [. Имя файла «my file.txt» превратится в два аргумента:
f="my file.txt"
ls -l $f
ls -l "$f"
Первая команда ищет файлы my и file.txt, вторая работает с одним файлом. В одинарных кавычках всё остаётся буквальным, в двойных подставляются $name, $(команда) и обратные кавычки.
Полезные формы подстановки:
| Запись | Значение |
|---|---|
$(команда) |
вывод команды без завершающих переводов строки |
${name:-запас} |
значение или «запас», если переменная пуста или не задана |
${name:?сообщение} |
если переменная пуста или не задана, вывести сообщение и завершить скрипт |
${name%.txt} |
значение без суффикса .txt |
$((a + b)) |
арифметика |
Форма ${name:?} выручит там, где пустая переменная опасна (ниже она защищает rm).
Аргументы, ввод и коды возврата
Аргументы скрипта лежат в $1, $2 и так далее, их число в $#, имя скрипта в $0, все аргументы в "$@". Именно "$@" в кавычках передаёт каждый аргумент отдельным словом, даже с пробелами. Ввод пользователя читает read; опция -r отключает обработку обратного слэша:
read -r -p 'Продолжить? [y/N] ' answer
[[ $answer == [yY] ]] || exit 1
Если включён set -u (о нём ниже), необязательный аргумент нужно читать как ${1:-} или с запасным значением, как ${1:-мир} в hello.sh: иначе его отсутствие станет ошибкой.
Каждая команда завершается с кодом возврата: 0 значит успех, остальное ошибку; последний код лежит в $?. Код 127 значит «команда не найдена», 126 «не исполняется». Коды удобно комбинировать: a && b запускает b только после успеха a, a || b после неудачи. Свой код скрипт задаёт командой exit N; соглашение такое: 0 — всё хорошо, 1 — ошибка выполнения, 2 — неверные аргументы.
Условия
Для проверок есть два синтаксиса: старая команда [ … ] (она же test) и конструкция bash [[ … ]]. Используйте [[ ]]: внутри неё нет разбиения на слова и раскрытия шаблонов, поэтому пустая переменная и пробелы не ломают проверку, а ещё доступны &&, || и сравнение с шаблоном.
if [[ -d $dir && -w $dir ]]; then
echo "каталог доступен для записи"
elif [[ -e $dir ]]; then
echo "это не каталог или нет прав"
fi
| Проверка | Смысл |
|---|---|
-e путь, -f, -d |
существует; обычный файл; каталог |
-r, -w, -x |
можно читать, писать, выполнять |
-s путь |
файл не пуст |
-z строка, -n строка |
строка пуста; не пуста |
a == b, a != b |
строки равны; не равны (с учётом регистра) |
a -eq b, -ne, -lt, -le, -gt, -ge |
сравнение чисел |
строка =~ regex |
совпадение с регулярным выражением |
Справа от == внутри [[ ]] стоит шаблон: [[ $f == *.log ]] проверяет окончание. Чтобы сравнить буквально, берите правую часть в кавычки. В PowerShell -eq для строк не различает регистр, в bash == различает: [[ $s == abc ]] для ABC вернёт ложь.
Циклы, case, функции, массивы
for f in "$dir"/*.log; do # по файлам
[[ -e $f ]] || continue # нет файлов: шаблон не раскрылся
echo "$f"
done
while IFS= read -r line; do # по строкам файла
echo "строка: $line"
done < /etc/hostname
case $answer in
[yY]|[yY]es) echo "да" ;;
*) echo "нет" ;;
esac
log() { # функция
local msg=$1 # local: видна в функции и вызванных
printf '%s %s\n' "$(date '+%F %T')" "$msg"
}
files=(a.txt "b c.txt") # массив
echo "элементов: ${#files[@]}"
for f in "${files[@]}"; do echo "$f"; done
IFS= перед read сохраняет пробелы по краям строки. "${files[@]}" в кавычках раскрывается в отдельные слова без разбиения, это способ хранить список имён с пробелами. Функция возвращает код (return N), а не значение: результат передают через вывод и $(…).
Текст вместо объектов
Чем текст bash отличается от объектов PowerShell, разобрано в статье про командную строку. Для скриптов отсюда три привычки. Не разбирайте вывод ls: берите шаблоны ("$dir"/*.log), find или команды с выбором полей (в check-disk.sh ниже это df --output=pcent,target). Помните, что ошибка здесь не объект, а число, код возврата. Проверяйте это число сами или поручайте режиму set -e.
Надёжный скрипт
set -euo pipefail
Сразу после шебанга пишут три защитных режима. Каждый делает одну вещь:
-e(errexit): завершить скрипт, если команда вернула ненулевой код;-u(nounset): неизвестная переменная считается ошибкой, а не пустой строкой;-o pipefail: код конвейера равен коду самой правой из упавших команд, а не просто последней команды (по умолчанию дляfalse | trueполучится0).
Режимы полезны, но не волшебны. Особенно -e: у него много исключений, и о них нужно знать.
- Условия.
-eне срабатывает, если команда стоит в проверкеifилиelif, в условииwhileилиuntil, в списке&&и||(кроме последней команды) или инвертирована!. - Функции в условии. Если функция вызвана там, где
-eигнорируется (например,if check; then), он не действует во всём её теле: после упавшей команды функция продолжит работу. - Подстановки. По умолчанию
$(команда)не наследует-e: команда внутри продолжит после ошибки. Это меняетshopt -s inherit_errexit. localиexport. Вlocal out=$(cmd)код возвратаcmdтеряется: результат берётся уlocal. Объявляйте и присваивайте раздельно.- Счётчик.
((count++))приcount=0вычисляет0, и команда((…))возвращает код1, скрипт с-eзавершится. Пишитеcount=$((count + 1)). - Подоболочки. Подоболочка (subshell) — дочерний экземпляр bash: в ней выполняются
$(…)и команды конвейера. Режим-eприменяется к скрипту и к каждой подоболочке отдельно, и подоболочка может завершиться раньше, чем дойдёт до последней команды. - Конвейеры и
grep. Безpipefailошибка не последней команды конвейера остаётся незамеченной.grepбез совпадений возвращает1: под-eодиночныйgrepзавершит скрипт, а подpipefailи конвейер вродеgrep … | wc -l. Нужно:grep -q шаблон файл || trueили проверка вif.
f() { local out; out=$(date); echo "$out"; } # а не: local out=$(date)
count=$((count + 1)) # а не: ((count++))
[[ -r $log ]] || exit 1 # нет файла: grep вернул бы 2
grep -q ошибка "$log" || echo "ошибок нет" # код 1 - это не сбой
trap, mktemp, flock, logger
trap 'команды' EXIT выполняет команды при выходе из скрипта, в том числе после ошибки при -e (но не при kill -9). Это место для очистки. Временные файлы создают mktemp: он выбирает уникальное имя и выдаёт права только владельцу; предсказуемые имена вроде /tmp/work небезопасны. Опция -d создаёт каталог, -p КАТАЛОГ выбирает место.
tmp=$(mktemp)
trap 'rm -f -- "$tmp"' EXIT
flock не даёт запустить скрипт дважды. Блокировку держит открытый файл, пока скрипт работает. Номер, под которым процесс держит открытый файл, называется файловым дескриптором:
exec 9>/var/lock/myjob.lock # открыли файл под номером 9 (от root)
flock -n 9 || { echo "уже запущен" >&2; exit 1; }
Опция -n не ждёт, а сразу завершается неудачей, если блокировка занята. Держите файл блокировки на локальном диске: на NFS и CIFS flock может работать ограниченно. logger пишет в системный журнал; -t задаёт метку, -p приоритет (user.warning), по умолчанию user.notice. Прочитать запись можно по метке: sudo journalctl -t метка.
Для разбора опций (-t 90) есть встроенная команда getopts: строка ':t:' объявляет опцию -t с аргументом, значение попадает в OPTARG, а двоеточие в начале включает тихий режим, где ошибки разбора вы обрабатываете сами.
Практика 1: проверка свободного места
Скрипт check-disk.sh печатает файловые системы, заполненные не меньше порога, пишет об этом в журнал и завершается кодом 1, если порог превышен (код 2 — неверные аргументы, 3 — сбой df). df --output=pcent,target печатает только процент и точку монтирования, а -x исключает типы файловых систем (образы squashfs только для чтения всегда заполнены на 100 %).
#!/bin/bash
# check-disk.sh - сообщает о файловых системах выше порога заполнения.
set -euo pipefail
usage() {
echo "Использование: ${0##*/} [-t ПОРОГ_В_ПРОЦЕНТАХ]" >&2
exit 2
}
threshold=85
while getopts ':t:' opt; do
case $opt in
t) threshold=$OPTARG ;;
*) usage ;;
esac
done
shift $((OPTIND - 1))
(( $# == 0 )) || usage # лишние аргументы - ошибка
if ! [[ $threshold =~ ^[1-9][0-9]*$ ]] || (( threshold > 100 )); then
echo "Порог должен быть целым числом от 1 до 100" >&2
exit 2
fi
# Сбой df - отдельный код 3: мониторинг отличит его от превышения порога.
out=$(df --output=pcent,target -x tmpfs -x devtmpfs -x squashfs) || {
echo "df завершился с ошибкой" >&2
exit 3
}
status=0
while read -r pcent target; do
used=${pcent%\%} # убрали знак % в конце
[[ $used =~ ^[0-9]+$ ]] || continue # пропуск заголовка и строк без числа
if (( used >= threshold )); then
msg="Заполнено ${used}%: ${target} (порог ${threshold}%)"
echo "$msg"
logger -t check-disk -p user.warning -- "$msg"
status=1
fi
done <<< "$out"
exit "$status"
Вывод df сохраняется в переменную, а сбой df обрабатывается явно и получает собственный код, который мониторинг отличит от превышения порога. При чтении через < <(df …) код df терялся бы, и скрипт молча сообщал бы «всё в порядке». Цикл получает текст через <<< "$out" (here-string, строка в роли ввода): так while работает в текущей оболочке, а не в подоболочке, и значение status сохраняется. Строка заголовка Use% Mounted on отсеивается проверкой на число. После цикла getopts команда shift убирает разобранные опции, а лишние аргументы (например, ./check-disk.sh 90 без -t) считаются ошибкой, а не игнорируются молча. Запустите с низким порогом, чтобы увидеть срабатывание, затем проверьте код возврата и журнал:
chmod +x check-disk.sh
./check-disk.sh -t 10; echo "код: $?"
sudo journalctl -t check-disk --since today
Пример вывода:
Заполнено 16%: / (порог 10%)
код: 1
Письмо с сервера отправляется только при настроенной почтовой службе (отдельная тема); без неё читайте журнал или подключите внешнюю систему мониторинга (Zabbix с нуля). Для запуска от root скрипт кладут в /usr/local/sbin (владелец root, права 755), а расписание (раз в час) задают cron или таймером systemd, как в статье 06.
Практика 2: копия каталога с ротацией
Скрипт backup-dir.sh собирает архив каталога под именем ИМЯ-дата.tar.gz и хранит последние N копий. Три правила делают его безопасным: архив сначала пишется во временный файл и переименовывается только после успеха; старые копии удаляются после создания новой; удаляются только файлы, подходящие под точный шаблон имени.
#!/bin/bash
# backup-dir.sh - архив каталога с ротацией старых копий.
# Использование: backup-dir.sh ИМЯ ИСТОЧНИК КАТАЛОГ_КОПИЙ [СКОЛЬКО_ХРАНИТЬ]
set -euo pipefail
umask 077 # архивы читает только владелец
name=${1:?Не указано имя копии}
src=${2:?Не указан каталог-источник}
dest=${3:?Не указан каталог для копий}
keep=${4:-7}
[[ $name =~ ^[A-Za-z0-9._-]+$ ]] || { echo "Недопустимое имя" >&2; exit 2; }
[[ $keep =~ ^[1-9][0-9]*$ ]] || { echo "Нужно число копий" >&2; exit 2; }
[[ -d $src ]] || { echo "Нет каталога: $src" >&2; exit 1; }
mkdir -p -- "$dest"
# Одна копия за раз: блокировка живёт, пока открыт дескриптор 9.
exec 9>"$dest/.$name.lock"
flock -n 9 || { echo "Копия $name уже выполняется" >&2; exit 1; }
archive="$dest/$name-$(date +%F_%H%M%S).tar.gz"
tmp=$(mktemp -p "$dest" ".$name.XXXXXX")
trap 'rm -f -- "$tmp"' EXIT # при выходе убираем временный файл
tar -czf "$tmp" -C "$src" .
mv -- "$tmp" "$archive"
logger -t backup-dir -- "создан $archive"
echo "Создан $archive"
# Ротация: только после успешной копии, по шаблону имени.
shopt -s nullglob
files=( "$dest/$name"-????-??-??_??????.tar.gz )
extra=$(( ${#files[@]} - keep ))
if (( extra > 0 )); then
for old in "${files[@]:0:extra}"; do
rm -- "$old"
logger -t backup-dir -- "удалена старая копия $old"
done
fi
Что здесь происходит:
${1:?…}останавливает скрипт, если аргумент не передан; имя копии проверяется регулярным выражением, потому что оно попадает в шаблон дляrm.exec 9>файлиflock -n 9не дают двум запускам работать одновременно.- Дата в имени записана как
ГГГГ-ММ-ДД_ЧЧММСС, поэтому алфавитный порядок совпадает с хронологическим.shopt -s nullglobзаставляет шаблон без совпадений раскрываться в пустой список (а не оставаться строкой), а${files[@]:0:extra}берёт первыеextraэлементов, то есть самые старые. rmстоит без-rи-fи получает конкретные файлы после--. Каталог копий не должен лежать внутри источника, иначе архив попадёт сам в себя.
Подготовьте тестовые каталоги и запустите копирование:
chmod +x backup-dir.sh
mkdir -p ~/lab/src ~/lab/backups ~/lab/restore
echo test > ~/lab/src/a.txt
./backup-dir.sh docs ~/lab/src ~/lab/backups 3
ls -l ~/lab/backups
Повторите команду ./backup-dir.sh … несколько раз с паузой в секунду (имя архива содержит секунды) и снова посмотрите ls: останется не больше трёх копий.
Главная проверка копии — восстановление. Найдите архив в ls, замените имя в команде и распакуйте его в пустой каталог:
tar -tzf ~/lab/backups/docs-2026-11-04_131500.tar.gz
tar -xzf ~/lab/backups/docs-2026-11-04_131500.tar.gz -C ~/lab/restore
ls -l ~/lab/restore
Убрать учебные файлы: cd ~, затем ls -R lab и rm -r lab.
Для сравнения вот опасная запись, которую встречают в чужих скриптах:
rm -rf $dir/* # антипример: при пустой $dir станет rm -rf /*
rm -rf -- "${dir:?}"/* # защита: пустая $dir остановит скрипт
Защита ${dir:?} описана в Bash Manual, а shellcheck предупреждает о подобных записях (в том числе кодом SC2115). Но надёжнее вообще не удалять «всё под путём»: в примере выше удаляются только файлы по точному шаблону.
Отладка и проверка
bash -n скриптчитает файл и проверяет синтаксис, ничего не выполняя.bash -x скриптпечатает каждую команду после подстановок: видно, что реально было запущено. Чтобы трассировать только подозрительный участок, окружите его командамиset -xиset +x. В трассировку попадут и значения переменных, включая пароли: не показывайте и не сохраняйте такой вывод.shellcheck скриптищет типичные ошибки (кавычки,cdбез проверки, потерянные коды возврата) и выдаёт кодыSCxxxx, каждый описан в вики проекта. Ставится командойsudo apt install shellcheck. Прогоните через него оба скрипта: замечания полезно читать, а не просто подавлять.
Безопасность
- Не используйте
evalс чужими данными.evalвыполняет строку как команду, и значение от пользователя или из файла превращается в произвольную команду. Составные команды собирайте в массив и запускайте как"${cmd[@]}", варианты выбирайте черезcase. - Не храните пароли в скрипте и в аргументах. Аргументы видны в
psи истории. Секрет кладут в отдельный файл с правами600, принадлежащий пользователю скрипта, а скрипт читает его при запуске. - Ограничивайте права на создаваемое.
umask 077в начале скрипта делает новые файлы доступными только владельцу. - Не подключайте через
sourceфайлы, которые может править кто-то ещё:sourceвыполняет их содержимое как код. - Имена файлов — это данные. Используйте кавычки,
--перед именами и./перед относительными путями: файл-rfне должен стать опцией.
Идемпотентность
Скрипт идемпотентен, если повторный запуск даёт тот же результат и ничего не портит. Это свойство надёжнее любых напоминаний «не запускать дважды». Примеры: mkdir -p вместо mkdir; добавление строки в файл только при отсутствии: grep -qxF 'строка' файл || echo 'строка' >> файл; а в backup-dir.sh ротация оставляет заданное число копий, сколько бы раз вы ни запускали скрипт.
Типичные ошибки и недоразумения
- Переменная без кавычек.
rm $fileломается на пробелах и*. Всегда"$file". - Пробелы вокруг
=.name = xзапускает командуname. Пишитеname=x. [ $x == y ]с пустой$x. Старая[ ]получает неверное число аргументов. Используйте[[ ]].set -eкак страховка от всего. Он молчит в условиях, функциях вifи приlocal x=$(…), а((i++))при нуле, наоборот, неожиданно завершает скрипт. Проверяйте важные команды явно.cdбез проверки. Если каталога нет, дальнейшие команды работают в чужом месте: пишитеcd "$dir" || exit 1.- *`rm -rf $var/
.** Пустая переменная превращает команду в удаление корня. Используйте${var:?}` или точные шаблоны. - Конвейер с
while read. Изменения переменных в нём теряются (цикл идёт в подоболочке): читайте через<<< "$out", как вcheck-disk.sh, или через< <(команда)(но код этой команды-eне увидит). - Запуск по расписанию, а в терминале работало. Нет вашего
PATHи профиля: пишите полные пути и перенаправляйте вывод (статья 06). - Копия не проверена восстановлением. Пока вы не распаковали архив в пустой каталог, это не резервная копия.
Проверьте себя
- Почему переменную нужно писать как
"$file", а не$file? Без кавычек bash разбивает значение по пробелам и раскрывает `,?,[`: имя с пробелом превращается в несколько аргументов, а шаблон в список файлов. В кавычках значение остаётся одним словом.* - Что делает каждая часть
set -euo pipefail?-eзавершает скрипт при ошибке команды,-uсчитает неопределённую переменную ошибкой,pipefailделает код конвейера равным коду последней упавшей команды.-eне срабатывает в условияхif,while, списках&&/||и функциях, вызванных в таких местах. - Скрипт с
set -eзавершился на строке((count++)). Почему и как исправить? Приcount=0выражение равно нулю, и((…))возвращает код1. Пишитеcount=$((count + 1)). - Как защитить
rm -rf "$dir"/*от пустой переменной? Записать"${dir:?}": если переменная пуста или не задана, bash остановит скрипт с сообщением. Ещё лучше удалять только файлы по точному шаблону. - Зачем в
backup-dir.shархив пишется во временный файл, а ротация идёт после копирования? Если копирование прервётся, неполный файл не станет «копией» (trapего удалит), а старые копии не пострадают: сначала создаётся новая, потом удаляются лишние.
Что дальше
- Службы и процессы: systemd, journalctl, cron — запустить
check-disk.shиbackup-dir.shпо расписанию, таймером или cron. - Резервное копирование в Linux: tar, rsync и restic — готовые инструменты вместо собственных копий.
- Ansible для начинающих — когда скриптов становится много и серверов больше одного.
- Журналы: journald, rsyslog и logrotate — куда попадают записи
loggerи как не переполнить диск журналами.
Источники
- GNU Bash Reference Manual — шебанг, кавычки, подстановки,
[[ ]],set,trap,read,getopts, массивы (издание 5.3) - bash(1), Ubuntu 24.04 — bash 5.2.21: описание
set -eи его исключений - mktemp(1), Ubuntu 24.04 — безопасные временные файлы,
-d,-p - flock(1), Ubuntu 24.04 — блокировка из скрипта,
-n, замечание про NFS и CIFS - logger(1), Ubuntu 24.04 —
-t,-p, приоритет по умолчанию - df(1), Ubuntu 24.04 —
--output,-x - journalctl(1) — выбор записей по метке
-t - Filesystem Hierarchy Standard 3.0: /usr/local — каталог для локально установленных программ
- ShellCheck — назначение, установка, использование
- ShellCheck: SC2115 —
${var:?}передrm -rf - ShellCheck: SC2155 —
local x=$(cmd)теряет код возврата - Microsoft Learn: about_Comparison_Operators — строки в
-eqбез учёта регистра - Microsoft Learn: Set-StrictMode — запрет неинициализированных переменных
- Microsoft Learn: about_Preference_Variables —
$ErrorActionPreference - Microsoft Learn: about_Automatic_Variables —
$LASTEXITCODE