Как мне разрешить относительный путь в оболочке POSIX, если readlink/realpath недоступны?

Вопрос или проблема

Если ни утилита readlink, ни realpath недоступны (думаю, что в основном сегодня это от GNU coreutils?), что я могу использовать в сценариях POSIX shell для разрешения относительного пути?

Однако существует realpath C функция. Так что, возможно, одна из других утилит использует её тайно?

Однако существует realpath C функция. Так что, возможно, одна из других утилит использует её тайно?

На вскидку, я не знаю, чтобы какая-либо из этих утилит ссылалась на функцию realpath в их спецификации.

Что я знаю, так это то, что test для каталогов часто возвращает истину, когда путь разрешается в существующий каталог ¹ + x.

Заглядывая в реализацию в dash(git.kernel.org), она использует lstat64/stat64.

Это может быть достаточным для каталогов (test -d pathname), чтобы cd в них и вернуть разрешенный путь с помощью pwd.

Для cd CDPATH должен быть не установлен или пуст², и операнд не должен быть дефисом “-“

([ -d "$1" ] && [ '-' != "$1" ] && CDPATH='' cd -P -- "$1" && pwd -P)

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

Если это проблема исключить дефис (“-“) как путь, альтернативой является установка операнда в “./-” для этого конкретного случая.

¹ см. test;
² см. описание шага 5 cd,
³ см. операнд cd “-“


Как я мог прочитать, вы уже знакомы с dirname и basename, возможно, также о том, как проверить, сломан ли [а] символическая ссылка [символическая ссылка] или не существует Unix&Linux, а если нет, посмотрите там, если это интересно в контексте использования test этого ответа, например, чтобы различать статус выхода test -d pathname 1.

С тех пор, как я опубликовал вопрос, я придумал следующее решение:

#!/bin/sh
realpath() (
    file=
    path=$1
    [ -d "$path" ] || {
        file=/$(basename -- "$path")
        path=$(dirname -- "$path")
    }
    {
        path=$(cd -- "$path" && pwd)$file
    } || exit $?
    printf %s\\n "/${path#"${path%%[!/]*}"}"
)

Предупреждение: Очевидно, я до сих пор не смог это много протестировать! Всегда жду обратной связи. Если вы заметите что-то, пожалуйста, оставьте комментарий.

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

Вышеуказанная функция оболочки realpath является заменой для GNU coreutils realpath -s --, смотрите https://www.gnu.org/software/coreutils/manual/html_node/realpath-invocation.html. Это означает, что разрешение символических ссылок не выполняется. (Что также не обязательно, если нас интересует только разрешение относительного пути.)

  • #!/bin/sh
    

    Используйте шебанг для POSIX shell.

  • realpath() (
    

    Запустите функцию в подсистеме, т.е. realpath() (...) вместо realpath() {...}, чтобы мы не перезаписывали возможно существующие переменные file и path. (POSIX shell не поддерживает локальные переменные.)

  • file=
    path=$1
    

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

  • [ -d "$path" ] || {
        file=/$(basename -- "$path")
        path=$(dirname -- "$path")
    }
    

    В случае, если аргумент функции не был путем к существующему каталогу, как предполагалось ранее, мы должны скорректировать $path. Чтобы процитировать из man realpath:

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

    Поэтому не очень важно, существует ли $file (последний компонент) как существующий файл или несуществующий файл или папка – мы просто уберем его из $path на данный момент (и добавим его снова в конечном итоге).

  • {
        path=$(cd -- "$path" && pwd)$file
    

    Здесь происходит “трюк”: чтобы разрешить любые относительные пути, мы просто позволяем cd разрешать их за нас и используем pwd, чтобы напечатать каталог, в котором мы оказались. Этот новый путь сохраняется в $path. Кроме того, нам не нужно беспокоиться о случайном изменении нашего текущего рабочего каталога, потому что 2.6.3 Замена команд выполняется в подсистеме. $file добавляется в конце.

  • } || exit $?
    

    Эта строка является причиной блока кода {...} вокруг присвоения переменной из предыдущей строки. Если пользователь предоставил неверный путь (например, больше, чем просто последний компонент не существует), cd (изнутри замены команды) должен завершиться с ошибкой и напечатать сообщение об ошибке в stderr (например, “не могу перейти в …”) и выйти с кодом ошибки > 0. Это также будет кодом ошибки для подсистемы замены команды. Для доступа к этому коду ошибки как $? присвоение переменной оборачивается в {...}. Таким образом, в случае, если блок {...} завершится с ошибкой, мы выходим из функции и передаем тот же ненулевой код ошибки.

  • printf %s\\n "/${path#"${path%%[!/]*}"}"
    

    Из-за крайних случаев, таких как // или /a, где a не существует, мы могли бы получить результат // или //a. Поэтому 2.6.2 Расширение параметров используется для удаления всех ведущих слэшей из $path, перед добавлением одного слэша для окончательного вывода.

Вот некоторые тесты, которые я провел:

for path in / // /// . .. "$HOME/." "$HOME/.." "*" --help /tmp /notexisting
do
    if [ "$(realpath "$path")" != "$(/usr/bin/realpath -s -- "$path")" ]
    then
        printf %s\\n "$path"
    fi
done

Ответ или решение

Для разрешения относительных путей в POSIX-среде, где отсутствуют утилиты readlink или realpath, вы можете использовать встроенные команды оболочки, такие как cd и pwd. Основной подход заключается в том, чтобы использовать команду cd для перехода к директории, а затем запрашивать абсолютный путь с помощью pwd.

Ниже приведен пример функции на sh, которая выполняет эту задачу:

#!/bin/sh

realpath() {
    file=
    path=$1

    # Проверка, является ли путь директорией
    [ -d "$path" ] || {
        file=/"$(basename -- "$path")"
        path="$(dirname -- "$path")"
    }

    # Переход к директории и получение абсолютного пути
    {
        path="$(cd -- "$path" && pwd)${file}"
    } || exit $?

    # Удаление лишних слешей в начале
    printf %s\\n "/${path#"${path%%[!/]*}"}"
}

Объяснение кода функции realpath

  • Подготовка переменных:

    • В file изначально ничего не сохраняем.
    • В path сохраняем аргумент, переданный функции, предполагая, что это директория.
  • Проверка наличия директории:

    • Если переданный путь не является директорией ([ -d "$path" ]), мы разбиваем путь на директорию и последний компонент, чтобы работать с ними отдельно.
  • Использование cd и pwd:

    • Здесь происходит попытка перейти в каталоге (cd) и получить его абсолютный путь с помощью pwd. Эта часть выполняется в блоке, который, если провален, завершит выполнение функции с кодом ошибки.
  • Форматирование результата:

    • Для устранения возможных лишних слешей перед вызовом printf, мы используем подстановку параметров.

Пример использования функции

Вы можете протестировать функцию, вызвав ее с различными относительными путями:

for path in / // /// . .. "$HOME/." "$HOME/.." "*" --help /tmp /notexisting; do
    if [ "$(realpath "$path")" != "$(realpath -s -- "$path")" ]; then
        printf %s\\n "$path"
    fi
done

Замечания и рекомендации

  • Этот метод хорошо работает для относительных путей, но не обрабатывает символические ссылки. Если вам необходимо учитывать символические ссылки, потребуется более сложная логика.
  • Всегда будьте осторожны, используя cd в скриптах, так как перемещение в другую директорию может повлиять на последующие команды. В данном случае, так как мы используем командную замену, текущее рабочее пространство оболочки при этом не изменится.
  • Рекомендуется обработать возможные ошибки и исключения, которые могут возникнуть, например, при отсутствии прав доступа к директориям.

Следуя вышеуказанным рекомендациям и используя предложенный код, вы сможете успешно разрешать относительные пути в POSIX-совместимых оболочках без необходимости в readlink или realpath.

Оцените материал
Добавить комментарий

Капча загружается...