Как я могу получить поведение 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.

Реализация

  1. Установить варку

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

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

brew install coreutils

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

Вы можете поместить свой псевдоним в ~/.bashrc, ~/.bash_profile или в любое место, где вы храните псевдонимы bash. Я лично держу свои в ~/.bashrc

alias readlink=greadlink

Вы можете создать аналогичные псевдонимы для других coreutils, таких как gmv, gdu, gdf и так далее. Но имейте в виду, что поведение GNU на компьютере Mac может сбивать с толку других, привыкших работать с нативным coreutils, или может вести себя неожиданно в вашей системе Mac.

Ответ 19

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

stat -f %N $your_path

Ответ 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