Относительный путь к файлам: что это значит, зачем нужен?

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

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

Читатель, соответственно, путается, когда автор в последствии говорит об использовании абсолютного и(или) относительного пути.

Допустим, у Вас есть сайт и Вам нужно создать гиперссылку(ссылку) на одну из страниц сайта. Здесь нужно выбрать какой использовать тип пути: относительный или абсолютный.

Относительный путь к файлам: что это значит, зачем нужен?

Очень важно понимать, где какой путь нужно и лучше использовать. Абсолютный путь можно указать только одним способом. А вот относительный в отличии от абсолютного имеет несколько вариантов использования.

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

Так как большую часть своего времени я уделяю web-технологиям, то примеры буду приводить относящиеся к созданию сайтов.

Оглавление

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

Когда ссылка представляет из себя полный URL файла или страницы, это и есть абсолютный путь. При этом в адресе должен присутствовать используемый протокол. Например, http://www.uamedwed.com — это абсолютный путь к конкретному веб-сайту. В данном случае абсолютный путь к главной странице моего блога. Где протоколом является http, а www.uamedwed.com доменом(именем).

Если указывать ссылку на католог, например http://yourdomain.ua/web/ то будет загружаться(отображаться) индексный файл. Это правило применимо в основном к статическим сайтам. Так как при использовании языка программирования можно создать внутренний роутинг. Индексный файл обычно представляет из себя файл с именем index.php, index.html, index.phtml, index.shtml. Для того что бы использовать другой индексный файл, нужно создать в нужной директории файл с именем .htaccess, и в нем прописать некоторую директиву. Изменение и создание файла .htaccess, как и роутинг с помощью языка программирования, выходит за рамки этой статьи.

В основном абсолютный путь используется, тогда, когда нужно сослаться на другой сайт. Иными словами если Вы хотите отправить посетителя на другой сайт, то нужно использовать абсолютный путь. Хотя, такой путь можно использовать и на собственном сайте. Но многие придерживаются того, что ссылки внутри сайта должны быть относительными.

Использование абсолютного пути может повлечь за собой некоторые проблемы. Например при переносе сайта с локальной машины на сервер(это в том случае, если вы использовали на локальной машине адреса в виде http://localhost/sitename.ua/…). Трудности могут возникнуть, тогда, когда появится необходимость в смене домена(имени сайта). Хотя, все эти трудности решаемы, но на них придётся потратить некоторое количество времени.

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

Так вот, при использовании абсолютных путей, можно получить обратные ссылки с сайта, на котором находится сворованный контент. Но это только в том случае если у Вас внутренняя перелинковка осуществлялась с использованием абсолютных путей.

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

Немного отступив от темы хочу вкратце рассказать про то что такое URL.

Каждая веб-страница или документ в сети Интернет имеет собственный уникальный адрес, который и называется URL.
URL — единообразный локатор (определитель местонахождения) ресурса. Расшифровывается URL как Uniform Resource Locator(унифицированный адрес ресурса).

Можно так же встретить и такую расшифровку как Universal Resource Locator(универсальный локатор ресурса). Этот способ записи адреса стандартизирован в сети Интернет. Более общая и широкая система идентификации ресурсов URI постепенно заменяет термин URL.

URI — это символьная строка, которая идентифицирует какой-либо ресурс: документ, файл, и т.д. Конечно, здесь подразумеваются ресурсы сети Интернет.

Относительный путь

Часто относительные пути используются гораздо чаще чем абсолютные пути. В основном относительный путь указывается тогда, когда Вам нужно отправить посетителя на другую страницу Вашего сайта, или вставить объект (например изображение) на одной из страниц.

При этом использование относительно пути вполне оправдано. В зависимости от структуры сайта, зависит то, какую форму относительного пути нужно использовать. Существует два вида относительного пути: путь относительно документа, путь относительно корня сайта.

Путь относительно документа

Именно путь относительно документа используется чаще всего. Такие ссылки ещё называются локальными. В основном такой путь используется тогда, когда текущий и связанный документ(страница) находятся в одной директории.

Если переместить документ в другую директорию, то путь(ссылку) придётся менять. Хотя можно ссылаться и на документы(страницы) из других директорий. Для этого путь прописывается от текущего документа до целевого документа(страницы).

При этом путь относительно документа должен задаваться в зависимости от структуры директорий.
Возьмём к примеру простейшую структуру статического сайта.

Относительный путь к файлам: что это значит, зачем нужен?

Предположим, что каждое изображение в каталоге images нужно вставить в соответствующие страницы home.html, products.html, contact.html.

Для того что бы вставить изображение к примеру на страницу «home.html», нужно прописать путь, где расположено изображение.

Если использовать путь относительно документа, то нужно будет прописать в коде страницы следующее:

Этот код для вставки изображения на страницу — неполный. Так как он не содержит нескольких важных атрибутов, таких как ширина, высота и др. Атрибут src, здесь служит для указания пути к файлу. Здесь опущены все остальные атрибуты, так как они сейчас не столь важны.

Главное сейчас, что бы Вы имели представление о том, как выглядит путь относительно документа.
При использовании путей относительно документа отсутствует часть абсолютного пути. Часть абсолютного пути, здесь усекается, как для текущего документа(страницы), так и для связанного.

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

Напомню ещё раз про то, что при использовании пути относительно документа, нужно учитывать исходное расположение файлов.

Представим ситуацию, когда страница products.html, будет располагаться не в корневой директории сайта(как это было в предыдущем примере), а в подкаталоге. Теперь необходимо вставить изображение, в файл products.html, который находится, глубже чем корневая директория сайта.

Относительный путь к файлам: что это значит, зачем нужен?

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

Как видно из приведённого выше кода, к пути теперь добавилось следующее: ../. Как раз эта последовательность символов ../ и служит для перехода на одну директорию(уровень) выше в иерархии каталогов.

Путь в вышеприведённом коде можно прочесть так: «Перейти на один каталог выше(назад), зайти в директорию images и взять от туда файл products.png«.
Если ..

/ означает переход на одну директорию(уровень) выше в иерархии каталогов, то символ / обозначает переход на один уровень ниже.
Последовательность символов ../ можно использовать в пути неоднократно.

Например, если файл products.html переместить в три директории вложенные в друг друга, то нужно будет использовать следующий код:

Как видно из предыдущих примеров, использование путей относительно документа во многих случаях оправдано. Такой вид пути часто применяется в практике, и его можно применить практически в любой ситуации. Как я уже говорил выше, существует ещё путь относительно корня сайта, о котором собственно и пойдет речь ниже.

Путь относительно корня сайта

Вы наверное уже поняли что пути относительно документа используются очень часто. Но при их использовании существует одна проблема. Которая заключается в том, что при смене структуры директорий, пути придется менять.

Но такая проблема решаема при использовании путей относительно корня сайта. Где путь указывается от корневой директории до документа.
Все пути относительно корня сайта начинаются со знака /.

Только здесь, в отличии от путей относительно документа этот знак используется для указания корневой директории. Потому, что он используется в начале пути.
Путь относительно корня сайта позволяет перемещать некоторые файлы, без ущерба для ссылок.

Этот тип пути Вы сможете использовать только на web-сервере в интернете, или на web-сервере расположенном на локальной машине.

В качестве web-сервера на локальной машине может выступать — XAMPP. Который позволит у себя на компьютере создать среду для созданию сайтов и их предварительного тестирования.

Корневой относительный путь не содержит ни протокола http, ни доменного имени. И как я уже говорил начинается с указания символа /, который указывает на корневую директорию. В этой директории обычно располагается индексный файл главной страницы.

Например, /images/products.png обозначает, что файл products.png находится в папке images, которая расположена в корневом каталоге.

Самый простой способ определить корневой относительный путь — взять абсолютный и отбросить http:// и имя хоста.

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

Допустим есть файл _contact.html, который содержит информацию о телефонных номерах, e-mail и содержит изображение contact.png.

(Пускай это будет небольшая таблица, которая будет располагаться на каждой странице сайта.)

Относительный путь к файлам: что это значит, зачем нужен?

Следующий код предназначен для вставки изображения «contact.png».

Код, который необходим для вставки одного файла в другой, выходит за рамки этой статьи. Здесь все зависит от используемого языка программирования.

Я надеюсь, что Вы уже знаете какой тип пути использовался в вышеприведённом коде. Если нет, тогда посмотрите приведённое выше определение пути относительно документа.
Теперь, когда посетитель зайдет на такие страницы сайта как home.html, contact.ntml, он увидит прекрасно отображаемую страницу. В каждую из которых вставлен файл _contact.

Читайте также:  Базы данных MySQL: что такое и как с ними работать

html, в который, в свою очередь, вставлено изображение contact.png.
Другими словами зайдя, к примеру, на страницу home.html, происходит следующее: «Выполняется код основной страницы home.html. Затем вставляется и исполняется код страницы _contact.html. Код страницы _contact.

html, говорит что нужно перейти в директорию images и взять от туда изображение contact.png«.
Если опустить сам код для вставки, то все работает отлично. Но вот если запустить страницу products.html, то произойдет ошибка. Так как код будет пытаться найти директорию images и файл contact.

png в директории products. Но такой директории там не существует, из за чего собственно и возникает проблема.
Становится ясным, что использовать путь относительно документа здесь нельзя.
Конечно здесь можно использовать абсолютный путь. О плюсах и минусах данного подхода я говорил выше.

В общем говоря, это одна из ситуаций, когда нужно использовать путь относительно корня сайта. При использовании пути относительно корня сайта, ссылка будет всегда начинаться с корневого каталога(корня сайта). Такой тип пути позволит использовать код для вставки, например изображения, независимо от иерархии сайта, и его директорий.

Использование пути относительно корня сайта в вышеприведённом примере, позволит избежать проблем, со вставкой изображения. Потому как независимо от того где будет использовать такой тип пути, он всегда найдет указанный в нем файл.

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

Теперь изображение будет корректно вставляться на любой из страниц сайта.

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

Наверх

Абсолютные и относительные пути в PHP. Полный разбор

Пути к файлам — тема, которая обычно взрывает мозг новичкам. Но не волнуйся, сейчас мы всё расставим по полочкам.

Чем отличаются пути в PHP и URL

Когда мы смотрим любимый фильм или сериал, мы видим только готовый продукт.

А за кадром существует совсем другой, невидимый для нас мир: стилисты и гримёры, искусственные декорации, наложение спецэффектов и многое другое.

Относительный путь к файлам: что это значит, зачем нужен?

Процесс съёмок музыкального клипа

В PHP существует такое же разделение. С одной стороны — реальная файловая система с реальными папками и файлами, с другой — URL адреса, которые могут не иметь ничего общего с реальной файловой структурой.

Если ты видишь на каком-нибудь сайте страницу с URL /category/monitors — это совсем не значит, что на сайте есть скрипт /category/monitors/index.php.

Вполне вероятно, что и такой папки там тоже нет, а все URL адреса обрабатываются одним единственным PHP файлом.

И даже если в URL присутствует расширение файла, например /about.html — это тоже не говорит о существовании файла about.html. Может он есть, а может и нет.

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

Ошибка №1: Подстановка физического пути в URL

Очень частая ошибка новичков — пытаться подставить в URL адрес ссылку на физический файл, вроде такого:

Статья

Это неправильно. Браузер не может видеть реальную файловую структуру сервера. Он не видит никаких дисков D, он видит только URL адреса.

Правильная ссылка выглядит так (разницу объясню чуть позже):

Статья
Статья
Статья

Ошибка №2: Подключение скриптов по URL

Иногда новички пытаются подключить физический файл по его URL:

require 'http://test.ru/article.php';

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

Как правильно:

require 'config.php';
require 'D:OpenServerdomains est.ruconfig.php';
require __DIR__ . '/config.php';

Абсолютный путь в PHP

Абсолютный путь — это полный путь к папке или файлу. Вот пара примеров для разных операционных систем:

  • D:OpenServerdomains est.ruindex.php — для OpenServer на Windows
  • /var/www/html/test.ru/index.php — для Ubuntu

Как видите, это полный путь от корня диска до конкретного файла или папки. Начинается со слеша или буквы диска (Windows).

Получить абсолютный путь скрипта можно с помощью магической константы __FILE__:

Не используйте относительные имена файлов

Доступ к файлу можно получить по абсолютному или относительному имени (пути).

Абсолютное («полностью квалифицированное») имя начинается с имени диска или сервера и указывает все компоненты пути, например: «C:ProjectsTestProjectData.txt» или «\SERVERProjectsTestProjectData.txt». Такое имя всегда однозначно указывает на файл — вне зависимости от любых внешних факторов.

Относительное имя содержит не все компоненты пути и указывает файл относительно другого каталога, имя которого в самом имени не указано, например: «Data.txt» или «..Data.txt». Для определения точного положения файла недостаточно одного относительного имени, необходимо ещё имя каталога, относительно которого будет трактоваться это имя.

Поэтому один и тот же относительный путь может ссылаться на разные файлы. К примеру, путь «Data.txt» ссылается на C:ProjectsTestProjectData.txt, если текущий каталог (или каталог, относительно которого происходит разрешение имени) равен C:ProjectsTestProject, но этот же путь будет ссылаться на C:WindowsData.txt, если текущий каталог — C:Windows.

Подробнее о файловых именах можно почитать здесь.

Здесь же, в этой статье, я хочу показать, что вам никогда не нужно использовать относительные имена файлов.

Очень часто начинающие программисты используют относительные пути к файлам для работы с файлами внутри папки своей программы, например:
Assign(F, 'input.txt');
Reset(F);
// …

Что не так с этим кодом?

Начинающий программист считает, что имя «input.txt» будет вычисляться относительно пути к его программе. Иными словами, если программа (.exe-файл) лежит в «C:ProjectsTestProject», то, указав «input.txt» в Assign, мы откроем файл «C:ProjectsTestProjectinput.txt». Это попросту неверно!

Все относительные пути вычисляются относительно т.н. текущего каталога. Проблема состоит в том, что начинающие программисты не понимают, что это такое. Они считают, что текущий каталог — это каталог программы. Это не так:

  1. Для начала, текущий каталог при старте вашей программы задаётся не вами, а вызывающим вас процессом. Все функции запуска программы имеют строковый параметр для передачи туда имени каталога, который станет текущим для запускаемой программы. И вызывающая вас программа может передать туда всё, что угодно. Это может быть папка с вашей программой, да. Но это может быть и любая другая папка;
  2. Далее, к примеру, если ваша программа запускается через ярлык на рабочем столе или ярлык в меню Пуск / Программы, то текущий каталог для вашей программы указан в свойствах ярлыка. Например, сама Delphi запускается с текущим каталогом = папке с проектами (например, C:Program FilesBorlandDelphi 7Projects или даже просто C:Projects) — что, очевидно, не равно папке с программой (C:Program FilesBorlandDelphi 7Bin);
  3. А если вы пишете программу, которая открывает файлы какого-то типа, то вы, вероятно, назначите свою программу для открытия таких файлов (ассоциируете тип файлов в вашей программой). Но когда пользователь дважды-щёлкнет по такому файлу, ваша программа запустится с текущим каталогом равным каталогу открываемого файла. Т.е. текущий каталог будет C:Documents, а не C:ProjectsTestProject;
  4. А если вы пишете код для службы (Win32 Service), то текущий каталог будет C:WindowsSystem32.

Итак, поскольку текущий каталог при запуске задаётся не вами, то он не находится под вашим контролем. А раз так, то вам не следует делать предположений о том, чем он может оказаться. Достаточно наивно будет думать, что текущий каталог при старте всегда будет равен каталогу с программой.
P.S. Кроме того, размещение файлов с данными/конфигурацией в папке с программой — крайне плохая идея, если только вы не пишете портативную (portable) программу (см. также).

Далее, есть же такие функции как GetCurrentDir и (что интереснее) SetCurrentDir. «Set» решительным образом намекает на то, что текущий каталог — вещь не фиксированная и его можно менять. Иными словами, текущий каталог не только может быть не равен каталогу программы, но и вообще может меняться в процессе выполнения программы! Действительно:
Assign(F, 'input.txt');
Reset(F); //
SetCurrentDir('C:Windows');
Assign(F, 'input.txt');
Reset(F); // Конечно, тот факт, что текущий каталог можно менять, сам по себе ещё не означает проблему. Но посмотрите на такой код:if not OpenDialog1.Execute then Exit;
OutputFileName := OpenDialog1.FileName; Assign(F, 'input.txt');
Reset(F); //
Что случилось? Дело в том, что внешний код (а именно — код диалога открытия файла) поменял текущий каталог. Ваш код оказался не готов к этому. P.S. Почему вообще диалог открытия файла меняет текущий каталог? Потому что вы (= прикладные программисты) пишете код с использованием относительных имён файлов.
Иными словами, проблема состоит в том, что кто угодно может менять текущий каталог в любой момент времени. Даже если в вашем коде нет вызовов другого внешнего кода, который меняет текущий каталог, всё равно текущий каталог может быть изменён другим потоком в вашей программе. Даже если вы сами не создаёте других потоков, потоки могут быть созданы DLL, которые загружены в вашу программу. Помимо системных DLL, это могут быть DLL от любых оконных ловушек, расширителей оболочек и даже антивирусов.

Вывод? Код, который адресует файл относительным именем работает благодаря случайности, а именно: благодаря тому, что никакой другой код не изменил текущий каталог перед тем, как вы вызвали функцию доступа к файлу, передав ей относительное имя файла.

Неправильные решения

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

Например:AppPath := ExtractFilePath(ParamStr(0));
SetCurrentDir(AppPath); // AppPath = 'C:ProjectsTestProject'
Assign(F, 'input.txt');
Reset(F); //
Или же сохраняют/восстанавливают текущий каталог перед вызовом кода, который потенциально может менять текущий каталог, например:
CurrentDir := GetCurrentDir;
if not OpenDialog1.

Execute then Exit;
OutputFileName := OpenDialog1.FileName;
SetCurrentDir(CurrentDir);
Assign(F, 'input.txt');
Reset(F); // Что не так с этими решениями?

Во-первых, текущий каталог, являясь глобальной переменной, может быть изменён другим потоком как раз между вызовами SetCurrentDir и Assign.

Если этого не происходит, то ваш (некорректный) код работает благодаря случайности (случайность состоит в том, что этого не произошло).

Во-вторых, если вы форсированно устанавливаете свой текущий каталог, отбрасывая каталог, заданный вам вызывающим, вы можете открыть не тот файл! Например, пусть вы пишете программу-конвертер, пусть она конвертирует картинки из формата .png в формат .jpg, пусть вашу программу можно вызвать, передав ей имя файла в командной строке. Тогда пользователь может вызвать вас так:C:Documents>»C:Converterconvert.exe» «holidays.png» (здесь C:Documents> является приглашением командной строки, а «C:Converterconvert.exe» «holidays.png» — непосредственно командной строкой).

Т.е. пользователь открыл консоль, он находится в папке C:Documents и вызывает вас (convert.exe) из папки C:Converter, передавая вам имя файла (holidays.png) параметром командной строки.

(Почти аналогичная ситуация будет если пользователь дважды-щёлкнет по файлу holidays.png в открытой папке C:Documents в Проводнике — при условии, что ваша программа ассоциирована с .png файлами).

В этом случае ваша программа (convert.exe) запустится из папки C:Converter, но текущим каталогом для неё будет C:Documents. Если вы форсированно смените текущий каталог на папку с программой (C:Converter), то не сможете открыть файл holidays.png, поскольку он находится в папке C:Documents, а не C:Converter.

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

Имя файла вам могли передать в относительном формате (к примеру, вызвали вас из командной строки). Вы передали это имя «как есть» в первую копию своей программы. Однако текущий каталог первой копии равен неизвестно чему, он не равен текущему каталогу вашей второй копии.

Как бы вы не меняли текущий каталог в первой копии программы перед попыткой доступа к файлу, который вам передали, вы никак не можете знать, каким же был текущий каталог в другой программе (вашей второй копии).

И снова, ваш код, оперирующий относительными путями файлов, будет работать только благодаря случайности (в этом случае случайность состоит в том, что текущий каталог оказался одинаков в обоих экземплярах вашей программы).

Более того, когда вы меняете текущий каталог на какой-то — этот каталог открывается вашей программой. В частности, это означает, что вы не можете удалить этот каталог.

Зачем нужны относительные пути?

Зачем же вообще относительные пути, если они так плохи?

Ну, относительные пути нужны для человека-оператора. Они экономят время на набор текста.

Действительно, вместо того, чтобы вводить длинный путь вида C:Documents and SettingsAdminDocumentsData from 2014March.doc — вы можете ввести просто March.

doc (конечно же, при условии, что вы «находитесь» в каталоге C:Documents and SettingsAdminDocumentsData from 2014).

Правильное решение

Из вышесказанного напрямую следует правильное решение. Раз относительные имена предназначены для человека, то вам (вашему коду) не следует их использовать. Всё, что вы можете сделать с относительным именем — перевести его в абсолютное. И оперировать в дальнейшем только абсолютным именем файла.

Для этого вам понадобится функция нормализации внешних данных, которая преобразует относительное имя файла, принятое от пользователя, в абсолютное, пригодное для использования в коде программы. Как минимум, функция нормализации должна преобразовывать относительное имя файла в абсолютное. Но помимо этого желательно также выполнить и другие операции.

Вот конкретный список действий:

  1. Развернуть переменные окружения;
  2. Преобразовать относительное имя в абсолютное;
  3. Канонизировать путь, свернув '.', '..' и лишние разделители каталогов;
  4. Преобразовать короткий путь в длинный.

Как реализовать такую функцию нормализации — мы посмотрим ниже, в разделе практики.

А пока давайте сформулируем свод правил, которому вам стоит придерживаться:

  1. Каждый раз, когда вы получаете имя файла из внешнего источника (командной строки, конфигурации, диалога и т.п.) — сохраняйте его в переменную с суффиксом Unsafe. Например, DocumentFileNameUnsafe := ParamStr(1);
  2. Не передавайте переменные с суффиксом Unsafe в функции открытия файлов;
  3. Не передавайте переменные с суффиксом Unsafe в другие программы (через IPC, командную строку и т.п.);
  4. Вы можете сохранять переменные с суффиксом Unsafe в файл конфигурации;
  5. Передайте переменную с суффиксом Unsafe в функцию нормализации и сохраните результат в переменной без суффикса Unsafe. Например, DocumentFileName := PathSearchAndQualify(DocumentFileNameUnsafe);
  6. Вы можете передавать переменные без суффикса Unsafe в функции открытия файлов и другие программы (IPC, командная строка и т.п.);
  7. Если вам точно известно имя файла (оно задано константой в коде) и вы знаете папку, в которой лежит файл (не обязательно константа, но хотя бы логическое размещение вида «каталог программы», «подкаталог ABC папки Application Data»), то получите путь к каталогу, затем добавьте к нему имя файла и сохраните результат в переменную без суффикса Unsafe. Если имя файла, заданное в константе, содержит '.' или '..' — выполните нормализацию перед сохранением в переменную;
  8. Измените текущий каталог на, скажем, C:WindowsSystem32 (разумеется, путь надо задавать не константой, а получать через GetSystemDirectory) сразу после того, как вы нормализовали все имена файлов из параметров командной строки;
  9. Если вы запускаете внешнюю программу, передавая ей имя файла для открытия, то задайте текущий каталог для запускаемой программы равным каталогу, содержащему открываемый файл (даже хотя вы передаёте полное имя файла);
  10. Используйте суффикс Dir для переменных и функций, которые хранят/возвращают путь (к каталогу) без ведомого разделителя (например, 'C:Windows'). Используйте суффикс Path для переменных и функций, которые хранят/возвращают путь с ведомым разделителем (например, 'C:Windows'). Избегайте использования переменных и функций, для которых вы не знаете, будет ли в конце пути разделитель. Преобразуйте такие переменные и функции в Dir или Path с помощью ExcludeTrailingPathDelimiter и IncludeTrailingPathDelimiter соответственно, например: CurrentPath := IncludeTrailingPathDelimiter(GetCurrentFolder). Эта семантика с Dir/Path защитит вас от неверных результатов вида ExtractFileDir(…) + 'input.txt' = 'C:Programinput.txt' или ExtractFilePath(…) + 'input.txt' = 'C:Program\input.txt'.
  11. Старайтесь хранить имена каталогов с ведущим разделителем, а имена файлов — без разделителя. Например, 'C:Windows', но 'C:Windows
    otepad.exe';
  12. Если вы работаете в Unicode-версии Delphi (Delphi 2009 и выше) и хотите передать имя файла во внешний код (программу или DLL) — преобразуйте имя файла в короткое имя файла (PathGetShortPath — см. ниже). Это увеличит шансы правильного открытия файла, если вызываемый код не поддерживает Unicode или неверно обрабатывает пробелы;
  13. Если вы передаёте имя файла по IPC — всегда предпочитайте Unicode-форму (используйте WideString).

Тем не менее, существует один случай, когда вам нужно использовать в своём коде относительные пути. Речь идёт о сохранении путей в «конфигурацию» относительно некого корневого элемента. Например, это может быть портативная (portable) программа, сохраняющая пути к файлам, относительно каталога с программой. Или это может быть многофайловый документ. Например, проект Delphi сохраняется в .dpr файл (а его настройки — в .dproj файл в том же каталоге). При этом настройки проекта Delphi сохраняют пути до файлов проекта в относительной форме — пути рассчитываются относительно каталога с .dpr/.dproj. И если у вас возникает аналогичная ситуация, то действовать следует так:

  1. Проведите нормализацию имён файлов, как указано в алгоритме выше, сохранив их в переменные без суффикса Unsafe;
  2. Получите каталог, относительно которого вам нужно сохранять пути (каталог с программой, каталог с корневым файлом документа и т.п.). Нормализуйте его и сохраните в переменную без суффикса Unsafe;
  3. Получите относительный путь для вашего пути файла из п1 относительно каталога из п2 с помощью функции PathGetRelativePath (см. ниже раздел практики). Сохраните результат в переменную с префиксом Unsafe;
  4. Запишите переменную из п3 в вашу конфигурацию или документ.

Форматы путей к файлам в системах Windows

  • 05/14/2021
  • Чтение занимает 7 мин
    • a
    • o

Члены большинства типов в пространстве имен System.IO имеют параметр path, который позволяет указать абсолютный или относительный путь к ресурсу в файловой системе. Этот путь передается в API файловой системы Windows. В этом разделе рассматриваются форматы путей к файлам, которые можно использовать в операционных системах Windows.

Традиционные пути DOS

Стандартный путь DOS может состоять из трех компонентов:

  • Буква тома или диска, после которой следует разделитель томов (:).
  • Имя каталога. Символ разделителя каталогов служит для разделения подкаталогов во внутренней иерархии каталога.
  • Необязательное имя файла. Символ разделителя каталогов служит для разделения пути к файлу и его имени.

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

Путь
Описание:
C:DocumentsNewslettersSummer2018.pdf Абсолютный путь к файлу из корня диска C:.
Program FilesCustom UtilitiesStringFinder.exe Абсолютный путь из корня текущего диска.
2018January.xlsx Относительный путь к файлу в подкаталоге текущего каталога.
..PublicationsTravelBrochure.pdf Относительный путь к файлу в каталоге, который является одноранговым для текущего каталога.
C:Projectsapilibraryapilibrary.sln Абсолютный путь к файлу из корня диска C:.
C:Projectsapilibraryapilibrary.sln Относительный путь из текущего каталога диска C:.

Важно!

Обратите внимание на различия между двумя последними путями. В обоих случаях задается необязательный описатель тома (C:), однако первый путь, в отличие от второго, начинается с корня указанного тома.

В результате первый путь является абсолютным из корневого каталога диска C:, тогда как второй — относительным из текущего каталога C:.

Использование второй формы пути в тех случаях, когда предполагается наличие первой, является распространенным источником ошибок, связанных с путями к файлам в Windows.

Чтобы определить, является ли путь к файлу полным (такой путь не зависит от текущего каталога и не изменяется при смене текущего каталога), можно вызвать метод Path.IsPathFullyQualified. Обратите внимание, что такой путь может включать сегменты с относительным путем к каталогу (. и ..), но при этом по-прежнему будет полным, если разрешенный путь всегда указывает на одно и то же место.

В приведенном ниже примере показано различие между абсолютными и относительными путями. Предполагается, что каталог D:FY2018 существует и вы не установили какой-либо текущий каталог для диска D: из командной строки перед запуском этого примера.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;

public class Example
{
public static void Main(string[] args)
{
Console.WriteLine($»Current directory is '{Environment.CurrentDirectory}'»);
Console.WriteLine(«Setting current directory to 'C:\'»);

Directory.SetCurrentDirectory(@»C:»);
string path = Path.GetFullPath(@»D:FY2018″);
Console.WriteLine($»'D:\FY2018' resolves to {path}»);
path = Path.GetFullPath(@»D:FY2018″);
Console.WriteLine($»'D:FY2018' resolves to {path}»);

Console.WriteLine(«Setting current directory to 'D:\Docs'»);
Directory.SetCurrentDirectory(@»D:Docs»);

path = Path.GetFullPath(@»D:FY2018″);
Console.WriteLine($»'D:\FY2018' resolves to {path}»);
path = Path.GetFullPath(@»D:FY2018″);

// This will be «D:DocsFY2018″ as it happens to match the drive of the current directory
Console.WriteLine($»'D:FY2018' resolves to {path}»);

Console.WriteLine(«Setting current directory to 'C:\'»);
Directory.SetCurrentDirectory(@»C:»);

path = Path.GetFullPath(@»D:FY2018″);
Console.WriteLine($»'D:\FY2018' resolves to {path}»);

// This will be either «D:FY2018» or «D:FY2018FY2018″ in the subprocess. In the sub process,
// the command prompt set the current directory before launch of our application, which
// sets a hidden environment variable that is considered.
path = Path.GetFullPath(@»D:FY2018″);
Console.WriteLine($»'D:FY2018' resolves to {path}»);

if (args.Length < 1) { Console.WriteLine(@"Launching again, after setting current directory to D:FY2018"); Uri currentExe = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute); string commandLine = $"/C cd D:\FY2018 & "{currentExe.LocalPath}" stop"; ProcessStartInfo psi = new ProcessStartInfo("cmd", commandLine); ; Process.Start(psi).WaitForExit(); Console.WriteLine("Sub process returned:"); path = Path.GetFullPath(@"D:FY2018"); Console.WriteLine($"'D:\FY2018' resolves to {path}"); path = Path.GetFullPath(@"D:FY2018"); Console.WriteLine($"'D:FY2018' resolves to {path}"); } Console.WriteLine("Press any key to continue... "); Console.ReadKey(); } } // The example displays the following output: // Current directory is 'C:Programsfile-paths' // Setting current directory to 'C:' // 'D:FY2018' resolves to D:FY2018 // 'D:FY2018' resolves to d:FY2018 // Setting current directory to 'D:Docs' // 'D:FY2018' resolves to D:FY2018 // 'D:FY2018' resolves to D:DocsFY2018 // Setting current directory to 'C:' // 'D:FY2018' resolves to D:FY2018 // 'D:FY2018' resolves to d:FY2018 // Launching again, after setting current directory to D:FY2018 // Sub process returned: // 'D:FY2018' resolves to D:FY2018 // 'D:FY2018' resolves to d:FY2018 // The subprocess displays the following output: // Current directory is 'C:' // Setting current directory to 'C:' // 'D:FY2018' resolves to D:FY2018 // 'D:FY2018' resolves to D:FY2018FY2018 // Setting current directory to 'D:Docs' // 'D:FY2018' resolves to D:FY2018 // 'D:FY2018' resolves to D:DocsFY2018 // Setting current directory to 'C:' // 'D:FY2018' resolves to D:FY2018 // 'D:FY2018' resolves to D:FY2018FY2018 Imports System.Diagnostics Imports System.IO Imports System.Reflection Public Module Example Public Sub Main(args() As String) Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'") Console.WriteLine("Setting current directory to 'C:'") Directory.SetCurrentDirectory("C:") Dim filePath As String = Path.GetFullPath("D:FY2018") Console.WriteLine($"'D:\FY2018' resolves to {filePath}") filePath = Path.GetFullPath("D:FY2018") Console.WriteLine($"'D:FY2018' resolves to {filePath}") Console.WriteLine("Setting current directory to 'D:\Docs'") Directory.SetCurrentDirectory("D:Docs") filePath = Path.GetFullPath("D:FY2018") Console.WriteLine($"'D:\FY2018' resolves to {filePath}") filePath = Path.GetFullPath("D:FY2018") ' This will be "D:DocsFY2018" as it happens to match the drive of the current directory Console.WriteLine($"'D:FY2018' resolves to {filePath}") Console.WriteLine("Setting current directory to 'C:\'") Directory.SetCurrentDirectory("C:") filePath = Path.GetFullPath("D:FY2018") Console.WriteLine($"'D:\FY2018' resolves to {filePath}") ' This will be either "D:FY2018" or "D:FY2018FY2018" in the subprocess. In the sub process, ' the command prompt set the current directory before launch of our application, which ' sets a hidden environment variable that is considered. filePath = Path.GetFullPath("D:FY2018") Console.WriteLine($"'D:FY2018' resolves to {filePath}") If args.Length < 1 Then Console.WriteLine("Launching again, after setting current directory to D:FY2018") Dim currentExe As New Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute) Dim commandLine As String = $"/C cd D:FY2018 & ""{currentExe.LocalPath}"" stop" Dim psi As New ProcessStartInfo("cmd", commandLine) Process.Start(psi).WaitForExit() Console.WriteLine("Sub process returned:") filePath = Path.GetFullPath("D:FY2018") Console.WriteLine($"'D:\FY2018' resolves to {filePath}") filePath = Path.GetFullPath("D:FY2018") Console.WriteLine($"'D:FY2018' resolves to {filePath}") End If Console.WriteLine("Press any key to continue... ") Console.ReadKey() End Sub End Module ' The example displays the following output: ' Current directory is 'C:Programsfile-paths' ' Setting current directory to 'C:' ' 'D:FY2018' resolves to D:FY2018 ' 'D:FY2018' resolves to d:FY2018 ' Setting current directory to 'D:Docs' ' 'D:FY2018' resolves to D:FY2018 ' 'D:FY2018' resolves to D:DocsFY2018 ' Setting current directory to 'C:' ' 'D:FY2018' resolves to D:FY2018 ' 'D:FY2018' resolves to d:FY2018 ' Launching again, after setting current directory to D:FY2018 ' Sub process returned: ' 'D:FY2018' resolves to D:FY2018 ' 'D:FY2018' resolves to d:FY2018 ' The subprocess displays the following output: ' Current directory is 'C:' ' Setting current directory to 'C:' ' 'D:FY2018' resolves to D:FY2018 ' 'D:FY2018' resolves to D:FY2018FY2018 ' Setting current directory to 'D:Docs' ' 'D:FY2018' resolves to D:FY2018 ' 'D:FY2018' resolves to D:DocsFY2018 ' Setting current directory to 'C:' ' 'D:FY2018' resolves to D:FY2018 ' 'D:FY2018' resolves to D:FY2018FY2018

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

UNC-пути

UNC-пути (универсальное соглашение об именовании) используются для доступа к сетевым ресурсам и имеют следующий формат:

  • Имя сервера или узла, которому предшествуют символы \. В качестве имени сервера может выступать имя компьютера NetBIOS, а также IP-адрес или полное доменное имя (поддерживаются адреса IPv4 и IPv6).
  • Имя общего ресурса, которое отделяется от имени узла символами . Имя сервера и имя общего ресурса в совокупности образуют том.
  • Имя каталога. Символ разделителя каталогов служит для разделения подкаталогов во внутренней иерархии каталога.
  • Необязательное имя файла. Символ разделителя каталогов служит для разделения пути к файлу и его имени.

Ниже приводятся некоторые примеры UNC-путей:

Path
Описание
\system07C$ Корневой каталог диска C: на компьютере system07.
\Server2ShareTestFoo.txt Файл Foo.txt в тестовом каталоге тома \Server2Share.

UNC-пути всегда должны быть полными. Они могут включать сегменты с относительным путем к каталогу (. и ..), однако они должны быть частью полного пути. Использовать относительные пути можно только посредством сопоставления UNC-пути с буквой диска.

Пути к устройствам DOS

В операционной системе Windows используется унифицированная объектная модель, которая указывает на все ресурсы, включая файлы.

Эти пути к объектам доступны из окна консоли и предоставляются на уровень Win32 с использованием специальной папки с символьными ссылками, с которыми сопоставляются устаревшие пути DOS и UNC.

Доступ к этой специальной папке осуществляется с использованием синтаксиса пути к устройству DOS, который может иметь одну из приведенных ниже форм:

\.C:TestFoo.txt
\?C:TestFoo.txt

Помимо использования буквы диска, вы можете указать том с помощью его GUID. Синтаксис будет иметь вид:

\.Volume{b75e2c83-0000-0000-0000-602f00000000}TestFoo.txt
\?Volume{b75e2c83-0000-0000-0000-602f00000000}TestFoo.txt

Примечание

Синтаксис пути к устройству DOS поддерживается в реализациях платформы .NET для ОС Windows, начиная с версий .NET Core 1.1 и .NET Framework 4.6.2.

Путь к устройству DOS состоит из следующих компонентов:

  • Описатель пути к устройству (\. или \?), который идентифицирует путь как путь к устройству DOS.

    Примечание
    Описатель \? поддерживается во всех версиях .NET Core, в .NET 5 и более поздних версий, а также в .NET Framework, начиная с версии 4.6.2.

  • Символьная ссылка на «реальный» объект устройства (C: в случае имени диска или Volume{b75e2c83-0000-0000-0000-602f00000000} в случае GUID тома).
    Первый сегмент пути к устройству DOS после описателя пути к устройству идентифицирует том или диск. (Например, \?C: и \.BootPartition.)
    Для UNC-путей существует специальная ссылка, которая называется UNC. Пример:
    \.UNCServerShareTestFoo.txt
    \?UNCServerShareTestFoo.txt
    Для UNC-путей к устройствам часть сервера или общего сетевого ресурса образует том. Например, в пути \?server1e:utilities\filecomparer часть server1utilities представляет сервер или общий сетевой ресурс. Это важно при вызове такого метода, как Path.GetFullPath(String, String) с сегментами с относительным путем к каталогу, поскольку переход дальше тома невозможен.

Пути к устройствами DOS по определению являются полными. Сегменты с относительным путем к каталогу (. и ..) в них не допускаются. Они никогда не задаются относительно текущего каталога.

Пример. Способы задать ссылку на один и тот же файл

В следующем примере демонстрируются некоторые способы задать ссылку на файл с использованием API в пространстве имен System.IO. В этом примере создается экземпляр объекта FileInfo и используются его свойства Name и Length, чтобы отобразить имя и длину файла.

Ссылка на основную публикацию