Learning Platform
Глоссарий Troubleshooting
Урок 05.03 · 15 мин
Начальный
PathsAbsoluteRelativeTilderealpath

Абсолютные и относительные пути

«У меня скрипт не находит файл, хотя я его точно создал!» — каждый Junior хоть раз попадал в эту ловушку. Причина почти всегда — путаница между абсолютным и относительным путём. В этом уроке разберём, что есть что, и научимся не путаться.


Абсолютный vs относительный

Абсолютный путь начинается с /. Это полный путь от корня файловой системы:

/home/user/projects/etl/main.py
/etc/passwd
/var/log/syslog

Относительный путь не начинается с /. Он отсчитывается от текущей директории ($PWD):

main.py                  # файл в текущей папке
./main.py                # то же самое, явный './'
subfolder/main.py        # в подпапке
../other-project/main.py # на уровень вверх и в другую папку
Абсолютный vs относительный путь
/Корень файловой системы. Если путь начинается с / — он абсолютный. Указывает ОДНОЗНАЧНО на файл, независимо от того, где вы
АбсолютныйРаботает из любой директории. Длиннее, но недвусмысленный
./Если не начинается с / — путь относительный. Складывается с текущей директорией (PWD)
ОтносительныйКороче, но зависит от того, где вы. Может сломаться при cd

Простой тест: «работает ли путь, если изменить cd?»

  • Абсолютный — да, всегда указывает на одно и то же.
  • Относительный — нет, после cd тот же путь укажет на другое.
$ pwd
/home/user/projects

$ ls main.py        # относительный — ищет /home/user/projects/main.py
$ ls /home/user/projects/main.py    # абсолютный — то же самое

$ cd /tmp
$ ls main.py        # ищет /tmp/main.py — НЕ ТУ ЖЕ!
$ ls /home/user/projects/main.py    # ВСЁ ЕЩЁ работает

./ — explicit current

./file означает «file в текущей папке». Зачем ./ если можно просто file? Две причины:

  1. Для исполнения скриптов. Когда вы хотите запустить ./script.sh, нужно явное ./ — иначе bash ищет в PATH:
$ ls
script.sh

$ script.sh           # ищет в PATH — не найдёт
bash: script.sh: command not found

$ ./script.sh         # явный путь — запустит
Hello from script
  1. Когда имя файла начинается с -. Без ./ команда подумает, что это опция:
$ touch -- -weird-name.txt    # создать файл с именем '-weird-name.txt'

$ rm -weird-name.txt           # ОПАСНО: rm подумает, что -w -e -i -r... опции
rm: invalid option -- 'w'

$ rm ./-weird-name.txt         # сработает корректно

.. — родительская директория

.. всегда указывает на родительскую папку:

$ pwd
/home/user/projects/airflow

$ cd ..
$ pwd
/home/user/projects

$ cd ../..
$ pwd
/home

$ cd ../user/projects/airflow
$ pwd
/home/user/projects/airflow

В каждой директории есть . (текущая) и .. (родительская) как реальные directory entries:

$ ls -la | head -3
total 24
drwxr-xr-x  3 user user 4096 May 13 14:00 .
drwxr-xr-x 47 user user 4096 May 13 13:55 ..

Это часть файловой системы. Когда ядро резолвит путь ../foo/bar, оно идёт по этим entries.

Исключение — корень: /.. равно /. Дальше идти некуда.


Tilde ~ — home

~ — это сокращение, которое shell разворачивает в значение $HOME. Обычно $HOME — это /home/user/ (на Linux) или /Users/user/ (на macOS).

$ echo $HOME
/home/user

$ echo ~
/home/user

$ cd ~/projects        # cd /home/user/projects
$ ls ~/Documents       # ls /home/user/Documents

Важно: tilde разворачивается только bash-ом перед запуском команды. Программы сами не знают про ~:

$ python -c "import os; print(os.path.exists('~/projects'))"
False                  # Python не разворачивает ~

$ python -c "import os; print(os.path.expanduser('~/projects'))"
/home/user/projects    # Python должен явно expand

В bash:

$ FILE="~/projects/main.py"
$ ls $FILE
ls: cannot access '~/projects/main.py': No such file or directory
# tilde не разворачивается в переменных в кавычках!

$ FILE="$HOME/projects/main.py"
$ ls $FILE
# работает

Поэтому в скриптах лучше использовать $HOME, а не ~ в строках.


~user — home другого пользователя

~alice — это home директория пользователя alice (если такой пользователь есть):

$ ls ~alice
# содержимое /home/alice/

$ cd ~root
$ pwd
/root

Это разворачивается из /etc/passwd (где указано, где home каждого пользователя). Подробнее в модуле 6.


HOME,HOME, OLDPWD, $PWD

Переменные окружения, которые полезно знать:

Path-связанные переменные
$HOMEHome директория текущего пользователя. На Linux /home/user, на macOS /Users/user. Из /etc/passwd
$PWDТекущая директория (то же, что pwd выводит). Обновляется при cd
$OLDPWDПредыдущая директория (где были до cd). `cd -` использует эту переменную
$PATHСписок папок, в которых shell ищет команды. Разделитель — :. Например, /usr/local/bin:/usr/bin:/bin
$CDPATHСписок папок, в которых cd ищет аргумент. Редко используется (путает)
$TMPDIRПапка для временных файлов. Если установлена — mktemp использует её
$ cd /var/log
$ echo $PWD
/var/log

$ cd /etc
$ echo $OLDPWD
/var/log

$ cd $OLDPWD
$ pwd
/var/log

realpath (или readlink -f) — это команда, которая «разворачивает» путь:

  • резолвит все symlinks (показывает реальный путь);
  • удаляет .., .;
  • получает абсолютный путь.
# Допустим, /home/user — symlink на /mnt/users/user
$ realpath ~
/mnt/users/user

$ realpath ./relative/path
/home/user/current/relative/path

$ realpath /etc/alternatives/python3
/usr/bin/python3.13

Это часто нужно в скриптах для нормализации:

SCRIPT_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
echo "Script lives in: $SCRIPT_DIR"

Эта конструкция гарантированно даст абсолютный путь к директории, где лежит сам скрипт. Полезно, когда скрипт хочет открыть файлы относительно своего расположения.

TIP

readlink без -f показывает только цель symlink (не резолвит цепочки). readlink -f — резолвит всё. На macOS readlink -f нет (BSD-вариант), используйте realpath. Или ставьте coreutils через brew, тогда greadlink -f работает.


Канонический путь в реальных задачах

Допустим, у вас DAG в Airflow:

# /opt/airflow/dags/etl_dag.py
def my_task():
    data_path = "../../data/raw.csv"
    process(data_path)

В какой момент это резолвится? Зависит от того, где запускается код. Если Airflow worker запускается из /opt/airflow, то ../../data/ — это /data/. Если из /, то ../../data/ — это ../../data/… что?

Лучшая практика — НЕ использовать относительные пути в production-коде:

import os
# Берём абсолютный путь из переменной окружения или конфига
DATA_ROOT = os.environ.get("DATA_ROOT", "/var/data")
data_path = os.path.join(DATA_ROOT, "raw.csv")
process(data_path)

То же в bash:

# В начале скрипта — определить абсолютные пути
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
DATA_DIR="${DATA_DIR:-/var/data}"
LOG_DIR="${LOG_DIR:-/var/log/myapp}"

# Дальше работаем только с абсолютами
process "$DATA_DIR/raw.csv" >> "$LOG_DIR/etl.log"

Когда относительные OK

В скриптах относительные пути плохи. Но в интерактивной работе — нормально:

$ cd ~/projects/etl
$ vim main.py            # относительный — OK, работает
$ python main.py
$ ls outputs/

В Git тоже относительные — git add ./src/main.py (относительно корня репо).

И в коротких pipelines:

$ cd /var/log
$ grep ERROR syslog | wc -l

Правило: интерактив — относительные OK; скрипты production — абсолютные.


Проверка существования путей

В bash есть условия для проверки:

# Файл существует?
$ [[ -e /etc/passwd ]] && echo "exists"
exists

# Существует и это файл (не папка)?
$ [[ -f /etc/passwd ]] && echo "is file"
is file

# Существует и это папка?
$ [[ -d /etc ]] && echo "is dir"
is dir

# Существует и это symlink?
$ [[ -L /etc/alternatives/python ]] && echo "is symlink"

# Можно читать?
$ [[ -r /etc/shadow ]] || echo "cannot read"
cannot read

# Можно писать?
$ [[ -w /tmp ]] && echo "can write"
can write

Это всё в скриптах для проверки перед операцией. Подробнее в модуле 17.


Попробуй сам

Создайте sandbox:

$ mkdir -p ~/linux-sandbox/lesson-paths/{src,data,logs}
$ cd ~/linux-sandbox/lesson-paths

# Абсолютные vs относительные
$ pwd
/home/user/linux-sandbox/lesson-paths

$ ls src/
$ ls /home/user/linux-sandbox/lesson-paths/src/
# Одинаково

$ cd src
$ ls ../data/
$ ls /home/user/linux-sandbox/lesson-paths/data/
# Одинаково

Поиграйте с tilde:

$ echo "I am at: ~/some/path"            # tilde НЕ развернётся в строке
I am at: ~/some/path

$ echo "I am at: $HOME/some/path"        # развернётся
I am at: /home/user/some/path

$ FILE=~/some/path                       # развернётся (без кавычек!)
$ echo $FILE
/home/user/some/path

$ FILE="~/some/path"                     # в кавычках — нет
$ echo $FILE
~/some/path

Symlinks:

$ ln -s /etc/passwd ./pass-link
$ ls -l pass-link
lrwxrwxrwx 1 user user 11 May 13 14:00 pass-link -> /etc/passwd

$ realpath pass-link
/etc/passwd

$ readlink pass-link
/etc/passwd

$ readlink -f pass-link    # резолвит все цепочки
/etc/passwd

realpath всегда даёт реальный конечный путь. Это надёжнее readlink в скриптах.


Проверка знанийKnowledge check
DAG Airflow содержит код: `with open('config.yaml') as f: config = yaml.safe_load(f)`. Локально работает, в production падает с FileNotFoundError. Junior гадает: 'но файл же там есть!' В чём проблема и как починить?
ОтветAnswer
Проблема в относительном пути. `open('config.yaml')` ищет файл относительно текущей рабочей директории (cwd) процесса. Локально Junior запустил Python из той же папки, где config.yaml — всё нашлось. В production Airflow worker запускается из своего рабочего каталога (например, /opt/airflow или /), и `config.yaml` ищется уже там — не находится. Решения: (1) Использовать `__file__` — путь к самому DAG-файлу: `os.path.join(os.path.dirname(__file__), 'config.yaml')` — резолвится относительно расположения DAG, что стабильнее. (2) Использовать абсолютный путь через переменную окружения: `os.environ['CONFIG_PATH']` или явный `/opt/airflow/dags/config.yaml`. (3) Airflow Variables — хранить путь там и читать через `Variable.get('config_path')`. Главный урок: в production-коде НЕ полагаться на cwd, всегда строить абсолютные пути от известной точки (`__file__`, env-var, конфиг).

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Что такое абсолютный путь?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5