Как я могу получить путь к файлу относительно другого файла?

Я создаю программу на 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), но, возможно, есть лучший способ.

Джонатан, слово «компилятор» не было опечаткой. Я имею в виду, что компилятор C включает файлы (заголовки) относительно исходного пути, а не относительно пути, из которого была запущена команда компиляции. Это пример того, что я подразумеваю под «относительно другого файла».

trxgnyp1 04.09.2024 22:29

Я думаю, вам нужно будет узнать каталог, в котором находится file1.txt, а затем очистить путь. Это можно сделать с помощью замены текста, которая может привести к путанице, если вокруг есть символические ссылки, или с помощью анализа с stat() (и его родственниками), возможно, readdir() (и его сопутствующими функциями), а может быть, readlink() — остерегайтесь его интерфейса в стиле рококо! OTOH, я больше не уверен, что понимаю ваш вопрос.

Jonathan Leffler 04.09.2024 22:29

Обратите внимание, что компилятор C имеет множество известных каталогов, которые он просматривает (и вы можете настроить этот список в командной строке), и когда он ищет заголовок (включенный файл), он ищет файл, добавляя то, что находится внутри. "../dirname/header.h" или <../dirname/header.h> в одну из известных локаций. В системах POSIX, таких как Linux, первым каталогом, в котором осуществляется поиск, является каталог, в котором находится текущий файл; если он там не найден, используется известный путь поиска. Однако я не уверен, какое отношение это имеет к вашей проблеме с содержимым file1.txt.

Jonathan Leffler 04.09.2024 22:35

Если вы знаете относительный путь ..\dir1\file1.txt, можете ли вы просто изменить 1 на 2? Или используйте другие манипуляции со строками, если это просто красивый пример вопроса.

Weather Vane 04.09.2024 22:38

@WeatherVane Я знаю это. Это был пример, иллюстрирующий мою проблему, поэтому это не решение. Также мой вопрос касается Linux, .\dir — это путь для Windows.

trxgnyp1 04.09.2024 22:40

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

Barmar 04.09.2024 22:40

@JonathanLeffler Вы сказали: «Первый искомый каталог — это каталог, в котором находится текущий файл», и это именно то, чего я хочу добиться. Компилятор C может быть вызван из root/, но он вычисляет пути к заголовкам относительно источников, из которых они были включены, что может быть root/src/. Это яснее?

trxgnyp1 04.09.2024 22:41

Если вам удалось найти относительный путь к file1.txt, не можете ли вы просто обобщить то, что вы сделали, в функцию, чтобы безболезненно получить относительный путь к another.txt? Кроме того, я не понимаю, каким образом то, что делает компилятор или находит файлы, имеет отношение к обработке файлов во время выполнения (или платформы).

Weather Vane 04.09.2024 22:42

@WeatherVane Нет, все сложнее. В моем примере то, что я называю file1.txt, было получено посредством пользовательского ввода, и это нормально. Путь к file2.txt нужно получить так, как я объяснил. Компилятор был просто примером, потому что он иллюстрирует именно то, что я хочу сделать. Компиляторам приходится анализировать пути так же, как и мне. На самом деле моя настоящая проблема связана с проектом, который я делаю, похожим на компилятор.

trxgnyp1 04.09.2024 22:46

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

Weather Vane 04.09.2024 22:49

Я задал этот вопрос, потому что не знал, существует ли более стандартный способ получить этот путь, кроме манипуляций со строками. @Barmar ответил на это.

trxgnyp1 04.09.2024 22:55

похоже, вы хотите, чтобы ваш текущий рабочий каталог был каталогом root/dir1/file1.txt на время его оценки. Вы можете сделать это довольно легко с помощью chdir и getcwd.

erik258 04.09.2024 23:25

Вы можете найти полезную информацию в ответах на Преобразование абсолютного пути в относительный путь к текущему каталогу с помощью Bash. Однако из-за упоминания Bash в большинстве ответов используются языки сценариев — в первую очередь Perl и Python — так что это может быть не так полезно, как все это. OTOH, есть указатели на код C для команды GNU realpath.

Jonathan Leffler 04.09.2024 23:41
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
13
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

как мне открыть 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 05.09.2024 00:46

@trxgnyp1, да, fdopen(), а не openfd(). Да, strdup() — это POSIX, но не стандарт C. Однако, если вы используете dirname(), вы уже находитесь на территории POSIX.

John Bollinger 05.09.2024 15:22

Обратите внимание, что strdup() и strndup() являются (будут) частью стандартной библиотеки C23 — наконец-то!

Jonathan Leffler 05.09.2024 16:18

Другие вопросы по теме