2009-06-28 2 views
263

В Linux утилита readlink принимает опцию -f, которая следует за дополнительными ссылками. Это, похоже, не работает на Mac и, возможно, на BSD-системах. Каким будет эквивалент?Как я могу получить поведение readlink -f GNU на Mac?

Вот некоторая информация отладки:

$ which readlink; readlink -f 
/usr/bin/readlink 
readlink: illegal option -f 
usage: readlink [-n] [file ...] 
+0

Немного поздно, но в вашем вопросе отсутствует упоминание оболочки, которую вы используете. Это актуально, поскольку 'readlink' может быть встроенной или внешней командой. – 0xC0000022L

+0

Это могло бы объяснить разницу. Я уверен, что я использовал bash в обоих случаях. – troelskn

+0

Почему эта опция незаконна на компьютерах Mac? – CommaToast

ответ

124

readlink -f делает две вещи:

  1. он перебирает по последовательности симлинок, пока он не найдет фактический файл.
  2. Он возвращает этот файл canonicalized имя — т. Е. Его абсолютный путь.

Если вы хотите, вы можете просто создать сценарий оболочки, который использует поведение readlink 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.

EDITED использует pwd -P вместо $PWD.

Обратите внимание, что этот сценарий ожидает называться как ./script_name filename, не -f, изменить $1 к $2, если вы хотите, чтобы иметь возможность использовать с -f filename как GNU readlink.

+1

Насколько я могу судить, это не сработает, если родительский каталог пути является символической ссылкой. Например. if 'foo ->/var/cux', тогда' foo/bar' не будет разрешен, потому что 'bar' не является ссылкой, хотя' foo'. – troelskn

+1

Ах. Да.Это не так просто, но вы можете обновить вышеупомянутый скрипт, чтобы справиться с этим. Я отредактирую (переписываю, действительно) ответ соответственно. –

+0

Ну, ссылка может быть в любом месте пути. Я предполагаю, что скрипт может перебирать каждую часть пути, но потом становится немного сложнее. – troelskn

-2

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

/sw/sbin/readlink -f

+1

И что - точно - разница между/sw/sbin и/usr/bin? И почему эти два бинарника отличаются? – troelskn

+4

http://www.finkproject.org/doc/packaging/fslayout.php – ennuikiller

+4

Aha .. поэтому fink содержит замену 'readlink', совместимую с gnu. Это приятно знать, но это не решает мою проблему, так как мне нужен мой сценарий для работы на компьютере других людей, и я не могу требовать, чтобы они устанавливали Fink для этого. – troelskn

37

Вы можете быть заинтересованы в realpath(3) или Питона 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 этого сценария должна быть короче. :)

+0

Да, 'realpath' действительно то, что я хочу. Но мне кажется неудобным, что я должен скомпилировать двоичный код, чтобы получить эту функцию из сценария оболочки. – troelskn

+4

Почему бы не использовать однострочный Python в сценарии оболочки? (Не так сильно отличается от однострочного вызова 'readlink', это?) – Telemachus

+2

' python -c 'import os, sys; print (os.path.realpath (os.path.expanduser (sys.argv [1 ]))) "" $ {1} "' работает с путями типа '' ~/.symlink'' –

13

Я сделал скрипт Realpath лично, который выглядит немного что-то вроде:

#!/usr/bin/env python 
import os.sys 
print os.path.realpath(sys.argv[1]) 
+0

Вы должны изменить это на 'sys.argv [1]', если вы хотите, чтобы сценарий печатал реальную траекторию первого аргумента. 'sys.argv [0]' просто печатает реальный путь самого скрипта pyton, что не очень полезно. –

+13

Вот версия псевдонима для '~/.profile':' alias realpath = "python -c 'import os, sys; print os.path.realpath (sys.argv [1])'" ' – jwhitlock

6

Вот портативная функция оболочки, которая должна работать в ЛЮБОГО 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" 
} 

также, только в случае, если кто-то заинтересован вот как реализовать базовое имя и имя-каталога в 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 style). Копия лицензии может быть найдена, следуя приведенной гиперссылке на мой сайт.

+0

Не могли бы вы поставить заявление о лицензии на вашей странице? Если никакая лицензия не указана явно, никто не может использовать этот код в любом месте. – 0x89

+1

Спасибо за отзыв. Пусть будет известно, что вышеуказанный код свободен (как в пиве и свободе) в соответствии с лицензией BSD с 2 условиями. Я приложил копию лицензии BSD с двумя предложениями на мою веб-страницу по вашему запросу. спасибо и наслаждайтесь. – masta

+0

Real_path возвращает/filename вместо/path/to/filename в Mac OS X Lion в оболочке bash. – Barry

306

MacPorts и Homebrew предоставляют coreutils пакет, содержащий greadlink (GNU readlink). Кредит на должность Майкла Каллвейта в mackb.com.

brew install coreutils 

greadlink -f file.txt 
+11

То же самое для Homebrew; coreutils предоставляет 'greadlink'. – user

+0

очень полезно! благодаря! – zach

+44

'alias readlink = greadlink' –

-2

Perl имеет функцию readlink (например, How do I copy symbolic links in 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 
+5

Это делает то же самое, что и 'readlink' без параметр '-f' - нет рекурсии, нет абсолютного пути, поэтому вы можете использовать' readlink' напрямую. – mklement0

9

Что об этом?

function readlink() { 
    DIR=$(echo "${1%/*}") 
    (cd "$DIR" && echo "$(pwd -P)") 
} 
+0

Это единственное решение, которое работает для меня; Мне нужно разбирать пути, такие как '../../../' -> '/' – keflavich

+0

, что это не сработает, если у вас есть симлинк/usr/bin/'например, например, система' alternatives' любит иметь. – akostadinov

22
  1. Установить доморощенного
  2. Run "заварить установить Coreutils"
  3. Run "greadlink -f путь"

greadlink является гну readlink, который реализует -f. Вы можете использовать macports или другие, я предпочитаю homebrew.

1

Лучше поздно, чем никогда, я полагаю. Я был мотивирован развивать это специально, потому что мои сценарии Fedora не работали на Mac. Проблема заключается в зависимостях и Bash. Маки их не имеют, или, если они это делают, они часто находятся где-то в другом месте (другой путь). Манипуляция зависимостями в кросс-платформенном сценарии Bash является головной болью в лучшем случае и угрозой безопасности в худшем случае - так что лучше избегать их использования, если это возможно.

Функция get_realpath() ниже проста, Bash-centric и никакие зависимости не требуются. Я использую только встроенные Bash echo и cd. Он также довольно безопасен, поскольку все тестируется на каждом этапе пути и возвращает условия ошибки.

Если вы не хотите следовать символическим ссылкам, а затем положить набор -P в передней части сценария, но в противном случае кд должен разрешить символические ссылки по умолчанию. Он был протестирован с аргументами файла, которые являются {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+, но может быть сделано для работы со старыми версиями, если они все еще существуют.

3

Начать обновление

Это такая частая проблема, которую мы собрали библиотеку Bash 4 для свободного использования (MIT License) называется Realpath Пб. Это предназначено для эмуляции readlink -f по умолчанию и включает в себя два набора тестов для проверки (1), что он работает для данной системы unix, и (2) против readlink -f если установлен (но это не требуется). Кроме того, его можно использовать для исследования, идентификации и размотки глубоких, сломанных символических ссылок и круговых ссылок, поэтому он может быть полезным инструментом для диагностики глубоко вложенных физических или символических каталогов и проблем с файлами. Его можно найти по адресу github.com или bitbucket.org.

End Update

Другим очень компактным и эффективным решением, которое не опирается ни на что, но 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, для целей тестирования.

25

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

См. my project on 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 
} 

Обратите внимание, что это немного упрощенная версия the full implementation. Полная реализация добавляет небольшую проверку для циклов симлинк, а также немного массирует результат.

Наконец, функция для канонизации путь:

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") 
} 

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

+2

Я, например, думаю, что это потрясающе, что вы нашли время, чтобы сделать это. Престижность. – troelskn

+1

Портативный, как в POSIX! –

+0

Это не POSIX - он использует ключевое слово «local», отличное от POSIX – go2null

14

Простой один вкладыш в Perl, что это обязательно работать практически везде без каких-либо внешних зависимостей:

perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file 

Will разыменовывают симлинки.

Использование в сценарии может выглядеть так:

readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";} 
ABSPATH="$(readlinkf ./non-absolute/file)" 
7

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 поставляется с установленной недавно verions из perl), то:

perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt 

будет работать.

+2

Это верно для FreeBSD 10, но не для OS X 10.9. – Zanchey

+0

Хороший улов. Страница руководства [stat manual] (https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/stat.1.html) приведена для OS/X 10.9. Опция 'R' для' -f% 'отсутствует. Возможно, 'stat -f% Y' дает желаемый результат. Я отрегулирую ответ. Обратите внимание, что инструмент 'stat' появился во FreeBSD в версии 4.10 и NetBSD в версии 1.6. –

+1

'stat -f% Y $ PATH' печально ведет себя как' readlink $ PATH'. – Zanchey

-2

Ответ от @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" 
3

Есть уже много ответов, но ни один не работал для меня ... Так что это то, что я используя сейчас.

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" 
} 
+0

Это не работает, если '$ target' - это путь, он работает только в том случае, если это файл. – MaxGhost

0

Я написал a realpath utility for 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.

1

платформы Истинно indpendent будет также это R-Onliner

readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";} 

На самом деле имитируют readlink -f <path>, $ 2 вместо $ 1 необходимо будет использоваться.

-1

Это то, что я использую:

stat -f %N $your_path

+0

Это не печатает полный путь. –

5

Самый простой способ решить эту проблему и включить функциональность readlink на Mac ж/Homebrew установлен или FreeBSD, чтобы установить пакет 'Coreutils. Может также понадобиться для некоторых дистрибутивов Linux и других ОС POSIX.

Например, в FreeBSD 11, я установил, вызывая:

# pkg install coreutils

На MacOS с Homebrew, команда будет:

$ brew install coreutils

Не совсем уверен, почему другой ответы настолько сложны, вот и все. Файлы находятся не в другом месте, они еще не установлены.

+3

'brew install coreutils --with-default-names' будет более конкретным. Или в итоге вы получите «greadlink» ... – Henk

Смежные вопросы