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

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

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

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

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

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

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

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

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

Во избежание проблем, если всё-таки 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% (что может потребовать админских прав или отрицательно повлиять на другие программы).

P.S. Немного об упомянутых в комментах эффектах:
Размещено в Без категории
Просмотров 741 Комментарии 24
Всего комментариев 24
Комментарии
  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 вне форума
  11. Старый комментарий
    Аватар для Storm23
    Можно также отметить, что в системах младше Windows7, текущая директория могла меняться прямо во время работы уже запущенной программы. Так вызов OpenFileDialog менял текущую директорию на директорию файла, который выбрал пользователь. Но в Windows7 это поведение было пофикшено.
    Запись от Storm23 размещена 26.08.2018 в 11:15 Storm23 вне форума
  12. Старый комментарий
    Аватар для Rius
    Да.
    Применительно к .Net Framework этот баг можно вернуть:
    Кликните здесь для просмотра всего текста
    C#
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    private void button1_Click(object sender, EventArgs e)
    {
        using (var dialog = new OpenFileDialog())
        {
            dialog.AutoUpgradeEnabled = false;
            dialog.RestoreDirectory = false;
     
            // Как только открылся диалог, в соседнем обработчике таймера вылетит исключение.
            if (dialog.ShowDialog() == DialogResult.OK)
            {
            }
        }
    }
     
    private void timer1_Tick(object sender, EventArgs e)
    {
        this.textBox1.AppendText(Directory.GetCurrentDirectory());
     
        try
        {
            string content = File.ReadAllText("TextFile1.txt");
            this.textBox1.AppendText(content);
        }
        catch(Exception exc)
        {
            this.textBox1.AppendText(exc.Message);
        }
    }

    Ещё одна причина не полагаться на относительные пути.
    Запись от Rius размещена 26.08.2018 в 11:31 Rius вне форума
    Обновил(-а) Rius 26.08.2018 в 11:59 (Добавлен спойлер)
  13. Старый комментарий
    Аватар для Agregat
    Правильная статья. Тоже жизнь научила в своих программах оперировать только абсолютными путями.
    Запись от Agregat размещена 26.08.2018 в 17:00 Agregat вне форума
  14. Старый комментарий
    Аватар для Avazart
    Цитата:
    Почему? Потому что current directory это вовсе не каталог, где находится программа (внезапно!). Это текущий (т.е. активный в данный момент) каталог для процесса.
    Это не внезапно, это очевидно. И это даже не баг, а скорее фича, можно одну и туже программу запускать из разных директорий с разными настройками с помощью бат-файлов или ярлыков.

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

    Цитата:
    Речь о моменте самого обращения к файлу/каталогу. Вот тогда используемый путь должен быть уже абсолютным
    Если не ошибаюсь это нужно лишь при работе с программами MS Word /Excel в остальных случаях это лишь лишние телодвижения.


    Цитата:
    Можно также отметить, что в системах младше Windows7, текущая директория могла меняться прямо во время работы уже запущенной программы. Так вызов OpenFileDialog менял текущую директорию на директорию файла, который выбрал пользователь. Но в Windows7 это поведение было пофикшено.
    Если не подводит память это кажется настраивается в самом компоненте.

    Но да программа может поменять текущую директорию налету сама через SetCurrentDirectory(), но по очевидным причинам так не принято делать.
    Запись от Avazart размещена 26.08.2018 в 18:23 Avazart вне форума
    Обновил(-а) Avazart 26.08.2018 в 18:41
  15. Старый комментарий
    Аватар для Rius
    Avazart эта фича очевидна для тех, кто умеет работать с консолью.
    Внезапно натыкающиеся на это явление, темы которых перечислены в начале, к таковым не относятся. Они ошибочно полагают, что Current Directory это просто тот каталог, где лежит программа. А это, внезапно для них, оказывается не так и программа не работает.
    Запись от Rius размещена 26.08.2018 в 18:29 Rius вне форума
    Обновил(-а) Rius 26.08.2018 в 18:37
  16. Старый комментарий
    Аватар для Avazart
    Цитата:
    Avazart эта фича очевидна для тех, кто умеет работать с консолью.
    Если кто-то не умеет работать с консолью, то он ничего не умеет.

    Цитата:
    А это, внезапно для них, оказывается не так и программа не работает.
    Это не повод отказываться от относительных путей, тут скорее всего нужно вернуться к истокам.
    Запись от Avazart размещена 26.08.2018 в 18:43 Avazart вне форума
    Обновил(-а) Avazart 26.08.2018 в 18:45
  17. Старый комментарий
    Аватар для Rius
    Цитата:
    Если не ошибаюсь это нужно лишь при работе с программами MS Word /Excel в остальных случаях это лишь лишние телодвижения.
    Применение полного пути устраняет данную проблему вообще.
    Применение относительного пути требует понимания, что такой текущий каталог, как он устанавливается и от чего может измениться. И если код или софт даже от понимающего автора, использующий относительные пути, попадёт в руки непонимающему пользователю, то он может перестать работать.
    Это всё равно что утверждать, что антивирусный софт не нужен, потому что пряморукость решает. Только вот пряморукость не столь распространена, как хотелось бы.
    Цитата:
    Если кто-то не умеет работать с консолью, то он ничего не умеет.
    Данная запись как раз для тех, кто не умеет. Кто умеет, ему объяснять незачем.
    Цитата:
    Это не повод отказываться от относительных путей, тут скорее всего нужно вернуться к истокам.
    И опять же, те, кто интересуются истоками, на такие грабли не наступят. А кто не интересуется - попробуй заставь. Хотите - напишите статью об истории возникновения current directory.
    Запись от Rius размещена 26.08.2018 в 19:00 Rius вне форума
  18. Старый комментарий
    Аватар для Avazart
    Цитата:
    Применение полного пути устраняет данную проблему вообще.
    Какую проблему? Недостатка ума/знаний?
    Не думаю.

    Цитата:
    Это всё равно что утверждать, что антивирусный софт не нужен, потому что пряморукость решает. Только вот пряморукость не столь распространена, как хотелось бы.
    А это все равно что поощрять дураков в их невежестве и чуть ли не уступать им место в транспорте.

    Цитата:
    И опять же, те, кто интересуются истоками, на такие грабли не наступят. А кто не интересуется - попробуй заставь. Хотите - напишите статью об истории возникновения current directory.
    Ээ.. Вы предлагаете писать программы не имея даже приблизительного понятия что такое ОС ?

    Это не история это азы ОС да и можно погуглить https://ru.wikipedia.org/wiki/%D0%A0...BB%D0%BE%D0%B3

    Т.е это понятие не только для винды существует.
    Запись от Avazart размещена 26.08.2018 в 19:04 Avazart вне форума
    Обновил(-а) Avazart 26.08.2018 в 19:21
  19. Старый комментарий
    Аватар для Rius
    Цитата:
    Какую проблему?
    Да хотя бы упомянутую Storm23:
    Кликните здесь для просмотра всего текста


    Вы хотите каждого новичка
    • научиться пользоваться консолью
    • понимать
      • что такое current directory;
      • откуда оно возникло;
      • как устанавливать и получать;
      • как оно может измениться без нашего участия;
      • как с этим правильно работать?
    Цитата:
    А это все равно что поощрять дураков в их невежестве и чуть ли не уступать им место в транспорте.
    Так займитесь ими самостоятельно. Всё в ваших руках. Не надо мне это внушать, внушайте новичкам.
    Запись от Rius размещена 26.08.2018 в 19:18 Rius вне форума
  20. Старый комментарий
    Аватар для Avazart
    Цитата:
    Да хотя бы упомянутую Storm23:
    Нет такой проблемы ибо XP мертва.

    Цитата:
    Вы хотите каждого новичка

    научиться пользоваться консолью
    понимать
    что такое current directory;
    откуда оно возникло;
    как устанавливать и получать;
    как оно может измениться без нашего участия;
    как с этим правильно работать?
    Этого
    Цитата:
    научиться пользоваться консолью
    Достаточно и само собой разумеется.
    Большая часть книг начинают обучение именно с написания консольных приложений.

    Касательно рабочего каталога нужно только погуглить и прочитать пару строчек:

    Цитата:
    Текущий каталог

    Текущим называется каталог, с которым работает ОС, если ей не указать другого каталога. Он обозначается точкой (.).

    Для смены текущего каталога на другой используется команда cd; без указания целевого каталога она меняет каталог на домашний (в Unix-подобных ОС) или возвращает текущий (в Windows).
    https://ru.wikipedia.org/wiki/%D0%9A...BB%D0%BE%D0%B3
    Запись от Avazart размещена 26.08.2018 в 19:22 Avazart вне форума
    Обновил(-а) Avazart 26.08.2018 в 19:30
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru