С чего начать Первые шаги трек · 15 ступеней
Linux Labs

Bash-скрипты: запуск, переменные, аргументы

8 мин Ubuntu 24.04 · bash 5.2

Скрипт — текстовый файл с командами оболочки: строка #! выбирает интерпретатор, аргументы приходят в $1, $2 и "$@".

Шпаргалка

8 команд Версия для печати
  • #!/bin/bash # первая строка: чем запускать
  • set -euo pipefail остановиться на первой ошибке
  • chmod u+x backup.sh разрешить запуск
  • ./backup.sh "my file.txt" запуск по пути
  • bash backup.sh запуск без права x
  • bash -n backup.sh проверить синтаксис, не запуская
  • name="value"; echo "$name" переменная — в двойных кавычках
  • for f in "$@"; do echo "$f"; done все аргументы по одному

Обзор

Скрипт — текстовый файл с командами, которые ты набрал бы в терминале. Читает и выполняет его интерпретатор; у bash-скрипта это bash.

Запуск

Команда Что нужно Кто выбирает интерпретатор
./backup.sh право x строка #!
backup.sh право x и каталог скрипта в PATH строка #!
bash backup.sh право r ты: bash

Строка #! (shebang) — первая строка вида #!/bin/bash. По ней ядро узнаёт, какой программе отдать файл, когда его запускают как команду. При bash backup.sh интерпретатор уже выбран, и эта строка — комментарий.

sh на Ubuntu — это dash: ls -l /bin/sh покажет /bin/sh -> dash. Он понимает только синтаксис POSIX, и bash-скрипт, запущенный через sh, падает:

text
student@ubuntu:~$ sh b.sh x
b.sh: 2: [[: not found
b.sh: 3: Syntax error: "(" unexpected

Чтобы скрипт запускался по имени, без ./, положи его в ~/bin: если этот каталог есть, ~/.profile добавляет его в PATH при входе. Новый каталог заработает со следующего входа.

Переменные и кавычки

имя=значение пишется без пробелов вокруг =. Строку name = "x" bash читает как команду name с аргументами: name: command not found.

$имя подставляет значение. Без кавычек оболочка делит его по пробелам — разбиение на слова — и раскрывает шаблоны вроде *:

text
student@ubuntu:~$ f="my file.txt"; ls $f
ls: cannot access 'my': No such file or directory
ls: cannot access 'file.txt': No such file or directory
student@ubuntu:~$ ls "$f"
my file.txt

В двойных кавычках значение остаётся одним аргументом, а $имя и $(команда) подставляются. В одинарных кавычках текст берётся буквально. $(команда) подставляет вывод команды: today=$(date +%F).

Аргументы

Аргументы скрипта приходят в позиционных параметрах:

Запись Что это
$0 имя скрипта, как его вызвали
$1, $2… первый, второй аргумент
$# число аргументов
"$@" все аргументы, каждый — отдельным словом
"$*" все аргументы одной строкой через пробел
text
student@ubuntu:~$ cat args.sh
#!/bin/bash
echo "args: $#"
for a in "$@"; do echo "[$a]"; done
student@ubuntu:~$ ./args.sh "my file.txt" two
args: 2
[my file.txt]
[two]

Без кавычек $@ и $* снова делят my file.txt на два слова.

Строгий режим

set -euo pipefail в начале скрипта:

  • -e — выйти, как только команда вернула не 0; проверки в if, &&, || скрипт не останавливают;
  • -u — ошибка на необъявленной переменной: опечатка в имени не превратится в пустую строку, скрипт выйдет с кодом 1;
  • -o pipefail — конвейер падает, если упало любое его звено.

Что значат коды и как их проверять — в exit-codes.

Рецепты

4

Условия и циклы

bash
if [ -f "$file" ]; then
  echo "файл есть"
elif [ -d "$file" ]; then
  echo "это каталог"
else
  echo "нет такого: $file" >&2
fi

for f in /var/log/*.log; do echo "$f"; done
while read -r host; do ping -c1 "$host"; done < hosts.txt

Проверки [ -f ], [ -z ], -eq — в test-conditions.

Значение по умолчанию и части пути

bash
dir="${1:-/srv/backup}"     # первый аргумент или /srv/backup
f=/var/log/app.log
echo "${f##*/}"             # app.log — имя без каталога
echo "${f%.log}"            # /var/log/app — без расширения
echo "${f%/*}"              # /var/log — каталог

Проверить синтаксис, не запуская

text
student@ubuntu:~$ bash -n syn.sh; echo "rc=$?"
syn.sh: line 5: syntax error: unexpected end of file
rc=2

Глубже проверяет shellcheck: кавычки, cd без проверки, $?.

Файлы с пробелами в именах

bash
for f in /srv/data/*.csv; do gzip "$f"; done   # шаблон вместо ls
find /srv/data -name '*.csv' -exec gzip {} +   # с подкаталогами

Цикл по выводу ls или find в $( ) делит имена по пробелам. Шаблон и find -exec передают каждое имя целиком (см. xargs для -print0).

Неполадки

9

Permission denied

bash: ./backup.sh: Permission denied, код 126: у файла нет права x. Выполни chmod u+x backup.sh или запусти bash backup.sh.

command not found

backup.sh: command not found, код 127: имя без пути оболочка ищет только в каталогах PATH. Запусти ./backup.sh или положи скрипт в ~/bin.

cannot execute: required file not found

bash: ./backup.sh: cannot execute: required file not found — ядро не нашло интерпретатор из строки #!. Либо опечатка в пути, либо файл пришёл из Windows с концами строк CRLF: file backup.sh напишет with CRLF line terminators. Убери \r: sed -i 's/\r$//' backup.sh.

[[: not found, Syntax error: "(" unexpected

Bash-скрипт выполнил sh (dash): его запустили как sh backup.sh или в строке #! стоит /bin/sh. Запускай ./backup.sh с #!/bin/bash или bash backup.sh.

name: command not found в строке с =

Пробелы вокруг =: name = "x". Пиши name="x".

unbound variable

backup.sh: line 3: name: unbound variable — set -u поймал переменную без значения, часто опечатку. Исправь имя или дай значение по умолчанию: "${name:-}".

syntax error: unexpected end of file

Не закрыт блок или кавычка: if без fi, do без done, лишняя ". Номер строки указывает на конец файла — ищи выше.

Скрипт ломается на имени с пробелом

Переменная без кавычек: cp $f /backup/ получает два аргумента вместо одного. Пиши "$f", "$@"; shellcheck найдёт такие места (SC2086).

Руками работает, по расписанию — нет

У cron и служб systemd другое окружение: короткий PATH, каталог /, нет переменных из ~/.bashrc. Подробно — в окружении службы.

Опасно

2

rm с пустой переменной

rm -rf "$dir/"* при пустой dir превращается в rm -rf /*. Ставь set -u или пиши "${dir:?}" — пустое значение остановит скрипт с ошибкой.

cd без проверки

cd /srv/tmp; rm -rf ./* удалит файлы в текущем каталоге, если cd не сработал. Пиши cd /srv/tmp || exit 1.

Термины

6
shebang (строка
Первая строка скрипта вида #!/bin/bash: по ней ядро выбирает интерпретатор, когда файл запускают как программу. При запуске «bash файл» интерпретатор уже выбран, и строка — просто комментарий. Ещё встречается в: Права доступа
интерпретатор interpreter
Программа, которая читает скрипт и выполняет его команды по очереди: bash, sh, python3. На Ubuntu sh — это dash, у него меньше возможностей, чем у bash. Ещё встречается в: Capabilities: права root по частям, auditd, cat, pgrep
разбиение на слова word splitting
Шаг разбора команды: значение $имя без кавычек оболочка делит по пробелам на отдельные аргументы. В двойных кавычках "$имя" остаётся одним аргументом. Ещё встречается в: shellcheck
позиционный параметр positional parameter
Аргумент скрипта по номеру: $1, $2 и дальше; $# — число аргументов, "$@" — все аргументы, каждый отдельным словом.
set -e (errexit)
Режим bash: скрипт выходит, как только команда вернула не 0. Проверки в if и команды слева от && и || его не включают. Ещё встречается в: Коды завершения: $?, && и ||, flock, wait
pipefail
Режим bash (set -o pipefail): конвейер возвращает код упавшего звена, а без него — код последнего звена, даже если раньше что-то упало. Ещё встречается в: Коды завершения: $?, && и ||, alias, Ansible: инвентарь, модули и разовые команды, bash и ещё 6

См. также

  • exit-codes — коды завершения, &&, ||, exit.
  • test-conditions — проверки файлов, строк и чисел.
  • shellcheck — найти ошибки до запуска.
  • env-vars — PATH и переменные окружения.
  • command-syntax — кавычки и аргументы в командной строке.
  • redirection — вывод в файл и в поток ошибок >&2.
  • flock — защита от двойного запуска.
  • bash — режимы оболочки и файлы запуска.
Загрузка…