Я создаю программу на C, которая должна читать различные файлы в GNU/Linux.
Представьте себе следующую файловую структуру.
root
|- dir1
| |- file1.txt
|
|- dir2
|- file2.txt
Теперь представьте, что я уже получил относительный путь к root/dir1/file1.txt
и прочитал следующее содержимое:
Load: ../dir2/file2.txt
...
Я могу сохранить эту строку ../dir2/file2.txt
в буфере C, но не могу использовать ее напрямую с чем-то вроде fopen(3)
, потому что она попытается открыть этот путь относительно каталога, в котором был запущен исполняемый файл.
Такое поведение также происходит при использовании C #include
. Путь указывается относительно источника, в котором он был указан, а не пути, по которому был запущен компилятор.
Мой вопрос: как мне открыть file2.txt
, зная (относительный) путь к file1.txt
и относительный путь, хранящийся в нем? Я думал о том, чтобы получить каталог file1.txt
(root/dir1/
) и объединить полученный оттуда относительный путь (root/dir1/../dir2/file2.txt
), но, возможно, есть лучший способ.
Я думаю, вам нужно будет узнать каталог, в котором находится file1.txt
, а затем очистить путь. Это можно сделать с помощью замены текста, которая может привести к путанице, если вокруг есть символические ссылки, или с помощью анализа с stat()
(и его родственниками), возможно, readdir()
(и его сопутствующими функциями), а может быть, readlink()
— остерегайтесь его интерфейса в стиле рококо! OTOH, я больше не уверен, что понимаю ваш вопрос.
Обратите внимание, что компилятор C имеет множество известных каталогов, которые он просматривает (и вы можете настроить этот список в командной строке), и когда он ищет заголовок (включенный файл), он ищет файл, добавляя то, что находится внутри. "../dirname/header.h"
или <../dirname/header.h>
в одну из известных локаций. В системах POSIX, таких как Linux, первым каталогом, в котором осуществляется поиск, является каталог, в котором находится текущий файл; если он там не найден, используется известный путь поиска. Однако я не уверен, какое отношение это имеет к вашей проблеме с содержимым file1.txt
.
Если вы знаете относительный путь ..\dir1\file1.txt
, можете ли вы просто изменить 1
на 2
? Или используйте другие манипуляции со строками, если это просто красивый пример вопроса.
@WeatherVane Я знаю это. Это был пример, иллюстрирующий мою проблему, поэтому это не решение. Также мой вопрос касается Linux, .\dir
— это путь для Windows.
Для этого не существует стандартной функции. Приложения, которым нужна эта функциональность, определяют свои собственные функции, которые анализируют имена путей.
@JonathanLeffler Вы сказали: «Первый искомый каталог — это каталог, в котором находится текущий файл», и это именно то, чего я хочу добиться. Компилятор C может быть вызван из root/
, но он вычисляет пути к заголовкам относительно источников, из которых они были включены, что может быть root/src/
. Это яснее?
Если вам удалось найти относительный путь к file1.txt
, не можете ли вы просто обобщить то, что вы сделали, в функцию, чтобы безболезненно получить относительный путь к another.txt
? Кроме того, я не понимаю, каким образом то, что делает компилятор или находит файлы, имеет отношение к обработке файлов во время выполнения (или платформы).
@WeatherVane Нет, все сложнее. В моем примере то, что я называю file1.txt
, было получено посредством пользовательского ввода, и это нормально. Путь к file2.txt
нужно получить так, как я объяснил. Компилятор был просто примером, потому что он иллюстрирует именно то, что я хочу сделать. Компиляторам приходится анализировать пути так же, как и мне. На самом деле моя настоящая проблема связана с проектом, который я делаю, похожим на компилятор.
Тогда, пожалуйста, опубликуйте пример, более соответствующий реальной ситуации. Что вы имеете против предложенного решения в последнем абзаце вопроса?
Я задал этот вопрос, потому что не знал, существует ли более стандартный способ получить этот путь, кроме манипуляций со строками. @Barmar ответил на это.
похоже, вы хотите, чтобы ваш текущий рабочий каталог был каталогом root/dir1/file1.txt
на время его оценки. Вы можете сделать это довольно легко с помощью chdir
и getcwd
.
Вы можете найти полезную информацию в ответах на Преобразование абсолютного пути в относительный путь к текущему каталогу с помощью Bash. Однако из-за упоминания Bash в большинстве ответов используются языки сценариев — в первую очередь Perl и Python — так что это может быть не так полезно, как все это. OTOH, есть указатели на код C для команды GNU realpath
.
как мне открыть
file2.txt
, зная (относительный) путь кfile1.txt
и относительный путь, хранящийся в нем? Я думал о том, чтобы получить каталогfile1.txt
(root/dir1/
) и объединить полученный оттуда относительный путь (root/dir1/../dir2/file2.txt
), но, возможно, есть лучший способ.
В C нет стандартных библиотечных функций для манипулирования путями. Однако POSIX определяет некоторые из них, поэтому, если вы можете предположить среду POSIX (и вы отметили Linux, что достаточно близко), то вы можете найти dirname() , basename() и realpath () могут быть вам полезны, хотя и не решают вашу проблему напрямую.
Описываемый вами метод должен отлично работать для правильно сформированных входных данных, в том числе, если известный путь к file1.txt
является абсолютным. Я не думаю, что есть более простой способ. Однако вы, вероятно, захотите убедиться, что путь к file2.txt
действительно является относительным, прежде чем применять этот подход. Вы также можете рассмотреть возможность проверки того, что компоненты ..
второго пути не пытаются выйти за пределы первого компонента первого пути.
Однако если вы можете положиться на POSIX, тогда другой альтернативой будет использование openat():
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
// ...
char *file1_copy = strdup(path_to_file1); // DO free() this
char *file1_dirname = dirname(file1_copy); // DO NOT free() this
// O_DIRECTORY and O_PATH are optional and Linux-specific
int dir1fd = open(file1_dirname, O_RDONLY | O_DIRECTORY | O_PATH);
// ... verify success ...
free(file1_copy);
int file2fd = openat(dir1fd, path_to_file2, O_RDONLY); // Possibly different flags
// ... verify success ...
close(dir1fd);
// And maybe you want that as a stream, in which case:
FILE *file2 = fdopen(file2fd, "r");
// ... verify success ...
Обратите внимание, что этот подход работает независимо от того, является ли заданный путь к file2.txt
относительным или абсолютным. Я не говорю, что это лучше в каком-то абсолютном смысле, но это может иметь преимущества для вас или в чем-то придется вам по вкусу.
Отличный ответ, именно тот подход, который я искал. Вы также упомянули несколько предостережений, которые я не учел, например, попытку выйти за пределы первого компонента первого пути. Небольшое замечание: вы ошиблись в вводе openfd
вместо fdopen
? Если кому-то еще интересно, вызов strdup
(который, кстати, не работает с -std=c99
) необходим, потому что dirname
изменяет входную строку.
@trxgnyp1, да, fdopen()
, а не openfd()
. Да, strdup()
— это POSIX, но не стандарт C. Однако, если вы используете dirname()
, вы уже находитесь на территории POSIX.
Обратите внимание, что strdup()
и strndup()
являются (будут) частью стандартной библиотеки C23 — наконец-то!
Джонатан, слово «компилятор» не было опечаткой. Я имею в виду, что компилятор C включает файлы (заголовки) относительно исходного пути, а не относительно пути, из которого была запущена команда компиляции. Это пример того, что я подразумеваю под «относительно другого файла».