Вы когда-нибудь пытались получить доступ к каталогу вашего скрипта 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, мы хотим придумать универсальный способ получения каталога скрипта…

…независимо от места выполнения скрипта.

Мы можем использовать следующий подход:

  1. Получить каталог скрипта (относительно текущего каталога)
  2. Перейдите в каталог
  3. Используйте 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-скрипт, в котором вам нужно получить каталог вашего скрипта?

Written by Иван Васильков

Системный администратор и DevOps с опытом 10+ лет.