Форум программистов, компьютерный форум, киберфорум
Rius
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

Относительное зло или когда пути неисповедимы

Запись от Rius размещена 03.08.2018 в 08:24
Показов 35764 Комментарии 24

Часто встречаются такие и подобные вопросы:Объединяет их одно: автор проблемного кода использует относительные пути.

Что такое относительный путь? Это путь к файлу/каталогу (далее просто к файлу) в сокращённом виде, т.е. без корневого каталога или буквы диска. Например: ../file.txt или ./subdirectory/.
В противоложность, есть абсолютный путь (полные) вида /etc/network/interfaces (Linux, начинается с корневого каталога) или c:\Windows\System32\drivers\etc\hosts (Windows, начинается с буквы диска).

Для обращения к файлу, операционной системе требуется знать абсолютный путь.
Но если путь указан относительный, системе придётся самостоятельно приводить его к абсолютному. В этом случае вступает в игру другое понятие, известное под именами current directory и другими.
Это путь к каталогу, относительно которого производятся операции с относительными именами файлов (каталогов)
Вот на этом этапе и всплывает такое, что авторы:
  1. Вообще не задумываются об том, что и откуда берётся, указывая путь вида database.mdf, file.txt.
  2. Намеренно (но без понимания), указывают путь вида dir/file, ..dir/file, ../file, ./file и т.п., ошибочно полагая, что путь к файлу/каталогу будет отсчитываться от каталога, где находится программа.
  3. Намеренно (но c недопониманием), указывают путь вида current_directory + "/file.txt", ошибочно полагая, что current directory это каталог, где находится программа.
  4. ...
Взращивается такое ложное преставление поведением среды разработки по умолчанию (см. ниже).

При обращении из кода (нашей программы, чужой программы или функции ОС) к файлу по относительному пути, будет задействован current directory текущего процесса, чтобы получить абсолютный путь к файлу. Для упрощения, можно представлять это как конкатенацию строк - строкового значения current directory и строкового же значения относительного пути.
Увидеть значение current directory можно в:
  • Проводнике Windows, отображается в адресной строке;
  • Консоли Windows или Linux, отображается в строке приглашения к вводу (промпт);
  • Консоли Windows, выполнив команду
    Windows Batch file
    1
    
    cd
  • Консоли Linux, выполнив команду
    Bash
    1
    
    pwd

Демонстрация взаимодействия относительного пути и current directory
Пусть есть 2 файла
Code
1
2
R:\demo\1\file1.txt
R:\demo\2\file2.txt
И есть некая программа (наша или чужая), которой требуется обратиться к этим файлам.
Для демонстрации - программа more (консольная команда или утилита), принимающая путь к файлу и выводящая этот файл в консоль.
Проблема в этом случае идентична обращению к файлам из нашего кода. Т.к. всё равно
  • Есть указанный нами (относительный или абсолютный) путь к файлу;
  • Есть код, которому требуется обратиться к этому файлу;
  • Если указан относительный путь, его требуется привести к абсолютному.

Если мы находимся в каталоге R:\demo\1, т.е. current directory = R:\demo\1, программа может обратиться к файлу просто по имени, т.к. нужный файл находится в current directory:
Windows Batch file
1
2
3
4
5
6
7
8
9
10
11
12
13
r:\demo\1>dir
 Содержимое папки r:\demo\1
 
03.08.2018  00:07    <DIR>          .
03.08.2018  00:07    <DIR>          ..
03.08.2018  00:10                11 file1.txt
               1 файлов             11 байт
               2 папок   2*038*804*480 байт свободно
 
r:\demo\1>more file1.txt
content 1
 
r:\demo\1>
Но для обращения к файлу в соседнем каталоге, надо указывать абсолютный путь, потому что просто имя файла уже не работает - такого файла в current directory нет:
Windows Batch file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
r:\demo\1>dir
 Содержимое папки r:\demo\1
 
03.08.2018  00:07    <DIR>          .
03.08.2018  00:07    <DIR>          ..
03.08.2018  00:10                11 file1.txt
               1 файлов             11 байт
               2 папок   2*038*804*480 байт свободно
 
r:\demo\1>more file2.txt
Не удается получить доступ к файлу R:\demo\1\file2.txt
 
r:\demo\1>more r:\demo\2\file2.txt
content 2
 
r:\demo\1>
К файлу в соседнем каталоге можно обратиться и проще - через относительный путь, указывая выход на каталог выше и оттуда вниз в соседний каталог:
Windows Batch file
1
2
3
4
5
6
7
8
9
10
11
12
13
r:\demo\1>dir
 Содержимое папки r:\demo\1
 
03.08.2018  00:07    <DIR>          .
03.08.2018  00:07    <DIR>          ..
03.08.2018  00:10                11 file1.txt
               1 файлов             11 байт
               2 папок   2*038*804*480 байт свободно
 
r:\demo\1>more ..\2\file2.txt
content 2
 
r:\demo\1>
И это даже работает.

Но если current directory не совпадает (внезапно!) с предполагаемым положением, всё идёт не так:
Windows Batch file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
r:\demo\1>dir
 Содержимое папки r:\demo\1
 
03.08.2018  00:07    <DIR>          .
03.08.2018  00:07    <DIR>          ..
03.08.2018  00:10                11 file1.txt
               1 файлов             11 байт
               2 папок   2*038*804*480 байт свободно
 
r:\demo\1>cd r:\
 
r:\>more ..\2\file2.txt
Не удается получить доступ к файлу R:\2\file2.txt
 
r:\>more R:\demo\2\file2.txt
content 2
 
r:\>
В этом случае (если current directory не совпадает с ожидаемым) относительные пути перестают работать так, как ожидалось.
А вот абсолютные - продолжают работать, потому что на значение current directory они не опираются.


В ходе написания программы в соответствующей среде разработки никаких проблем обычно не замечается, потому что при запуске своей программы из среды разработки автор обычно получает current directory совпадающим с каталогом, куда собирается программа. Поэтому не возникает ни единой мысли о том, что в коде с относительными путями что-то не так.
А в ходе пробной эксплуатации, или при запуске иным способом, это не так расцветает во всей красе.

Почему? Потому что current directory это вовсе не каталог, где находится программа (внезапно!). Это текущий (т.е. активный в данный момент) каталог для процесса.
Они могут совпадать в какой-то момент времени, могут не совпадать, но это точно не одно и то же.

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

В этих случаях current directory тоже совпадает с каталогом, где расположена программа, и относительные пути отсчитываются относительно него.
Как только запуск производится в иных условиях, каковые как раз и встречены в упомянутых вверху темах, всё тут же перестаёт работать.

Решение: при обращении к файлам (в соответствующих методах), должны применяться только абсолютные пути, не относительные.
Это вовсе не значит, что надо их хардкодить, а путь к файлу, расположенному рядом с исполняемым, получить невозможно. Если понадобился путь относительно каталога программы, или любого иного каталога, нужно этот относительный путь привести программно к абсолютному.
Внимание: здесь говорится не об архитектуре приложения, а о том, что к моменту обращения к файлу, используемое значение пути должно быть абсолютным, чтобы никто (система или автор) не делал предположений о том, какой же именно путь надо будет применить, потому что реальность может разойтись с предположениями.
Когда именно абсолютный путь должен быть вычислен - уже иной вопрос (см. комменты ниже).
Проблемы возникают у тех, кто к указанному моменту (обращение к файлу) допустил значение с относительным путём.

В комментах кое-кто утверждает, что нужно пользоваться только относительными путями.
Пользоваться можно, только осторожно но не нужно. Этот способ применим только к месту (например, в консольных утилитах) и при условии хорошего понимания, как это всё работает и отчего может измениться значение current directory.
Пример того, к чему может привести

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

Данный конкретный баг взят из коммента Storm23. В новых версиях Windows исправлен, но можно наткнуться на что-то подобное.

Во избежание проблем, если всё-таки current directory требуется, лучше сохранить это значение в переменную при старте приложения и после, на её основе, формировать абсолютные пути по мере надобности.
Какие могут быть проблемы, помимо бага в ОС?
Например, некий код обработки файлов в каталоге был построен на использовании относительного пути. При попытке его использования в нескольких потоках для обработки независимых каталогов, возникнет проблема с тем, что значение current directory одно для процесса. С абсолютными же путями никаких проблем не будет.
Код надо писать так, чтобы возможностей ошибиться при его использовании было как можно меньше.
Путь к каталогу программы можно получить разными способами.
Примеры на C# .Net
  • C#
    1
    
    string path = AppDomain.CurrentDomain.BaseDirectory;
  • C#
    1
    
    string path = Path.GetDirectoryName(Application.ExecutablePath);
  • C#
    1
    2
    3
    4
    5
    6
    7
    8
    
    private string GetExeDirectory()
    {
      string codeBase = Assembly.GetExecutingAssembly().CodeBase;
      UriBuilder uri = new UriBuilder(codeBase);
      string path = Uri.UnescapeDataString(uri.Path);
      path = Path.GetDirectoryName(path);
      return path;
    }
  • ...

Пример на VB .Net

VB.NET
1
2
3
Public Function GetExeDir() As String
    GetExeDir = My.Application.Info.DirectoryPath
End Function

Пример на C++
C++
1
2
3
4
TCHAR exePath[MAX_PATH];
HMODULE hModule = GetModuleHandleW(NULL);
GetModuleFileNameW(hModule, exePath, MAX_PATH);
PathRemoveFileSpec(exePath);

Пример на Delphi
Delphi
1
2
3
4
5
6
7
var
  AppDir: string;
begin
  AppDir := ExtractFileDir(Application.ExeName);
  // Do something with AppDir, e.g.:
  ShowMessage(AppDir);
end;

Есть простые, есть посложнее. Метод не один потому, что в нетривиальных условиях простые методы могут выдавать ложный результат, а в тривиальных же условиях сложный код - избыточен.

Далее полученный абсолютный путь к каталогу программы надо сложить с относительным путём к файлу, чтобы получить абсолютный путь к искомому файлу.
Пример на C# .Net
C#
1
2
3
4
5
6
string exeDir = @"r:\demo\1"; // Путь к исходному каталогу
string relPath = @"..\2\file2.txt"; // Относительный путь к файлу
string resPath = Path.Combine(exeDir, relPath); // Объединяет две строки в путь.
Console.WriteLine(resPath);
resPath = Path.GetFullPath(resPath); // Возвращает для указанной строки пути абсолютный путь.
Console.WriteLine(resPath);
Результат:
r:\demo\1\..\2\file2.txt
r:\demo\2\file2.txt


Пример на VB .Net

VB.NET
1
2
3
4
5
6
7
8
9
10
Sub Main()
    Dim exeDir, relPath, resPath As String
 
    exeDir = "r:\demo\1" ' Путь к исходному каталогу
    relPath = "..\2\file2.txt" ' Относительный путь к файлу
    resPath = System.IO.Path.Combine(exeDir, relPath) ' Объединяет две строки в путь.
    Console.WriteLine(resPath)
    resPath = System.IO.Path.GetFullPath(resPath) ' Возвращает для указанной строки пути абсолютный путь.
    Console.WriteLine(resPath)
End Sub


Пример на C++

C++
1
2
3
4
5
6
7
8
9
10
TCHAR libraryPath[MAX_PATH] = { 0 };
_tcscat_s(libraryPath, MAX_PATH, exePath);
 
#ifdef _M_IX86
  _tcscat_s(libraryPath, MAX_PATH, TEXT("\\x86\\library.dll"));
#elif defined(_M_AMD64)
  _tcscat_s(libraryPath, MAX_PATH, TEXT("\\x64\\library.dll"));
#endif
 
LoadLibrary(libraryPath);
И уже вот этот, полученный программно абсолютный путь, нужно использовать там, где происходит обращение к файлу.

Почему, однако, можно запустить готовую программу типа notepad.exe или more.exe (что была в примере выше) без указания абсолютного пути?
Потому что для поиска программ есть такая системная переменная PATH. Поиск программ производится сначала в current directory, а если там не найдена - далее в каталогах, перечисленных в этой переменной.
Windows Batch file
1
2
3
4
5
6
7
r:\>echo %PATH%
C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files\dotnet\;C:\Users\User\AppData\Local\Microsoft\WindowsApps
 
r:\>where more
C:\Windows\System32\more.com
 
r:\>
Поэтому программу можно запускать просто по имени, если она расположена в текущем каталоге или одном из указанных в переменной PATH. В иных случаях также следует (во избежание) формировать абсолютный путь.
Также, случается наличие в путях поиска нескольких исполняемых файлов с одинаковым именем. Тогда тоже помогает указание абсолютного пути, либо модификация порядка путей в %PATH% (что может потребовать админских прав или отрицательно повлиять на другие программы).

P.S. Немного об упомянутых в комментах эффектах:
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 24
Комментарии
  1. Старый комментарий
    Аватар для Avazart
    Так займитесь ими самостоятельно. Всё в ваших руках. Не надо мне это внушать, внушайте новичкам.
    Проблемы дураков, это их личные проблемы.

    А вот вводить в заблуждение не нужно.
    Запись от Avazart размещена 26.08.2018 в 19:23 Avazart вне форума
  2. Старый комментарий
    Аватар для Rius
    Ваш взгляд на эту проблему - это ваша проблема. Меня это не интересует.
    Обзывать дураками и RTFM'кать древними мануалами, ничего не предпринимая для исправления, много ума не надо.
    Запись от Rius размещена 27.08.2018 в 06:33 Rius вне форума
  3. Старый комментарий
    Аватар для Agregat
    Avazart: Текущим называется каталог, с которым работает ОС, если ей не указать другого каталога. Он обозначается точкой (.).
    Предположим, разрабатывается программа, работающая с несколькими разными файлами в режиме чтения и/или записи. Разработка идет на скриптовом языке. Основной скрипт еще вызывает функции из других скриптов-библиотек. Ну и какой же каталог в любой момент времени будет текущим:
    - каталог интерпретатора;
    - каталог скрипта;
    - каталог вызванной библиотеки;
    - каталоги файлов, которые читает или в которые пишет скрипт?
    Запись от Agregat размещена 28.08.2018 в 09:35 Agregat вне форума
  4. Старый комментарий
    Аватар для Avazart
    Не что из перечисленного.

    Рабочим будет каталог из которого пользователь запускает программу, по нормальному.

    Пути поиска библиотек должны как-то отдельно задаваться (отдельным параметром консоли или переменной среды окружения итд.).
    При чем наверное библиотеки должны как то делится стандартные/пользовательские.
    Если же речь про пользовательские то логично брать папку расположения скрипта.
    И тут интерпретатор может менять текущий каталог на каталог скрипта.

    Т.е. когда речь идет о скриптовых языках все сложнее ибо есть интерпретатор, а есть скрипт с которым он работает.
    Запись от Avazart размещена 28.08.2018 в 17:15 Avazart вне форума
 
Новые блоги и статьи
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru