Вопрос или проблема
Если ни утилита 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
.