Что такое относительное имя файла
В операционной системе UNIX существуют три базовых понятия: "процесс", "файл" и "пользователь". С понятием " пользователь " мы только что уже столкнулись и будем сталкиваться в дальнейшем при изучении работы операционной системы UNIX . Понятие "процесс" характеризует динамическую сторону происходящего в вычислительной системе, оно будет подробно обсуждаться в лекции 2 и в описании последующих семинаров. Понятие " файл " характеризует статическую сторону вычислительной системы.
Из предыдущего опыта работы с вычислительной техникой вы уже имеете некоторое представление о файле, как об именованном наборе данных, хранящемся где-нибудь на магнитных дисках или лентах. Для нашего сегодняшнего обсуждения нам достаточно такого понимания, чтобы разобраться в том, как организована работа с файлами в операционной системе UNIX . Более подробное рассмотрение понятия " файл " и организации файловых систем для операционных систем в целом будет приведено в лекции 11 и лекции 12, а также на семинарах 11–12, посвященных организации файловых систем в UNIX .
Все файлы, доступные в операционной системе UNIX , как и в уже известных вам операционных системах, объединяются в древовидную логическую структуру. Файлы могут объединяться в каталоги или директории. Не существует файлов, которые не входили бы в состав какой-либо директории. Директории в свою очередь могут входить в состав других директорий. Допускается существование пустых директорий, в которые не входит ни один файл , и ни одна другая директория (см. рис. 1–2.1). Среди всех директорий существует только одна директория , которая не входит в состав других директорий – ее принято называть корневой. На настоящем уровне нашего незнания UNIX мы можем заключить, что в файловой системе UNIX присутствует, по крайней мере, два типа файлов: обычные файлы, которые могут содержать тексты программ, исполняемый код , данные и т.д. – их принято называть регулярными файлами, и директории.
Каждому файлу (регулярному или директории) должно быть присвоено имя. В различных версиях операционной системы UNIX существуют те или иные ограничения на построение имени файла. В стандарте POSIX на интерфейс системных вызовов для операционной системы UNIX содержится лишь три явных ограничения:
- Нельзя создавать имена большей длины, чем это предусмотрено операционной системой (для Linux – 255 символов).
- Нельзя использовать символ NUL (не путать с указателем NULL !) – он же символ с нулевым кодом, он же признак конца строки в языке C.
- Нельзя использовать символ '/' .
От себя добавим, что также нежелательно применять символы "звездочка" – "*" , "знак вопроса" – "?" , "кавычка" – "\"" , " апостроф " – "\`" , " пробел " – " " и " обратный слэш" – "\\" (символы записаны в нотации символьных констант языка C).
Единственным исключением является корневая директория , которая всегда имеет имя "/" . Эта же директория по вполне понятным причинам представляет собой единственный файл , который должен иметь уникальное имя во всей файловой системе. Для всех остальных файлов имена должны быть уникальными только в рамках той директории, в которую они непосредственно входят. Каким же образом отличить два файла с именами "aaa.c" , входящими в директории "b" и "d" на рисунке 1–2.1, чтобы было понятно о каком из них идет речь? Здесь на помощь приходит понятие полного имени файла .
Давайте мысленно построим путь от корневой вершины дерева файлов к интересующему нас файлу и выпишем все имена файлов (т.е. узлов дерева), встречающиеся на нашем пути, например, "/ usr b aaa.c" . В этой последовательности первым будет всегда стоять имя корневой директории, а последним – имя интересующего нас файла. Отделим имена узлов друг от друга в этой записи не пробелами, а символами "/" , за исключением имени корневой директории и следующего за ним имени ( "/usr/b/aaa.c" ). Полученная запись однозначно идентифицирует файл во всей логической конструкции файловой системы. Такая запись и получила название полного имени файла .
Понятие о текущей директории. Команда pwd. Относительные имена файлов
Полные имена файлов могут включать в себя достаточно много имен директорий и быть очень длинными, с ними не всегда удобно работать. В то же время, существуют такие понятия как текущая или рабочая директория и относительное имя файла .
Для каждой работающей программы в операционной системе, включая командный интерпретатор ( shell ), который обрабатывает вводимые команды и высвечивает приглашение к их вводу, одна из директорий в логической структуре файловой системы назначается текущей или рабочей для данной программы. Узнать, какая директория является текущей для вашего командного интерпретатора, можно с помощью команды операционной системы pwd .
. when altering one's mind becomes as easy as programming a computer, what does it mean to be human.
10 сентября 2011 г.
Сериализация - общие сведения о файлах
Именование файлов
Все файловые системы следуют одной и той же общей системе именования отдельных файлов: базовое имя файла ( MyFile ) и дополнительное расширение файла ( txt ), разделенные точкой. Базовое имя файла вместе с расширением файла называется именем файла: ( MyFile.txt ). Тем не менее, каждая файловая система (вроде NTFS, CDFS, ExFAT, UDF, FAT и FAT32) может иметь конкретные и иные правила формирования отдельных компонентов в пути к каталогу или файлу. Обратите внимание, что каталог (также называемый директорией ), предназначенный для упорядочивания файлов путём группировки, - это просто файл со специальным атрибутом, отмечающим его как каталог, но в остальном каталоги должны следовать всё тем же правилам именования, как и обычные файлы. Поскольку термин "каталог" просто ссылается на специальный тип файла, то некоторые справочные материалы используют общий термин "файл", чтобы охватить как понятия каталога, так и понятие файла данных как такового. Из-за этого, если не указано иное, любые имена и правила использования или примеры для файла применимы также и к каталогам. Каталог не следует путать с папкой. Папка - это более общее понятие. Каталог всегда физически представлен на диске, а папка может как быть каталогом, так и представлять виртуальное (логическое) размещение - к примеру, папка "Сетевое окружение" или "Мой компьютер". Каталог самого верхнего уровня на диске называется корневым. Корневой каталог всегда единственен, но у каждого диска он свой.
В каждом каталоге, кроме корневого, существуют псевдо-каталоги со специальными зарезервированными именами . (точка) и .. (две точки). Каталог . ссылается на этот же каталог, а .. - на предыдущий (родительский каталог, каталог верхнего уровня).
Термин "путь" ссылается на один или несколько каталогов (или папок), разделённых обратной косой чертой (\ - обратный слэш, бэкслэш, back-slash), и, возможно, на имя тома ( C: ) или имя сервера ( \\server , \\?\UNC\server или \\?\C: ). Примечание: в некоторых дальневосточных версиях Windows для разделителя пути используется иной символ, но надо понимать, что это ровно тот же символ (с тем же ANSI-кодом), просто он выглядит иначе.
- LFS (Local File System) - имена в локальной файловой системе, например: C:\MyFolder\MyFile.txt
- UNC (Uniform Naming Convention) - сетевые UNC-имена, например: \\server\MyFolder\MyFile.txt
- Long UNC или UNCW - длинные имена, например: \\?\UNC\server\MyFolder\MyFile.txt или \\?\C:\MyFolder\MyFile.txt
Один из каталогов на диске является активным для работающей программы. Он называется текущим каталогом. Текущий каталог всегда один, он задаётся при запуске программы и может меняться в процессе её работы (путём вызова функции смены каталога). Текущий каталог является активным, рабочим - он используется при разрешении имён (см. ниже). Кроме текущего каталога программы система также отдельно отслеживает текущий каталог каждого диска. Диск, указанный в текущем каталоге, называется текущим диском.
Путь, начинающийся с имени тома ( C:\MyFolder\MyFile.txt ), имени сервера ( \\server\MyFolder\MyFile.txt ) или корневого каталога ( \MyFolder\MyFile.txt ) называется абсолютным - потому что такое имя всегда однозначно указывает на один и тот же файл, вне зависимости от внешнего окружения. В противном случае путь называется относительным (вроде MyFile.txt , .\MyFile.txt , .\MyFolder\MyFile.txt или ..\..\MyFolder\MyFile.txt ). Относительные пути трактуются в зависимости от текущего каталога. Поэтому один и тот же относительный путь может ссылаться на разные файлы. К примеру, путь MyFile.txt и .\MyFile.txt ссылаются на C:\MyFolder\MyFile.txt , если текущий каталог (или каталог, относительно которого происходит разрешение имени) равен C:\MyFolder\ , но эти же имена будут ссылаться на D:\Program Files\MyFolder\MyFile.txt , если текущий каталог - D:\Program Files\MyFolder\ . Не следует путать полное имя файла с абсолютным. Это немного разные понятия, хотя часто их рассматривают как синонимы. Под полным именем файла понимается имя файла с путём - имя, по которому можно найти файл. Но оно не обязано быть абсолютным. С другой стороны, любое абсолютное имя всегда является полным именем. В английском языке используется термин "fully-qualified path" ("полностью указанный путь") - это синоним абсолютного пути файла.
Ограничения на количество символов также могут быть различны и меняться в зависимости от файловой системы и способа именования файла. Это осложняется ещё и поддержкой обратной совместимости. Например, старые файловые системы MS-DOS поддерживают максимум 8 символов для базового имени файла и 3 символа для расширения - в общей сложности 12 символов, включая точку-сепаратор. Кроме того, эти имена не могли включать в себя многие символы - к примеру, пробел. Этот формат имени файла широко известен как "формат файла 8.3" или короткое имя файла. Файловые системы Windows не имеют подобного ограничения, и хотя они поддерживают имена формата 8.3 для обратной совместимости, в основном они работают с длинными именами файлов.
Соглашения по именованию
- Используйте точку для отделения базового имени файла от расширения в имени файла или каталога. Каталоги могут иметь расширение, хотя обычно оно не используется.
- Используйте обратную косую черту (\) для разделения компонентов пути. Обратная косая черта разделяет имя файла от пути к нему, и имя одного каталога от другого каталога в пути. Вы не можете использовать обратную косую черту как часть имени реального файла или каталога, потому что это зарезервированный символ, который делит полное имя файла на компоненты.
- Используйте обратную косую черту в соответствии с требованиями как часть имени тома, например, C:\ в C:\path\file или \\server\share в \\server\share\path\file .
- Имена файлов не чувствительны к регистру. Например, имена OSCAR , Oscar и oscar ссылаются на один и тот же файл. Примечание: в целях совместимости с POSIX стандартом вы можете включить чувствительность к регистру для файловых имён, но это нестандартное поведение и оно не рекомендуется к использованию в общих сценариях.
- Имена томов (буквы дисков) также не чувствительны к регистру. Например, D: и d: относятся к одному и тому же тому.
- Вы можете использовать любой символ для имени файла, включая Unicode символы, за исключением следующих специальных символов:
- < (меньше)
- > (больше)
- : (двоеточие)
- " (двойные кавычки)
- / (косая черта, слэш)
- \ (обратная косая черта, обратный слэш)
- | (вертикальная черта, труба)
- ? (знак вопроса)
- * (звёздочка)
- Ноль (NUL-символ)
- Символы, чьи коды лежат в диапазоне от 1 до 31 (за исключением альтернативных потоков данных, где эти символы допускаются)
- Любые другие символы, который не поддерживает нижележащая файловая система
Путь к указанному файлу состоит из одного или нескольких компонентов, разделенных специальным символом (обратный слэш), при этом каждый компонент обычно является именем каталога или именем файла, но с некоторыми исключениями, обсуждаемыми ниже. Очень часто решающее значение для интерпретации пути в системе имеет начало пути - так называемый префикс пути. Этот префикс определяет пространство имён для использования с этим путём, и, кроме того, какие специальные символы могут использоваться в пути - включая последний символ.
Если какой-то компонент пути является именем файла, то он должен быть последним компонентом в пути.
Каждый компонент пути также имеет ограничение на максимальную длину имени, зависящее от конкретной файловой системы. Чаще всего, эти ограничения сводятся к двум основным группам: короткие и длинные имена файлов. Обратите внимание, что имена каталогов хранятся в файловой системе как особый тип файлов, так что правила именования файлов распространяются также на названия каталогов. Подводя итог: путь - это просто строковое представление иерархии между всеми каталогами, которые существуют для определённого файла или каталога.
Абсолютные и относительные пути
- UNC-имя любого формата, которое всегда начинается с двух бэк-слешей ( \\ ).
- Обозначение диска с бэк-слешем, например: C:\ или D:\ .
- Один обратный бэк-слеш, представляющий корневой каталог - например, \folder или \file.txt .
- C:tmp.txt ссылается на файл с именем tmp.txt в текущем каталоге на диске С.
- C:Temp\tmp.txt ссылается на файл tmp.txt в подпапке Temp текущего каталога диска С.
- ..\tmp.txt указывает на файл с именем tmp.txt , расположенный в родительском каталоге текущего каталога.
- ..\..\tmp.txt указывает на файл, находящийся на два каталога выше текущего каталога.
- ..\Temp\tmp.txt указывает на файл с именем tmp.txt , находящийся в каталоге Temp , который в свою очередь находится в родительском каталоге текущего каталога.
- C. \Temp\tmp.txt указывает на файл с именем tmp.txt , находящийся в каталоге Temp , который в свою очередь находится в родительском каталоге текущего каталога диска C.
- C:\Temp\..\Temp\tmp.txt и C:\Temp\.\tmp.txt - эти два пути ссылаются на файл C:\Temp\tmp.txt . Хотя никто не будет задавать путь в таком виде, но подобные пути могут получаться после склейки полного пути из нескольких компонентов из разных источников. Хотя путь такого вида является абсолютным (не относительным) в смысле исходного определения, иногда его всё же называют относительным, подчёркивая наличие компонента .. в пути.
Максимальное ограничение длины пути
В Windows максимальная длина пути равна MAX_PATH символов, где MAX_PATH определена как константа, равная 260 - за некоторыми исключениями, обсуждаемыми ниже. Локальный путь состоит из следующей последовательности: буква диска, двоеточие, бэк-слеш, компоненты имени, разделённые бэк-слешами. Например, максимальный путь на диске D имеет вид D:\какие-то-256-символов-пути (и ещё один символ, до 260, занимает терминирующий ноль).
В Windows также имеются функции, которые позволяют использовать расширенные пути файлов. Для таких путей ограничение на максимальную длину имени равно 32'767 символов. А каждый компонент в пути ограничен значением, зависящим от файловой системы - как правило, 255 символов. Подобные пути задаются (и трактуются) специальным образом. Для задания такого пути нужно использовать префикс \\?\ , например: \\?\D:\очень-длинный-путь или \\?\UNC\server\очень-длинный-путь .
Подобные имена можно использовать только в Unicode-функциях Windows. К ним (именам) следует относиться с осторожностью по двум причинам. Во-первых, обычные программы не смогут получить доступ к файлам и каталогам, имена которых превысят типичное ограничение в MAX_PATH . Во-вторых, UNCW-имена передаются нижележащей файловой системе "как есть", минуя обычный слой нормализации путей. К примеру, / не будет заменён на \, имена .. (две точки) и . (одна точка) не будут являться специальными и не будут разворачиваться в реальные имена каталогов. Вот почему и появляется возможность задавать имена более 260 символов в пути (а также имена с именами, иначе считающимися недопустимыми - скажем, с точкой на конце) - потому что имена передаются файловой системе без обработки, так что слой нормализации не накладывает ограничение в 260 символов (и другие правила файловых имён Windows).
Пространства имён
Префикс имени файла определяет пространство имён, к которому принадлежит путь. Существуют две основные категории пространств имён, используемые в Windows API: пространства имён NT и пространств имён Win32. Пространство имён NT было разработано как пространство имён низкого уровня, корневым пространством имён, поверх которого могли бы существовать другие пространства имён - включая подсистему Win32 и, как следствие, пространство имён Win32. POSIX является еще одним примером подсистемы в Windows, которая построена поверх NT.
Для исследования пространства имён вы можете использовать утилиту WinObj от SysInternals.
Файловые пространства имён Win32
К ним относятся имена, начинающиеся с \\?\ - мы уже разобрали их выше.
Префиксы вида C:\ являются псевдонимами.
Пространства имён устройств Win32
Для доступа к устройствам вместо физических файлов используется пространство имён устройств. Для указания пути при этом используется префикс \\.\ (два бэк-слеша, точка, бэк-слеш). К примеру, так вы можете получить доступ к диску как физическому устройству, без обращения к файловой системе. Но, конечно же, "устройства" не ограничиваются только дисками.
К примеру, если вы хотите открыть порт последовательной связи номер 1, то вы можете использовать имя COM1 в вызове функции CreateFile . Это работает, потому что COM1-COM9 являются частью зарезервированных имён в пространстве имён NT. Это работает как псевдоним на устройство, хотя вы можете и явно указывать префикс \\.\ . Для сравнения: если вдруг у вас есть сто COM-портов и вам надо обратиться к 56-му COM-порту, то вы не сможете открыть его по имени COM56 - потому что для него нет никакого предопределённого псевдонима или резервирования. Вам нужно будет открыть его по имени \\.\COM56 .
Пространства имён NT
Существуют также API функции, которые позволяют использовать именование в стиле NT, но в большинстве случаев это не нужно. Для наиболее востребованных объектов создаются ссылки (псевдонимы), чтобы к ним можно было получить доступ, используя обычные функции. К примеру, к пространству имён NT относятся такие вещи как Serial0 и Serial1 , HarddiskVolume1 и Harddisk0 , но обычно с ними работают через пространство имён Win32, используя такие имена как C: и \\.\PhysicalDrive0 .
Как уже было сказано, другие пространства имён реализуются поверх пространства имён NT. К примеру, для реестра в корне создаётся элемент REGISTRY , объекты ядра находятся в KernelObjects , про устройства и файлы Win32 я уже говорил, тут же находятся и сессии и, скажем, глобальные и локальные имена объектов IPC и так далее.
Напоминаю, что вы можете использовать утилиту WinObj для просмотра пространств имён.
На этом я заканчиваю рассказ про файлы и перехожу к собственно сериализации данных.
. when altering one's mind becomes as easy as programming a computer, what does it mean to be human.
15 января 2015 г.
Не используйте относительные имена файлов
Доступ к файлу можно получить по абсолютному или относительному имени (пути).
Абсолютное ("полностью квалифицированное") имя начинается с имени диска или сервера и указывает все компоненты пути, например: " C:\Projects\TestProject\Data.txt " или " \\SERVER\Projects\TestProject\Data.txt ". Такое имя всегда однозначно указывает на файл - вне зависимости от любых внешних факторов.
Относительное имя содержит не все компоненты пути и указывает файл относительно другого каталога, имя которого в самом имени не указано, например: " Data.txt " или " ..\Data.txt ". Для определения точного положения файла недостаточно одного относительного имени, необходимо ещё имя каталога, относительно которого будет трактоваться это имя. Поэтому один и тот же относительный путь может ссылаться на разные файлы. К примеру, путь " Data.txt " ссылается на C:\Projects\TestProject\Data.txt , если текущий каталог (или каталог, относительно которого происходит разрешение имени) равен C:\Projects\TestProject , но этот же путь будет ссылаться на C:\Windows\Data.txt , если текущий каталог - C:\Windows .
Подробнее о файловых именах можно почитать здесь.
Здесь же, в этой статье, я хочу показать, что вам никогда не нужно использовать относительные имена файлов.
Очень часто начинающие программисты используют относительные пути к файлам для работы с файлами внутри папки своей программы, например:
Что не так с этим кодом?Начинающий программист считает, что имя " input.txt " будет вычисляться относительно пути к его программе. Иными словами, если программа (.exe-файл) лежит в " C:\Projects\TestProject ", то, указав " input.txt " в Assign , мы откроем файл " C:\Projects\TestProject\input.txt ". Это попросту неверно!
- Для начала, текущий каталог при старте вашей программы задаётся не вами, а вызывающим вас процессом. Все функции запуска программы имеют строковый параметр для передачи туда имени каталога, который станет текущим для запускаемой программы. И вызывающая вас программа может передать туда всё, что угодно. Это может быть папка с вашей программой, да. Но это может быть и любая другая папка;
- Далее, к примеру, если ваша программа запускается через ярлык на рабочем столе или ярлык в меню Пуск / Программы, то текущий каталог для вашей программы указан в свойствах ярлыка. Например, сама Delphi запускается с текущим каталогом = папке с проектами (например, C:\Program Files\Borland\Delphi 7\Projects\ или даже просто C:\Projects\ ) - что, очевидно, не равно папке с программой ( C:\Program Files\Borland\Delphi 7\Bin\ );
- А если вы пишете программу, которая открывает файлы какого-то типа, то вы, вероятно, назначите свою программу для открытия таких файлов (ассоциируете тип файлов в вашей программой). Но когда пользователь дважды-щёлкнет по такому файлу, ваша программа запустится с текущим каталогом равным каталогу открываемого файла. Т.е. текущий каталог будет C:\Documents , а не C:\Projects\TestProject ;
- А если вы пишете код для службы (Win32 Service), то текущий каталог будет C:\Windows\System32 .
P.S. Кроме того, размещение файлов с данными/конфигурацией в папке с программой - крайне плохая идея, если только вы не пишете портативную (portable) программу (см. также).
Далее, есть же такие функции как GetCurrentDir и (что интереснее) SetCurrentDir . "Set" решительным образом намекает на то, что текущий каталог - вещь не фиксированная и его можно менять. Иными словами, текущий каталог не только может быть не равен каталогу программы, но и вообще может меняться в процессе выполнения программы! Действительно:
Конечно, тот факт, что текущий каталог можно менять, сам по себе ещё не означает проблему. Но посмотрите на такой код:
Что случилось? Дело в том, что внешний код (а именно - код диалога открытия файла) поменял текущий каталог. Ваш код оказался не готов к этому.P.S. Почему вообще диалог открытия файла меняет текущий каталог? Потому что вы (= прикладные программисты) пишете код с использованием относительных имён файлов.
Иными словами, проблема состоит в том, что кто угодно может менять текущий каталог в любой момент времени. Даже если в вашем коде нет вызовов другого внешнего кода, который меняет текущий каталог, всё равно текущий каталог может быть изменён другим потоком в вашей программе. Даже если вы сами не создаёте других потоков, потоки могут быть созданы DLL, которые загружены в вашу программу. Помимо системных DLL, это могут быть DLL от любых оконных ловушек, расширителей оболочек и даже антивирусов.
Вывод? Код, который адресует файл относительным именем работает благодаря случайности, а именно: благодаря тому, что никакой другой код не изменил текущий каталог перед тем, как вы вызвали функцию доступа к файлу, передав ей относительное имя файла.
Прежде чем мы посмотрим на правильное решение, давайте сделаем обзор того, чего делать не нужно.
Многие либо первым действием в программе, либо непосредственно перед выполнением участка кода с использованием относительных имён явно меняют текущий каталог на каталог программы. Например:
Или же сохраняют/восстанавливают текущий каталог перед вызовом кода, который потенциально может менять текущий каталог, например:
Что не так с этими решениями?Во-первых, текущий каталог, являясь глобальной переменной, может быть изменён другим потоком как раз между вызовами SetCurrentDir и Assign . Если этого не происходит, то ваш (некорректный) код работает благодаря случайности (случайность состоит в том, что этого не произошло).
Во-вторых, если вы форсированно устанавливаете свой текущий каталог, отбрасывая каталог, заданный вам вызывающим, вы можете открыть не тот файл! Например, пусть вы пишете программу-конвертер, пусть она конвертирует картинки из формата .jpg в формат .jpg, пусть вашу программу можно вызвать, передав ей имя файла в командной строке. Тогда пользователь может вызвать вас так:
(здесь C:\Documents> является приглашением командной строки, а "C:\Converter\convert.exe" "holidays.jpg" - непосредственно командной строкой).
Т.е. пользователь открыл консоль, он находится в папке C:\Documents и вызывает вас ( convert.exe ) из папки C:\Converter , передавая вам имя файла ( holidays.jpg ) параметром командной строки.
(Почти аналогичная ситуация будет если пользователь дважды-щёлкнет по файлу holidays.jpg в открытой папке C:\Documents в Проводнике - при условии, что ваша программа ассоциирована с .jpg файлами).
В этом случае ваша программа ( convert.exe ) запустится из папки C:\Converter , но текущим каталогом для неё будет C:\Documents . Если вы форсированно смените текущий каталог на папку с программой ( C:\Converter ), то не сможете открыть файл holidays.jpg , поскольку он находится в папке C:\Documents , а не C:\Converter .
В-третьих, если вы передаёте имена файлов между процессами, то текущий каталог в вашей программе никак не связан с текущим каталогом в программе, с которой вы общаетесь. Например, самый типичный случай: пусть вы пишете просмотрщик файлов, ваша программа ограничена одним экземпляром. Если вас запускают на просмотр файла, вы проверяете, не открыты ли вы уже, если да - то вы передаёте в первый экземпляр имя файла и выходите.
Имя файла вам могли передать в относительном формате (к примеру, вызвали вас из командной строки). Вы передали это имя "как есть" в первую копию своей программы. Однако текущий каталог первой копии равен неизвестно чему, он не равен текущему каталогу вашей второй копии. Как бы вы не меняли текущий каталог в первой копии программы перед попыткой доступа к файлу, который вам передали, вы никак не можете знать, каким же был текущий каталог в другой программе (вашей второй копии). И снова, ваш код, оперирующий относительными путями файлов, будет работать только благодаря случайности (в этом случае случайность состоит в том, что текущий каталог оказался одинаков в обоих экземплярах вашей программы).
Более того, когда вы меняете текущий каталог на какой-то - этот каталог открывается вашей программой. В частности, это означает, что вы не можете удалить этот каталог.
Зачем же вообще относительные пути, если они так плохи?
Ну, относительные пути нужны для человека-оператора. Они экономят время на набор текста. Действительно, вместо того, чтобы вводить длинный путь вида C:\Documents and Settings\Admin\Documents\Data from 2014\March.doc - вы можете ввести просто March.doc (конечно же, при условии, что вы "находитесь" в каталоге C:\Documents and Settings\Admin\Documents\Data from 2014\ ).
Из вышесказанного напрямую следует правильное решение. Раз относительные имена предназначены для человека, то вам (вашему коду) не следует их использовать. Всё, что вы можете сделать с относительным именем - перевести его в абсолютное. И оперировать в дальнейшем только абсолютным именем файла.
- Развернуть переменные окружения;
- Преобразовать относительное имя в абсолютное;
- Канонизировать путь, свернув '.', '..' и лишние разделители каталогов;
- Преобразовать короткий путь в длинный.
- Каждый раз, когда вы получаете имя файла из внешнего источника (командной строки, конфигурации, диалога и т.п.) - сохраняйте его в переменную с суффиксом Unsafe . Например, DocumentFileNameUnsafe := ParamStr(1) ;
- Не передавайте переменные с суффиксом Unsafe в функции открытия файлов;
- Не передавайте переменные с суффиксом Unsafe в другие программы (через IPC, командную строку и т.п.);
- Вы можете сохранять переменные с суффиксом Unsafe в файл конфигурации;
- Передайте переменную с суффиксом Unsafe в функцию нормализации и сохраните результат в переменной без суффикса Unsafe . Например, DocumentFileName := PathSearchAndQualify(DocumentFileNameUnsafe) ;
- Вы можете передавать переменные без суффикса Unsafe в функции открытия файлов и другие программы (IPC, командная строка и т.п.);
- Если вам точно известно имя файла (оно задано константой в коде) и вы знаете папку, в которой лежит файл (не обязательно константа, но хотя бы логическое размещение вида "каталог программы", "подкаталог ABC папки Application Data"), то получите путь к каталогу, затем добавьте к нему имя файла и сохраните результат в переменную без суффикса Unsafe . Если имя файла, заданное в константе, содержит '.' или '..' - выполните нормализацию перед сохранением в переменную;
- Измените текущий каталог на, скажем, C:\Windows\System32 (разумеется, путь надо задавать не константой, а получать через GetSystemDirectory ) сразу после того, как вы нормализовали все имена файлов из параметров командной строки;
- Если вы запускаете внешнюю программу, передавая ей имя файла для открытия, то задайте текущий каталог для запускаемой программы равным каталогу, содержащему открываемый файл (даже хотя вы передаёте полное имя файла);
- Используйте суффикс 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' .
- Старайтесь хранить имена каталогов с ведущим разделителем, а имена файлов - без разделителя. Например, 'C:\Windows\', но 'C:\Windows\notepad.exe';
- Если вы работаете в Unicode-версии Delphi (Delphi 2009 и выше) и хотите передать имя файла во внешний код (программу или DLL) - преобразуйте имя файла в короткое имя файла ( PathGetShortPath - см. ниже). Это увеличит шансы правильного открытия файла, если вызываемый код не поддерживает Unicode или неверно обрабатывает пробелы;
- Если вы передаёте имя файла по IPC - всегда предпочитайте Unicode-форму (используйте WideString ).
- Проведите нормализацию имён файлов, как указано в алгоритме выше, сохранив их в переменные без суффикса Unsafe ;
- Получите каталог, относительно которого вам нужно сохранять пути (каталог с программой, каталог с корневым файлом документа и т.п.). Нормализуйте его и сохраните в переменную без суффикса Unsafe ;
- Получите относительный путь для вашего пути файла из п1 относительно каталога из п2 с помощью функции PathGetRelativePath (см. ниже раздел практики). Сохраните результат в переменную с префиксом Unsafe ;
- Запишите переменную из п3 в вашу конфигурацию или документ.
Давайте посмотрим, как эти рекомендации нужно делать на примерах.
Реализация: голая Delphi
Во-первых, даже в Delphi "из коробки" есть несколько подходящих функций (некоторые функции могут отсутствовать в старых версиях Delphi):
Реализация: JCL
Во-вторых, хочу заметить, что если вы используете JCL (JEDI Code Library), то весь код у вас уже есть - в файле JclFileUtils : И хотя здесь нет PathGetAbsolutePath / ExpandFileName , но эта функция тривиальна:
Реализация: системные функции
В-третьих, я также предлагаю вам воспользоваться функциями системы. Заметьте, что хотя эти функции относятся к функциям Оболочки (Shell), они также относятся к т.н. группе "легковесных вспомогательных функций" (Shell Lightweight Utility Functions) и импортируются из ShlwAPI.dll , а не из ShellAPI.dll . В частности, это означает, что у них нет тяжёлых зависимостей и им не нужен COM - в отличие от высококоуровневых функций Оболочки.
И для этого я предлагаю создать отдельный модуль (File / New / Other / Unit) и сохранить его, скажем, с именем ShellFileSupport.pas . В этот модуль мы поместим весь вспомогательный код. Сложные функции мы будем импортировать из системы, а простые функции напишем сами. Вот какие функции мы реализуем:
Заметьте, что некоторые функции мы реализуем сами, поэтому они работают несколько иначе, чем системные. Кроме того, для некоторых функций мы добавляем дополнительный функционал. Всё это сделано для того, чтобы упростить использование функций. Дело в том, что эти функции несколько узко-специализированы. Например, системный PathQuoteSpaces не обрабатывает кавычки внутри строки, а PathCanonicalize не преобразует некорректные разделители каталогов. Поэтому в своих функциях мы дополнительно исправляем эти упущения.Взять готовый модуль можно здесь, а пример работы функций посмотреть здесь.
P.S. К сожалению, Delphi не поддерживает передачу кавычек в параметрах командной строки. В этом случае функции PathQuoteSpaces и PathProcessCommand используют семантику C runtime: кавычки внутри командной строки защищаются символом '\'. Но в любом случае такие параметры нельзя будет прочитать внутри Delphi-программы, если только вы не реализуете свой собственный разбор командной строки. Но зато их может прочитать другая программа, которая использует CommandLineToArgvW .
Реализации: вывод
Так или иначе, у вас есть богатый выбор: вы можете использовать встроенные функции, свои собственные полностью реализованные функции (взяв готовые из библиотеки JCL или написав свои) или же вы можете использовать системные функции, импортировав их из ShlwAPI.dll .
Вот как может выглядеть "функция нормализации", упомянутая выше, в разделе "Правильное решение":
А вот также упомянутая функция создания относительного пути:
Здесь ARootPath - каталог с программой, документом и т.п., относительно которого нужно создать путь. ATarget - имя файла, которое нужно сохранить в конфигурацию, документ и т.п. Result - собственно результат, который нужно сохранить. Например:Да, вам также понадобится функция, чтобы "увести" текущий каталог на безопасное место. Вот подходящий код:
Примеры кода
Открываем файл в известной папке
Неправильный код:
А вот правильный вариант этого кода: или: Где GetApplicationDataPath - ваша функция получения пути к папке Application Data (подробнее).Читаем имя файла из командной строки
Неправильный код:
А вот правильный вариант этого кода: Здесь OpenDocument - какая-то ваша функция открытия файла (например, загрузить .jpg файл и показать его на форме). Предполагается, что вы вызовете ProcessCommandLine при запуске программы, например:
(в примере выше ProcessCommandLine удобно сделать методом главной формы)На сегодняшний день подавляющее большинство компьютерных программ работают с файлами, а поэтому важно уметь пользоваться функциями или классами, которые позволяют открывать и закрывать, записывать и считывать информацию из файлов. В этой статье я немного расскажу о файлах и о классе QFile, а также покажу пример использования этого класса.
Полное имя файла, дерево файловой системы.
Существует такое понятие, как относительное имя файла. Относительное имя файла не содержит полного пути к нему. Его имя относительно к текущей рабочей директории, например из которой запущена программа, которая работает с файлами.
Если мы хотим обратиться к файлу /text.txt, находясь в директории /etc/, то необходимо писать ../text.txt
Если к файлу в текущей директории, то text.txt или ./text.txt
Обычно, когда говорят об имени файла, то подразумевают ту часть, где опущен полный путь к нему, т.е. просто file.txt. Путь к файлу и полное имя файл понятия взаимозаменяемые.
Более подробную информацию о файлах можно найти в сети.
Обратите внимание, что на сайте имеется очень похожая статья статья про реализацию чтения из файлов на C++, но без использования фреймворка Qt.
Класс QFile наследует класс QIODevice, который для работы с файлами предоставляет методы: открытия и закрытия файлов, для записи и чтения из файла, для создания и удаления файлов.
Чтобы создать объект для работы с файлом, нужно передать в конструктор имя файла.
Можно не передавать имя файла в конструктор, а установить его в объекте методом setName().
Часто при работе с файлами требуется узнать, открыт ли файл. Метод QIODevice::isOpen() возвращает значение true, если файл открыт и false в противном случае. А так как QFile унаследован от него, то мы можем проверить, открыт ли файл.
Для закрытия файла нужно вызвать метод QFile::close()
Обратите внимание, что данные сразу не записываются в файл на накопителе, они записываются в буфер в оперативной памяти. После закрытия файла данные из буфера записываются в файл на носителе. Это сделано для того, чтобы не нагружать жесткий диск или любой другой тип накопителя, на котором находится файл. Информацию из буфера в файл можно записать принудительно без закрытия файла, вызвав метод QFile::flush()
Существует очень полезный метод QFile::exists(). Он принимает на вход строку с именем файла и возвращает значение true, если такой файл существует. Существует статический и нестатический методы. Для работы со статическим методом необходимо указать имя файла.
Для работы с нестатическим достаточно просто его вызвать.
Для возможности записи или чтения необходимо открыть файл с указанием флага чтения QIODevice::ReadOnly или записи QIODevice::WriteOnly. Пример открытия файла для записи:
Есть разные способы чтения из фалов и записи. Можно считать или записать всю информацию за один раз, а можно по одному символу или блоками.
Для примера напишем программу, которая считывает из файла блок из первых 10-ти символов, а потом вставляет в другой файл.
Я создал файл filein.txt и внёс в него произвольный текст с помощью текстового редактора. После запуска программы я открыл filein.txt и fileout.txt в текстовом редакторе.
Первые 10 символов
Можно было считать все байты, тогда всё содержимое первого файла копировалось во второй. Для полного считывания строку
Нужно заменить на строку
В результате программа считает все байты в массив block, а после запишет их во второй файл.
Мы можем записывать информацию в файл строками, для этого его нужно открыть в текстовом режиме.
После передать адрес в конструктор нового объекста класса QTextStream.
А далее при помощи оператора << посылать строки в поток записи.
Содержимое fileout.txt после запуска программы
Содержимое файла fileout.txt
Запись в конец файла
Предыдущий метод полностью перезаписывал данные в файле, то есть очищал всё его содержимое и записывал новые данные. Перезаписи можно избежать и записывать новые данные в конец файла.
Флаг QIODevice::Append помещает указатель для записи (seek) в конец файла, в итоге входящий поток записывается сразу после имеющейся информации в файле. Пример фрагмента использования:
В примере вместо QIODevice::WriteOnly используется QIODevice::Append. Если сделать такое изменение в предыдущей программе, то после нескольких запусков в файле fileout.txt будет храниться строчка
Text, text, text.Text, text, text.Text, text, text.
Итак, мы рассмотрели основные методы для работы с файлами. Более подробную информацию обо всех методах класса QFile и QIODevice можно найти в официальной документации Qt и в сети.
Для вас это может быть интересно:
QFile и файлы. Чтение и запись строк в файл. : 4 комментария
Помогло разобраться с классом, хорошо изложено! 🙂
Не понимаю, где будут сохраняться эти файлы
Добавить комментарий Отменить ответ
Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.
Читайте также: