Как я могу получить поведение GNU readlink -f на Mac?
Вопрос
В Linux, the readlink
утилита принимает опцию -f
это следует по дополнительным ссылкам.Похоже, это не работает на системах Mac и, возможно, на базе BSD.Каким был бы эквивалент?
Вот некоторая отладочная информация:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
Решение
readlink -f
делает две вещи:
- Он перебирает последовательность символических ссылок до тех пор, пока не найдет реальный файл.
- Он возвращает этот файл канонизированный name — то есть его абсолютный путь.
Если вы хотите, вы можете просто создать сценарий оболочки, который использует ванильное поведение readlink для достижения той же цели.Вот пример.Очевидно, что вы могли бы вставить это в свой собственный скрипт, где вы хотели бы вызвать readlink -f
#!/bin/sh
TARGET_FILE=$1
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
TARGET_FILE=`readlink $TARGET_FILE`
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
done
# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
PHYS_DIR=`pwd -P`
RESULT=$PHYS_DIR/$TARGET_FILE
echo $RESULT
Обратите внимание, что это не включает в себя какую-либо обработку ошибок.Что особенно важно, он не обнаруживает циклы символьных ссылок.Простой способ сделать это - подсчитать, сколько раз вы обходите цикл и терпите неудачу, если набираете невероятно большое число, например 1000.
ОТРЕДАКТИРОВАНО для использования pwd -P
вместо того , чтобы $PWD
.
Обратите внимание, что этот скрипт ожидает, что он будет вызван следующим образом ./script_name filename
, нет -f
, изменить $1
Для $2
если вы хотите иметь возможность использовать с -f filename
как GNU readlink.
Другие советы
MacPorts и Homebrew обеспечивают основные компоненты упаковка, содержащая greadlink
(Ссылка для чтения GNU).Заслуга Майкла Каллвейтта в публикации в mackb.com.
brew install coreutils
greadlink -f file.txt
Возможно, вас заинтересует realpath(3)
, или Python's os.path.realpath
.Эти два понятия не совсем одно и то же;вызов библиотеки C требует, чтобы существовали компоненты промежуточного пути, в то время как версия Python этого не делает.
$ pwd
/tmp/foo
$ ls -l
total 16
-rw-r--r-- 1 miles wheel 0 Jul 11 21:08 a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 b -> a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 c -> b
$ python -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' c
/private/tmp/foo/a
Я знаю, вы сказали, что предпочли бы что-то более легкое, чем другой язык сценариев, но на всякий случай, если компиляция двоичного файла невозможна, вы можете использовать Python и ctypes (доступны в Mac OS X 10.5) для завершения вызова библиотеки:
#!/usr/bin/python
import ctypes, sys
libc = ctypes.CDLL('libc.dylib')
libc.realpath.restype = ctypes.c_char_p
libc.__error.restype = ctypes.POINTER(ctypes.c_int)
libc.strerror.restype = ctypes.c_char_p
def realpath(path):
buffer = ctypes.create_string_buffer(1024) # PATH_MAX
if libc.realpath(path, buffer):
return buffer.value
else:
errno = libc.__error().contents.value
raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value))
if __name__ == '__main__':
print realpath(sys.argv[1])
По иронии судьбы, версия этого скрипта на C должна быть короче.:)
Я ненавижу нагромождать еще одну реализацию, но мне нужен был) переносимая реализация в виде чистой оболочки, и б) охват модульным тестированием, поскольку количество крайних случаев для чего-то подобного равно нетривиальный.
Видишь мой проект на Github для тестов и полного кода.Ниже приводится краткий обзор реализации:
Как проницательно отмечает Кит Смит, readlink -f
делает две вещи:1) рекурсивно разрешает символические ссылки и 2) канонизирует результат, следовательно:
realpath() {
canonicalize_path "$(resolve_symlinks "$1")"
}
Во-первых, реализация распознавателя символических ссылок:
resolve_symlinks() {
local dir_context path
path=$(readlink -- "$1")
if [ $? -eq 0 ]; then
dir_context=$(dirname -- "$1")
resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
else
printf '%s\n' "$1"
fi
}
_prepend_path_if_relative() {
case "$2" in
/* ) printf '%s\n' "$2" ;;
* ) printf '%s\n' "$1/$2" ;;
esac
}
Обратите внимание, что это немного упрощенная версия полное внедрение. Полная реализация добавляет небольшую проверку на циклы символических ссылок, а также немного массирует выходной сигнал.
Наконец, функция для канонизации пути:
canonicalize_path() {
if [ -d "$1" ]; then
_canonicalize_dir_path "$1"
else
_canonicalize_file_path "$1"
fi
}
_canonicalize_dir_path() {
(cd "$1" 2>/dev/null && pwd -P)
}
_canonicalize_file_path() {
local dir file
dir=$(dirname -- "$1")
file=$(basename -- "$1")
(cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}
Вот и все, более или менее.Достаточно простой для вставки в ваш скрипт, но достаточно сложный, чтобы вы были сумасшедшими, полагаясь на любой код, в котором нет модульных тестов для ваших вариантов использования.
- Установите homebrew
- Запустите "brew install coreutils".
- Запустите "greadlink -f path".
greadlink - это gnu readlink, который реализует -f.Вы также можете использовать macports или другие, я предпочитаю homebrew.
Простой однострочник на perl, который наверняка будет работать практически везде без каких-либо внешних зависимостей:
perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file
Будет разыменовывать символические ссылки.
Использование в скрипте может быть примерно таким:
readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
Я лично создал скрипт под названием realpath, который выглядит примерно так:
#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
А как насчет этого?
function readlink() {
DIR="${1%/*}"
(cd "$DIR" && echo "$(pwd -P)")
}
FreeBSD и Операционная система OSX у вас есть версия stat
производный от NetBSD.
Вы можете настроить вывод с помощью переключателей формата (смотрите страницы руководства по ссылкам выше).
% cd /service
% ls -tal
drwxr-xr-x 22 root wheel 27 Aug 25 10:41 ..
drwx------ 3 root wheel 8 Jun 30 13:59 .s6-svscan
drwxr-xr-x 3 root wheel 5 Jun 30 13:34 .
lrwxr-xr-x 1 root wheel 30 Dec 13 2013 clockspeed-adjust -> /var/service/clockspeed-adjust
lrwxr-xr-x 1 root wheel 29 Dec 13 2013 clockspeed-speed -> /var/service/clockspeed-speed
% stat -f%R clockspeed-adjust
/var/service/clockspeed-adjust
% stat -f%Y clockspeed-adjust
/var/service/clockspeed-adjust
Некоторые версии OS X для stat
может не хватать -f%R
опция для форматов.В данном случае -stat -f%Y
может быть достаточно.В -f%Y
опция покажет цель символической ссылки, тогда как -f%R
показывает абсолютный путь, соответствующий файлу.
Редактировать:
Если вы можете использовать Perl (Darwin / OS X поставляется с последними версиями perl
) затем:
perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt
будет работать.
Вот переносимая функция оболочки, которая должна работать в Любой Оболочка, сравнимая с Борном.Это устранит пунктуацию относительного пути "..или " . и разыменовывать символические ссылки.
Если по какой-то причине у вас нет команды realpath(1) или readlink(1), это может быть псевдоним.
which realpath || alias realpath='real_path'
Наслаждайтесь:
real_path () {
OIFS=$IFS
IFS='/'
for I in $1
do
# Resolve relative path punctuation.
if [ "$I" = "." ] || [ -z "$I" ]
then continue
elif [ "$I" = ".." ]
then FOO="${FOO%%/${FOO##*/}}"
continue
else FOO="${FOO}/${I}"
fi
## Resolve symbolic links
if [ -h "$FOO" ]
then
IFS=$OIFS
set `ls -l "$FOO"`
while shift ;
do
if [ "$1" = "->" ]
then FOO=$2
shift $#
break
fi
done
IFS='/'
fi
done
IFS=$OIFS
echo "$FOO"
}
кроме того, на всякий случай, если кому-то интересно, вот как реализовать basename и dirname в 100% чистом коде оболочки:
## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html
# the dir name excludes the least portion behind the last slash.
dir_name () {
echo "${1%/*}"
}
## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html
# the base name excludes the greatest portion in front of the last slash.
base_name () {
echo "${1##*/}"
}
Вы можете найти обновленную версию этого кода оболочки на моем сайте Google: http://sites.google.com/site/jdisnard/realpath
Редактировать:Этот код лицензируется на условиях лицензии 2-clause (в стиле FreeBSD).Копию лицензии можно найти, перейдя по приведенной выше гиперссылке на мой сайт.
Самый простой способ решить эту проблему и включить функциональность readlink на Mac с установленным Homebrew или FreeBSD - это установить пакет 'coreutils'.Также может потребоваться в некоторых дистрибутивах Linux и других ОС POSIX.
Например, во FreeBSD 11 я установил, вызвав:
# pkg install coreutils
В macOS с Homebrew команда будет:
$ brew install coreutils
Не совсем уверен, почему другие ответы такие сложные, вот и все, что нужно сделать.Файлы находятся не в другом месте, они просто еще не установлены.
Начать обновление
Это настолько частая проблема, что мы собрали библиотеку Bash 4 для бесплатного использования (лицензия MIT) под названием реальный путь-библиотека.Это предназначено для эмуляции ссылка для чтения -f по умолчанию и включает в себя два набора тестов для проверки (1) того, что это работает для данной системы unix и (2) против ссылка для чтения -f если установлен (но это не обязательно).Кроме того, его можно использовать для исследования, выявления и устранения глубоких, неработающих символических ссылок и циклических ссылок, поэтому он может быть полезным инструментом для диагностики глубоко вложенных проблем с физическими или символьными каталогами и файлами.Его можно найти по адресу github.com или bitbucket.org.
Завершение обновления
Другим очень компактным и эффективным решением, которое не зависит ни от чего, кроме Bash, является:
function get_realpath() {
[[ ! -f "$1" ]] && return 1 # failure : file does not exist.
[[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
return 0 # success
}
Это также включает в себя настройку среды no_symlinks
это обеспечивает возможность разрешать символические ссылки на физическую систему.До тех пор , пока no_symlinks
установлен на что-то, т.е. no_symlinks='on'
затем символические ссылки будут разрешены для физической системы.В противном случае они будут применены (настройка по умолчанию).
Это должно работать в любой системе, предоставляющей Bash, и вернет код выхода, совместимый с Bash, для целей тестирования.
Уже есть много ответов, но ни один из них не сработал для меня...Итак, это то, что я использую сейчас.
readlink_f() {
local target="$1"
[ -f "$target" ] || return 1 #no nofile
while [ -L "$target" ]; do
target="$(readlink "$target")"
done
echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}
Полагаю, лучше поздно, чем никогда.Я был мотивирован разработать это специально, потому что мои скрипты Fedora не работали на Mac.Проблема заключается в зависимостях и Bash.У компьютеров Mac их нет, а если и есть, то они часто находятся где-то в другом месте (другой путь).Манипулирование путями зависимостей в кроссплатформенном Bash-скрипте в лучшем случае является головной болью, а в худшем - угрозой безопасности, поэтому лучше по возможности избегать их использования.
Приведенная ниже функция get_realpath() проста, ориентирована на Bash и не требует никаких зависимостей.Я использую только встроенные в Bash файлы эхо и компакт - диск.Это также довольно безопасно, так как все тестируется на каждом этапе пути и возвращает условия ошибки.
Если вы не хотите переходить по символическим ссылкам, то поставьте набор - P в начале сценария, но в остальном компакт - диск должны разрешать символические ссылки по умолчанию.Он был протестирован с аргументами файла, которые являются {absolute | relative | symlink | local}, и он возвращает абсолютный путь к файлу.До сих пор у нас не было с этим никаких проблем.
function get_realpath() {
if [[ -f "$1" ]]
then
# file *must* exist
if cd "$(echo "${1%/*}")" &>/dev/null
then
# file *may* not be local
# exception is ./file.ext
# try 'cd .; cd -;' *works!*
local tmppwd="$PWD"
cd - &>/dev/null
else
# file *must* be local
local tmppwd="$PWD"
fi
else
# file *cannot* exist
return 1 # failure
fi
# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success
}
Вы можете объединить это с другими функциями get_dirname, get_filename, get_stemname и validate_path.Их можно найти в нашем репозитории GitHub следующим образом реальный путь-библиотека (полное раскрытие информации - это наш продукт, но мы предлагаем его сообществу бесплатно без каких-либо ограничений).Это также могло бы послужить учебным пособием - оно хорошо документировано.
Мы изо всех сил старались применять так называемые "современные методы Bash", но Bash - это большая тема, и я уверен, что всегда найдется место для совершенствования.Для этого требуется Bash 4 +, но его можно настроить для работы со старыми версиями, если они все еще существуют.
Действительно независимым от платформы был бы и этот R-онлайнер
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}
Чтобы на самом деле имитировать readlink -f <path>
, нужно было бы использовать 2 доллара вместо 1 доллара.
Поскольку моя работа используется людьми с Linux, а также macOS, отличными от BSD, я решил использовать эти псевдонимы в наших сценариях сборки (sed
включен, так как у него есть похожие проблемы):
##
# If you're running macOS, use homebrew to install greadlink/gsed first:
# brew install coreutils
#
# Example use:
# # Gets the directory of the currently running script
# dotfilesDir=$(dirname "$(globalReadlink -fm "$0")")
# alias al='pico ${dotfilesDir}/aliases.local'
##
function globalReadlink () {
# Use greadlink if on macOS; otherwise use normal readlink
if [[ $OSTYPE == darwin* ]]; then
greadlink "$@"
else
readlink "$@"
fi
}
function globalSed () {
# Use gsed if on macOS; otherwise use normal sed
if [[ $OSTYPE == darwin* ]]; then
gsed "$@"
else
sed "$@"
fi
}
Необязательный флажок, который вы могли бы добавить для автоматической установки домашнее пиво + основные компоненты зависимости:
if [[ "$OSTYPE" == "darwin"* ]]; then
# Install brew if needed
if [ -z "$(which brew)" ]; then
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)";
fi
# Check for coreutils
if [ -z "$(brew ls coreutils)" ]; then
brew install coreutils
fi
fi
Я полагаю, чтобы быть действительно "глобальным", ему нужно проверять других...но это, вероятно, близко к отметке 80/20.
Я написал утилита realpath для OS X который может обеспечить те же результаты, что и readlink -f
.
Вот один из примеров:
(jalcazar@mac tmp)$ ls -l a
lrwxrwxrwx 1 jalcazar jalcazar 11 8月 25 19:29 a -> /etc/passwd
(jalcazar@mac tmp)$ realpath a
/etc/passwd
Если вы используете MacPorts, вы можете установить его с помощью следующей команды: sudo port selfupdate && sudo port install realpath
.
Объяснение
coreutils - это brew
пакет, устанавливающий основные утилиты GNU / Linux, которые соответствуют их реализации в Mac OSX, чтобы вы могли использовать их
Вы можете найти программы или утилиты в вашей системе mac osx, которые кажутся похожими на Linux coreutils ("Основные утилиты"), но они отличаются некоторыми способами (например, имеют разные флаги).
Это связано с тем, что реализация этих инструментов в Mac OSX отличается.Чтобы получить исходное поведение, подобное GNU / Linux, вы можете установить coreutils
посылка через brew
система управления пакетами.
Это приведет к установке соответствующих основных утилит с префиксом g
.Например.для readlink
, вы найдете соответствующий greadlink
программа.
Для того, чтобы сделать readlink
выполнять подобно GNU readlink
(greadlink
) реализация, вы можете создать простой псевдоним после установки coreutils.
Реализация
- Установите brew
Следуйте инструкциям на https://brew.sh/
- Установите пакет coreutils
brew install coreutils
- Создайте псевдоним
Вы можете поместить свой псевдоним в ~/.bashrc, ~/.bash_profile или везде, где вы привыкли хранить свои псевдонимы bash.Я лично храню свой в ~/.bashrc
alias readlink=greadlink
Вы можете создать аналогичные псевдонимы для других базовых файлов, таких как gmv, gdu, gdf и так далее.Но имейте в виду, что поведение GNU на компьютере Mac может ввести в заблуждение других пользователей, привыкших работать с родными coreutils, или может вести себя неожиданным образом в вашей системе Mac.
Это то, что я использую:
stat -f %N $your_path
Пути к readlink различаются в моей системе и вашей.Пожалуйста, попробуйте указать полный путь:
/sw/sbin/readlink -f
Perl имеет функцию readlink (например, Как мне скопировать символические ссылки в Perl?).Это работает на большинстве платформ, включая OS X:
perl -e "print readlink '/path/to/link'"
Например:
$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c
Ответ от @Keith Smith дает бесконечный цикл.
Вот мой ответ, который я использую только в SunOS (SunOS пропускает так много команд POSIX и GNU).
Это файл сценария, который вы должны поместить в один из ваших каталогов $ PATH:
#!/bin/sh
! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99
link="$1"
while [ -L "$link" ]; do
lastLink="$link"
link=$(/bin/ls -ldq "$link")
link="${link##* -> }"
link=$(realpath "$link")
[ "$link" == "$lastlink" ] && echo -e "ERROR: link loop detected on $link" 1>&2 && break
done
echo "$link"