Как я могу получить поведение GNU readlink-f на Mac?


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

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

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

20 ответов:

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

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

если вы хотите, вы можете просто построить сценарий оболочки, который использует поведение vanilla readlink для достижения того же самого. Вот вам пример. Очевидно, вы можете вставить это в свой собственный скрипт, где вы хотели бы позвонить readlink -f

#!/bin/sh

TARGET_FILE=

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

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

посмотреть мой проект на Github для тестов и полный код. Ниже приводится краткий обзор реализации:

как проницательно указывает Кит Смит,readlink -f делает две вещи: 1) разрешает символические ссылки рекурсивно, и 2) канонизирует результат, таким образом:

realpath() {
    canonicalize_path "$(resolve_symlinks "")"
}

во-первых, реализация решателя символьных ссылок:

resolve_symlinks() {
    local dir_context path
    path=$(readlink -- "")
    if [ $? -eq 0 ]; then
        dir_context=$(dirname -- "")
        resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
    else
        printf '%s\n' ""
    fi
}

_prepend_path_if_relative() {
    case "" in
        /* ) printf '%s\n' "" ;;
         * ) printf '%s\n' "/" ;;
    esac 
}

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

наконец, функция канонизации пути:

canonicalize_path() {
    if [ -d "" ]; then
        _canonicalize_dir_path ""
    else
        _canonicalize_file_path ""
    fi
}   

_canonicalize_dir_path() {
    (cd "" 2>/dev/null && pwd -P) 
}           

_canonicalize_file_path() {
    local dir file
    dir=$(dirname -- "")
    file=$(basename -- "")
    (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}

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

  1. установить homebrew
  2. запустите "brew install coreutils"
  3. запустить "greadlink-F path"

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

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

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

будет разыменовывать символические ссылки.

использование в скрипте может быть следующим:

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

Я сделал скрипт под названием 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)")
}

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 не хватает для форматов. В данном случае -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

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

вот портативная функция оболочки, которая должна работать в любой Борн сопоставимые оболочки. Это позволит решить относительный путь пунктуации".. или. - и разыменование символических связей.

Если по какой-то причине у вас нет команды realpath(1) или readlink(1), это может быть псевдоним.

which realpath || alias realpath='real_path'

наслаждайтесь:

real_path () {
  OIFS=$IFS
  IFS='/'
  for I in 
  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 [ "" = "->" ]
        then FOO=
             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 style). Копию лицензии можно найти, перейдя по указанной выше гиперссылке на мой сайт.

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

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

# pkg install coreutils

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

$ brew install coreutils

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

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

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

Обновления

еще одно очень компактное и эффективное решение, которое не полагается ни на что, кроме Bash:

function get_realpath() {

    [[ ! -f "" ]] && 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=""
  [ -f "$target" ] || return 1 #no nofile

  while [ -L "$target" ]; do
    target="$(readlink "$target")" 
  done
  echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}

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

функция get_realpath() ниже проста, Bash-ориентированный, и никаких зависимостей не требуется. Я использую только bash builtins Эхо и cd. Это также довольно безопасно, так как все проверяется на каждом этапе пути, и он возвращает условия ошибки.

Если вы не хотите следовать символическим ссылкам, то поставьте set-P в начале сценария, но в остальном cd должен разрешить символические ссылки по умолчанию. Он был протестирован с аргументами файла, которые являются {абсолютными / относительными | symlink / local} и возвращает абсолютный путь к файлу. До сих пор у нас не было никаких проблем с этим.

function get_realpath() {

if [[ -f "" ]]
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+ , но может быть сделано для работы со старыми версиями, если они все еще вокруг.

истинно платформо-независимые будут также в этом Р-покатушки

readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[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.

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

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=""
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"