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

StackOverflow https://stackoverflow.com/questions/1055671

  •  20-08-2019
  •  | 
  •  

Вопрос

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

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

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
Это было полезно?

Решение

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

  1. Он перебирает последовательность символических ссылок до тех пор, пока не найдет реальный файл.
  2. Он возвращает этот файл канонизированный 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")
}

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

  1. Установите homebrew
  2. Запустите "brew install coreutils".
  3. Запустите "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.

Реализация

  1. Установите brew

Следуйте инструкциям на https://brew.sh/

  1. Установите пакет coreutils

brew install coreutils

  1. Создайте псевдоним

Вы можете поместить свой псевдоним в ~/.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"
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top