Форум программистов, компьютерный форум, киберфорум
Наши страницы
Rius
Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 1.

Относительное зло

Запись от Rius размещена 03.08.2018 в 08:24
Обновил(-а) Rius 03.08.2018 в 22:01

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

Что такое относительный путь? Это путь к файлу/каталогу (далее просто к файлу) в сокращённом виде, т.е. без корневого каталога или буквы диска. Например: ../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 файла
Код:
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 тоже совпадает с каталогом, где расположена программа, и относительные пути отсчитываются относительно него.
Как только запуск производится в иных условиях, каковые как раз и встречены в упомянутых вверху темах, всё тут же перестаёт работать.

Решение: при обращении к файлам (в соответствующих методах), должны применяться только абсолютные пути, не относительные.
Это вовсе не значит, что надо их хардкодить, а путь к файлу, расположенному рядом с исполняемым, получить невозможно. Если понадобился путь относительно каталога программы, или любого иного каталога, нужно этот относительный путь привести программно к абсолютному.
Внимание: здесь говорится не об архитектуре приложения, а о том, что к моменту обращения к файлу, используемое значение пути должно быть абсолютным, чтобы никто (система или автор) не делал предположений о том, какой же именно путь надо будет применить, потому что реальность может разойтись с предположениями.
Когда именно абсолютный путь должен быть вычислен - уже иной вопрос (см. комменты ниже).
Проблемы возникают у тех, кто к указанному моменту (обращение к файлу) допустил значение с относительным путём.
Путь к каталогу программы можно получить разными способами.
Примеры на C#
  • 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;
    }
  • ...
Есть простые, есть посложнее. Метод не один потому, что в нетривиальных условиях простые методы могут выдавать ложный результат, а в тривиальных же условиях сложный код - избыточен.

Далее полученный абсолютный путь к программе надо сложить с относительным путём к файлу, чтобы получить абсолютный путь к искомому файлу.
Пример на C#
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
И уже вот этот, полученный программно абсолютный путь, нужно использовать там, где происходит обращение к файлу.

Почему, однако, можно запустить готовую программу типа 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% (что может потребовать админских прав или отрицательно повлиять на другие программы).
Размещено в Без категории
Просмотров 273 Комментарии 10
Всего комментариев 10
Комментарии
  1. Старый комментарий
    Аватар для Usaga
    Это куда-нибудь в FAQ бы. Чтобы на глазах постоянно было.
    Запись от Usaga размещена 03.08.2018 в 09:08 Usaga вне форума
  2. Старый комментарий
    Аватар для OwenGlendower
    Цитата:
    Сообщение от Usaga Просмотреть комментарий
    Это куда-нибудь в FAQ бы. Чтобы на глазах постоянно было.
    Это уже есть в FAQ. Правда в более кратком изложении - http://www.cyberforum.ru/csharp-begi...ml#post8862983

    Если у Rius есть желание выложить это в виде темы, то я перенесу её в прикрепленную тему по IO
    http://www.cyberforum.ru/csharp-beginners/thread163620.html
    Запись от OwenGlendower размещена 03.08.2018 в 10:11 OwenGlendower вне форума
  3. Старый комментарий
    Аватар для Rius
    Usaga, OwenGlendower, сия проблема относится не к одному какому-то языку, а ко многим (если не всем десктопным). Да и не только к языкам, но и к запуску готовых программ и их аргументам.
    Так что в каком-то определённом разделе писать... Просто ссылку добавьте, если хотите.
    Запись от Rius размещена 03.08.2018 в 10:25 Rius вне форума
    Обновил(-а) Rius 03.08.2018 в 10:35
  4. Старый комментарий
    Аватар для Evg
    Цитата:
    Решение: пути должны быть только абсолютными, а не относительными
    Это слишком категоричный совет, если смотреть на него глобально. Многие могут понять его неправильно. При проектировании внутренней инфраструктуры программы все файлы должны описываться относительными путями, которые приклеиваются к некому базовому пути, который уже является абсолютным и вычисляется/настраивается один раз при старте программы
    Запись от Evg размещена 03.08.2018 в 14:49 Evg вне форума
  5. Старый комментарий
    Аватар для Rius
    Да.
    Речь о моменте самого обращения к файлу/каталогу. Вот тогда используемый путь должен быть уже абсолютным. Проблема возникает у тех, кто к этому моменту оставил относительный путь.
    Запись от Rius размещена 03.08.2018 в 15:33 Rius вне форума
  6. Старый комментарий
    Аватар для Evg
    Цитата:
    Речь о моменте самого обращения к файлу/каталогу. Вот тогда используемый путь должен быть уже абсолютным
    Желательно в статье заострить на этом внимание, чтобы у читателей не возникало ошибочного понимания
    Запись от Evg размещена 03.08.2018 в 18:45 Evg вне форума
  7. Старый комментарий
    У меня тоже была такая проблема. В ярлыке подправил и всего делов
    Запись от TopLayer размещена 03.08.2018 в 18:50 TopLayer вне форума
  8. Старый комментарий
    Аватар для Rius
    Evg, добавил.
    TopLayer, а это то, что называется "костыли".
    Запись от Rius размещена 03.08.2018 в 19:15 Rius вне форума
  9. Старый комментарий
    Цитата:
    Сообщение от Rius Просмотреть комментарий
    TopLayer, а это то, что называется "костыли".
    Задача заключалась в слежении за файлами определённой программы. Изначально я думал просто закинуть свой exe-шник в папку, куда установлена программа. Но когда обнаружил багу, понял, что теперь ничего никуда копировать не надо, а достаточно настроить ярлык. Короче говоря, два неверных решения уравновесили друг друга. Или это всё равно костыльно?
    Запись от TopLayer размещена 04.08.2018 в 18:26 TopLayer вне форума
  10. Старый комментарий
    Аватар для Rius
    Сами вот эти параметры совместимости со старыми приложениями, что настраиваются в ярлыках, являются костылями, призванными подпереть работу в новых ОС тех программ, что с неё работать нормально не умеют.
    У вас всё совпало, что никто никому не мешает, повезло.
    Но могло быть такое, что та определённая программа для своих нужд могла попытаться удалить или переименовать свой каталог (для обновления, например). И наличие другого процесса, имеющего current directory настроенным на этот каталог, заблокировало бы эту операцию. Вот такая не очевидная фича у Windows есть.
    Запись от Rius размещена 04.08.2018 в 19:17 Rius вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru