Как я могу получить поведение readlink -f GNU на Mac?
В Linux readlink
утилита принимает опцию -f
это следует за дополнительными ссылками. Это не работает на Mac и, возможно, на системах BSD. Каким будет эквивалент?
Вот некоторая отладочная информация:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
26 ответов
readlink -f
делает две вещи:
- Он повторяет последовательность символических ссылок, пока не найдет фактический файл.
- Он возвращает канонизированное имя этого файла, т. Е. Его абсолютный путь.
Если вы хотите, вы можете просто создать сценарий оболочки, который использует ванильное поведение 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 предоставляют пакет coreutils, содержащий greadlink
(GNU readlink). Благодарим Майкла Каллвейта в mackb.com.
brew install coreutils
greadlink -f file.txt
Вы можете быть заинтересованы в 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 этого скрипта должна быть короче.:)
Простой однострочный в 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)"
Ненавижу накапливать еще одну реализацию, но мне понадобились: а) портативная реализация с чистой оболочкой и б) охват модульным тестом, так как число граничных случаев для чего-то подобного нетривиально.
Смотрите мой проект на 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")
}
Вот так, более или менее. Достаточно простой для вставки в ваш скрипт, но достаточно сложный, чтобы вы с ума сошли, полагаясь на любой код, не имеющий модульных тестов для ваших сценариев использования.
- Установить доморощенный
- Запустите "brew install coreutils"
- Запустите "greadlink -f путь"
greadlink - это ссылка для чтения gnu, которая реализует -f. Вы можете использовать macports или другие, я предпочитаю доморощенный.
Я лично создал скрипт под названием realpath, который выглядит примерно так:
#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
Как насчет этого?
function readlink() {
DIR=$(echo "${1%/*}")
(cd "$DIR" && echo "$(pwd -P)")
}
Ленивый способ, который работает для меня,
$ brew install coreutils
$ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
$ which readlink
/usr/local/bin/readlink
/usr/bin/readlink
объяснение
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.
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
буду работать.
Самый простой способ решить эту проблему и включить функциональность readlink на Mac с установленным Homebrew или FreeBSD - это установить пакет 'coreutils'. Может также потребоваться в некоторых дистрибутивах Linux и других ОС POSIX.
Например, в FreeBSD 11 я установил, вызвав:
# pkg install coreutils
На MacOS с Homebrew команда будет такой:
$ brew install coreutils
Не совсем уверен, почему другие ответы такие сложные, вот и все, что нужно сделать. Файлы не находятся в другом месте, они просто еще не установлены.
Вот переносимая функция оболочки, которая должна работать в любой похожей оболочке Bourne. Это разрешит пунктуацию относительного пути ".. или." и разыменование символических ссылок.
Если по какой-либо причине у вас нет команды 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-пунктовой (стиль FreeBSD) лицензии. Копию лицензии можно найти, перейдя по указанной выше гиперссылке на мой сайт.
Поскольку моя работа используется людьми с не-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 "$@"
else
readlink "$@"
fi
}
function globalSed () {
# Use gsed if on macOS; otherwise use normal sed
if [[ $OSTYPE == darwin* ]]; then
gsed "$@"
else
sed "$@"
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.
Ответов уже много, но у меня не получилось ни одного... Так что я сейчас и пользуюсь.
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"
}
Начать обновление
Это настолько частая проблема, что мы собрали библиотеку 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-совместимый код завершения для целей тестирования.
POSIX совместимый readlink -f
реализация сценариев оболочки POSIX
https://github.com/ko1nksm/readlinkf
Это POSIX-совместимый (без зазрения совести). Он не использует ниreadlink
ни realpath
. Я подтвердил, что это точно так же, сравнив с GNUreadlink -f
(см. результаты тестирования). Имеет обработку ошибок и хорошую производительность. Можно смело заменить изreadlink -f
. Лицензия CC0, поэтому вы можете использовать ее для любого проекта.
Этот код принят в проекте bats-core.
# POSIX compliant version
readlinkf_posix() {
[ "${1:-}" ] || return 1
max_symlinks=40
CDPATH='' # to avoid changing to an unexpected directory
target=$1
[ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
[ -d "${target:-/}" ] && target="$target/"
cd -P . 2>/dev/null || return 1
while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do
if [ ! "$target" = "${target%/*}" ]; then
case $target in
/*) cd -P "${target%/*}/" 2>/dev/null || break ;;
*) cd -P "./${target%/*}" 2>/dev/null || break ;;
esac
target=${target##*/}
fi
if [ ! -L "$target" ]; then
target="${PWD%/}${target:+/}${target}"
printf '%s\n' "${target:-/}"
return 0
fi
# `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
# <file mode>, <number of links>, <owner name>, <group name>,
# <size>, <date and time>, <pathname of link>, <contents of link>
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
link=$(ls -dl -- "$target" 2>/dev/null) || break
target=${link#*" $target -> "}
done
return 1
}
См. Последний код. Это может быть исправлено.
Наверное, лучше поздно, чем никогда. Я был мотивирован, чтобы разработать это специально, потому что мои скрипты Fedora не работали на Mac. Проблема в зависимостях и Bash. У Mac их нет, или, если они есть, они часто находятся где-то еще (другой путь). Манипулирование путями зависимости в кроссплатформенном скрипте Bash в лучшем случае является головной болью, а в худшем - угрозой безопасности, поэтому лучше по возможности избегать их использования.
Функция get_realpath() ниже проста, ориентирована на Bash, и никаких зависимостей не требуется. Я использую только встроенные в Bash echo и cd. Это также довольно безопасно, так как все проверяется на каждом этапе пути и возвращает ошибки.
Если вы не хотите переходить по символическим ссылкам, поместите set -P в начало сценария, но в противном случае 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+, но его можно настроить для работы со старыми версиями, если они все еще существуют.
По-настоящему независимым от платформы будет и этот R-onliner
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}
На самом деле подражать readlink -f <path>
, $2 вместо $1 необходимо будет использовать.
Я написал утилиту 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
,
Я просто вставил следующее в начало своих сценариев bash:
#!/usr/bin/env bash -e
declare script=$(basename "$0")
declare dirname=$(dirname "$0")
declare scriptDir
if [[ $(uname) == 'Linux' ]];then
# use readlink -f
scriptDir=$(readlink -f "$dirname")
else
# can't use readlink -f, do a pwd -P in the script directory and then switch back
if [[ "$dirname" = '.' ]];then
# don't change directory, we are already inside
scriptDir=$(pwd -P)
else
# switch to the directory and then switch back
pwd=$(pwd)
cd "$dirname"
scriptDir=$(pwd -P)
cd "$pwd"
fi
fi
И удалили все экземпляры
readlink -f
.
$scriptDir
и
$script
тогда будет доступен для остальной части скрипта.
Хотя это не соответствует всем символическим ссылкам, он работает во всех системах и кажется достаточно хорошим для большинства случаев использования, он переключает каталог в содержащую папку, а затем выполняет
pwd -P
чтобы получить реальный путь к этому каталогу, а затем, наконец, вернуться к исходному.
Ответ от @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"
Пути к 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