Как я могу получить поведение GNU readlink -f на Mac?
В Linux утилита readlink
принимает параметр -f
, который следует за дополнительными ссылками. Это, похоже, не работает на Mac и, возможно, на BSD-системах. Каким будет эквивалент?
Вот некоторые отладочные данные:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
Ответы
Ответ 1
readlink -f
выполняет две вещи:
- Он выполняет итерацию по последовательности символических ссылок, пока не найдет фактический файл.
- Он возвращает этот файл canonicalized name — i.e., его абсолютный путь.
Если вы хотите, вы можете просто создать оболочку script, которая использует поведение readlink readlink для достижения того же. Вот пример. Очевидно, вы можете вставить это в свой собственный script, где вы хотите называть 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.
EDITED использовать pwd -P
вместо $PWD
.
Обратите внимание, что этот script ожидает, что он будет вызван как ./script_name filename
, no -f
, измените $1
на $2
, если вы хотите иметь возможность использовать с -f filename
как GNU readlink.
Ответ 2
MacPorts и Homebrew предоставляют пакет coreutils, содержащий greadlink
(GNU readlink). Благодарим Майкла Каллвейта в mackb.com.
brew install coreutils
greadlink -f file.txt
Ответ 3
Вам может быть интересно realpath(3)
или Python 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 этого script должна быть короче.:)
Ответ 4
Мне не нравится накладывать еще одну реализацию, но мне нужно: a) переносимую, чистую реализацию оболочки и b) охват единичного тестирования, так как число краев для чего-то подобного нетривиальны.
Посмотрите мой проект на Github для тестов и полного кода. Ниже приведен краткий обзор реализации:
Как указывает Кит Смит, readlink -f
выполняет две вещи: 1) рекурсивно решает символические ссылки и 2) канонизирует результат, следовательно:
realpath() {
canonicalize_path "$(resolve_symlinks "$1")"
}
Во-первых, реализация распознавателя symlink:
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")
}
Что это, более или менее. Достаточно просто вставить в свой script, но достаточно сложно, чтобы вы были сумасшедшими, чтобы полагаться на любой код, который не имеет модульных тестов для ваших случаев использования.
Ответ 5
- Установить доморощенный
- Запустить "brew установить coreutils"
- Запустить "greadlink -f path"
greadlink - это канал чтения gnu, который реализует -f. Вы можете использовать macports или другие, я предпочитаю homebrew.
Ответ 6
Простой однострочный в perl, который обязательно работает почти везде без каких-либо внешних зависимостей:
perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file
Разделяют символические ссылки.
Использование в script может быть таким:
readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
Ответ 7
Я лично создал скрипт под названием realpath, который выглядит примерно так:
#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
Ответ 8
Как насчет этого?
function readlink() {
DIR="${1%/*}"
(cd "$DIR" && echo "$(pwd -P)")
}
Ответ 9
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
показывает абсолютный путь, соответствующий файлу.
EDIT:
Если вы можете использовать Perl (Darwin/OS X устанавливается с последними версиями perl
), то:
perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt
будет работать.
Ответ 10
Вот портативная функция оболочки, которая должна работать в ANY Bourne сопоставимой оболочке.
Он разрешит относительную пунктуацию пути ".. или." и разыменовать символические ссылки.
Если по какой-то причине у вас нет команды realpath (1) или readlink (1), это может быть псевдонимом.
which realpath || alias realpath='real_path'
Enjoy:
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
EDIT:
Этот код лицензируется в соответствии с лицензией на 2-уровне (стиль FreeBSD).
Копию лицензии можно найти, следуя приведенной гиперссылке на мой сайт.
Ответ 11
Самый простой способ решить эту проблему и включить функциональность readlink на Mac w/Homebrew или FreeBSD - установить пакет "coreutils". Может также понадобиться для некоторых дистрибутивов Linux и других ОС POSIX.
Например, в FreeBSD 11 я установил, вызывая:
# pkg install coreutils
В MacOS с Homebrew команда будет следующей:
$ brew install coreutils
Не совсем уверен, почему другие ответы настолько сложны, что все есть. Файлы находятся не в другом месте, они еще не установлены.
Ответ 12
Начать обновление
Это такая частая проблема, что мы собрали библиотеку Bash 4 для бесплатного использования (лицензия MIT) под названием realpath-lib. Он предназначен для эмуляции readlink -f по умолчанию и включает в себя два набора тестов для проверки (1), что он работает для данной системы unix, и (2) для readlink -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 совместимый код выхода для тестирования.
Ответ 13
Уже есть много ответов, но никто не работал у меня... Так вот что я сейчас использую.
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"
}
Ответ 14
Лучше поздно, чем никогда, я полагаю. Я был мотивирован развивать это специально, потому что мои сценарии Fedora не работали на Mac. Проблема заключается в зависимостях и Bash. Маки их не имеют, или, если они это делают, они часто находятся где-то в другом месте (другой путь). Манипуляция зависимостями в кросс-платформенном Bash script - это головная боль в лучшем случае и риск для безопасности в худшем случае - поэтому лучше избегать их использования, если это возможно.
Функция get_realpath() ниже проста, Bash -центрическая, и никаких зависимостей не требуется. Я использую только Bash встроенные эхо и cd. Он также довольно безопасен, поскольку все тестируется на каждом этапе пути и возвращает условия ошибки.
Если вы не хотите следовать символическим ссылкам, тогда поставьте set -P в начале script, но в противном случае cd должен разрешить символические ссылки по умолчанию, Он был протестирован с аргументами файла, которые {absolute | относительный | символическая ссылка | 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 как realpath-lib (полное раскрытие информации - это наш продукт, но мы предлагаем его бесплатно сообществу без каких-либо ограничений). Он также может служить учебным инструментом - он хорошо документирован.
Мы старались применить так называемые "современные методы Bash", но Bash - большая тема, и я уверен, что всегда будет место для улучшения. Он требует Bash 4+, но может быть сделан для работы со старыми версиями, если они все еще существуют.
Ответ 15
По-настоящему независимым от платформы будет также этот R-onliner
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}
Чтобы реально имитировать readlink -f <path>
, нужно использовать $2 вместо $.
Ответ 16
Так как моя работа используется людьми с не-BSD Linux, а также с macOS, я решил использовать эти псевдонимы в наших скриптах сборки (включая 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 "[email protected]"
else
readlink "[email protected]"
fi
}
function globalSed () {
# Use gsed if on macOS; otherwise use normal sed
if [[ $OSTYPE == darwin* ]]; then
gsed "[email protected]"
else
sed "[email protected]"
fi
}
Необязательная проверка, которую вы можете добавить для автоматической установки зависимостей homebrew + coreutils:
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.
Ответ 17
Я написал утилиту realpath для OS X, которая может дать те же результаты, что и readlink -f
.
Вот пример:
([email protected] tmp)$ ls -l a
lrwxrwxrwx 1 jalcazar jalcazar 11 8月 25 19:29 a -> /etc/passwd
([email protected] tmp)$ realpath a
/etc/passwd
Если вы используете MacPorts, вы можете установить его с помощью следующей команды: sudo port selfupdate && sudo port install realpath
.
Ответ 18
объяснение
coreutils - это пакет brew
, который устанавливает основные утилиты GNU/Linux, соответствующие их реализации в Mac OSX, чтобы вы могли использовать их
Вы можете найти программы или утилиты в вашей системе Mac OSX, которые кажутся похожими на Linux coreutils ("Утилиты ядра"), но в некоторых случаях они различаются (например, имеют разные флаги).
Это потому, что реализация этих инструментов в Mac OSX отличается. Чтобы получить исходное поведение, подобное GNU/Linux, вы можете установить пакет coreutils
через систему управления пакетами brew
.
Это установит соответствующие основные утилиты с префиксом g
. Например, для readlink
вы найдете соответствующую программу greadlink
.
Чтобы заставить readlink
работать подобно реализации GNU readlink
(greadlink
), вы можете создать простой псевдоним после установки coreutils.
Реализация
- Установить варку
Следуйте инструкциям на https://brew.sh/
- Установите пакет coreutils
brew install coreutils
- Создать псевдоним
Вы можете поместить свой псевдоним в ~/.bashrc, ~/.bash_profile или в любое место, где вы храните псевдонимы bash. Я лично держу свои в ~/.bashrc
alias readlink=greadlink
Вы можете создать аналогичные псевдонимы для других coreutils, таких как gmv, gdu, gdf и так далее. Но имейте в виду, что поведение GNU на компьютере Mac может сбивать с толку других, привыкших работать с нативным coreutils, или может вести себя неожиданно в вашей системе Mac.
Ответ 19
Это то, что я использую:
stat -f %N $your_path
Ответ 20
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
Ответ 21
Ответ от @Keith Smith дает бесконечный цикл.
Вот мой ответ, который я использую только на SunOS (SunOS пропускает столько команд POSIX и GNU).
Это файл script, который вы должны поместить в один из ваших $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"
Ответ 22
Пути к readlink различаются между моей системой и вашей. Попробуйте указать полный путь:
/sw/sbin/readlink -f