Вы когда-нибудь пытались получить доступ к каталогу вашего скрипта Bash программным способом?
Это распространенный вопрос, который задают люди, и сделать это можно по-разному.
Первое, на что следует обратить внимание при решении этой проблемы, — это переменная $0, используемая в Bash для хранения первого элемента выполненной команды.
Создайте скрипт с именем get_script_dir.sh
в каталоге /opt/scripts/:
#!/bin/bash
echo "$0"
Если мы это выполним:
- Из каталога скриптов (/opt/scripts/)
- Используя относительный путь из родительского каталога /opt
- Используя абсолютный путь скрипта
Получаем следующее:
1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 ~]$./test.sh
./test.sh
2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh
scripts/get_script_dir.sh
3) Absolute path
[ec2-user@ip-172-12-20-120 ~]$ /opt/scripts/get_script_dir.sh
/opt/scripts/get_script_dir.sh
Но этого недостаточно, поскольку только в сценарии 3) мы получаем полный путь к скрипту.
Прежде всего, чтобы найти решение, нам нужно ввести команду dirname
, которая удаляет последний компонент из имени файла. Мы обновляем наш скрипт, добавляя dirname $0
:
#!/bin/bash
echo "$0"
dirname "$0"
А вот вывод скрипта в трех сценариях, показанных ранее:
1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 scripts]$./get_script_dir.sh
./get_script_dir.sh
.
2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 scripts]$ cd..
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh
scripts/get_script_dir.sh
scripts
3) Absolute path
[ec2-user@ip-172-12-20-120 opt]$ /opt/scripts/get_script_dir.sh
/opt/scripts/get_script_dir.sh
/opt/scripts
Таким образом, dirname позволяет в сценарии 3) получить каталог скрипта.
Очевидно, что этот подход не работает должным образом, нам нужна команда, которая возвращает полный путь к скрипту при его выполнении из любого каталога (каталог скрипта, относительный путь и абсолютный путь).
Давайте также проверим, как $0 ведет себя с командой source
, очень распространенной в скриптах Bash для выполнения строк в скрипте:
[ec2-user@ip-172-12-20-120 scripts]$ source get_script_dir.sh
-bash
На этот раз мы не возвращаем путь к скрипту, а просто -bash
.
Это не работает так, как нам хотелось бы, нам нужно найти альтернативу.
Массив BASH_SOURCE
Массив $BASH_SOURCE представляет собой альтернативу $0, которая намного более надежна.
Я не хочу вдаваться в подробности о $BASH_SOURCE, которые на данном этапе могут оказаться довольно запутанными.
Единственное, что сейчас имеет значение, это то, что $BASH_SOURCE всегда содержит имя и путь к исполняемому скрипту. Как мы уже видели, это справедливо для $0 только тогда, когда скрипт не является исходным (используя команду source).
С $BASH_SOURCE наш скрипт становится следующим:
#!/bin/bash
echo "${BASH_SOURCE[0]}"
dirname "${BASH_SOURCE[0]}"
Где в ${BASH_SOURCE[0]}
находится первый элемент массива BASH_SOURCE.
Давайте посмотрим, как он поведет себя в трех сценариях, которые мы проанализировали ранее:
1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 scripts]$./get_script_dir.sh
./get_script_dir.sh
.
2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 scripts]$ cd..
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh
scripts/get_script_dir.sh
scripts
3) Absolute path
[ec2-user@ip-172-12-20-120 opt]$ /opt/scripts/get_script_dir.sh
/opt/scripts/get_script_dir.sh
/opt/scripts
Итак, по сравнению с $0 никаких изменений нет… так в чем смысл использования BASH_SOURCE?
Что произойдет, если мы воспользуемся источником сценария тем же способом, которым мы делали это раньше?
[ec2-user@ip-172-12-20-120 scripts]$ source get_script_dir.sh
get_script_dir.sh
.
Замечательно!
На этот раз мы получаем правильный вывод вместо -bash
(см. пример в предыдущем разделе с использованием источника и $0).
Одна строка, чтобы получить каталог скриптов
Теперь, когда мы знаем, что лучше использовать переменную $BASH_SOURCE, мы хотим придумать универсальный способ получения каталога скрипта…
…независимо от места выполнения скрипта.
Мы можем использовать следующий подход:
- Получить каталог скрипта (относительно текущего каталога)
- Перейдите в каталог
- Используйте pwd для получения абсолютного пути
Скрипт, который следует трем вышеописанным шагам, будет выглядеть так:
#!/bin/bash
# Step 1
SCRIPT_RELATIVE_DIR=$(dirname "${BASH_SOURCE[0]}")
# Step 2
cd $SCRIPT_RELATIVE_DIR
# Step 3
pwd
А теперь давайте рассмотрим три сценария, которые мы уже рассматривали:
1) From script directory: /opt/scripts
[ec2-user@ip-172-12-20-120 scripts]$./get_script_dir.sh
/opt/scripts
2) Relative path from parent directory: /opt
[ec2-user@ip-172-12-20-120 scripts]$ cd..
[ec2-user@ip-172-12-20-120 opt]$ scripts/get_script_dir.sh
/opt/scripts
3) Absolute path
[ec2-user@ip-172-12-20-120 opt]$ /opt/scripts/get_script_dir.sh
/opt/scripts/get_script_dir.sh
/opt/scripts
Наконец-то что-то работает хорошо…
Это не сработает во всех сценариях (например, с символическими ссылками), но этого достаточно для большинства случаев использования.
Теперь мы хотим создать однострочник, объединяющий все три шага:
Step 1+Step 2
cd $(dirname "${BASH_SOURCE[0]}")
Step 1+Step 2+Step 3
cd $(dirname "${BASH_SOURCE[0]}") && pwd
А чтобы сохранить этот каталог в переменной SCRIPT_DIR, мы используем:
SCRIPT_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
Команда pwd выполняется только в случае cd $(dirname "${BASH_SOURCE[0]}")
успеха.
Как это сделать? Используя &&.
Затем вы можете использовать команду echo для вывода значения $SCRIPT_DIR.
Заключение
Я думал, что получить каталог скрипта Bash будет очень просто!
Как мы увидели в этой статье, это не так…
Теперь вы знаете, как использовать $0 и $BASH_SOURCE.
А также как выполнить команду, если выполнение предыдущей команды прошло успешно, используя &&.
Надеюсь, эта информация оказалась для вас полезной…
Как вы будете использовать эти знания?
Вы сейчас пишете Bash-скрипт, в котором вам нужно получить каталог вашего скрипта?