Вопрос по sh, macos, freebsd – Как я могу получить поведение readlink -f GNU на Mac?

316

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

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

<code>$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
</code>
Мне бы очень хотелось, чтобы Apple сделала так, чтобы OS X поддерживала больше путей Linux по умолчанию, и обращалась к таким вещам. Они могли бы сделать это, ничего не сломав, не так ли? CommaToast
Почему эта опция запрещена на Mac? CommaToast
Это могло бы объяснить разницу, возможно. Я уверен, что использовал bash в обоих случаях. troelskn
@CommaToast Ну, они поставляются с Perl так ++ :-) ... откройте Terminal.app и введите:touch myfile ; ln -s myfile otherfile ; perl -MCwd=abs_path -le 'print abs_path readlink(shift);' otherfile ... в моем случае я вижу: / Users / cito / myfile`. Добавил его в мой ответ ниже. Приветствия. G. Cito
Немного поздно, но в вашем вопросе отсутствует упоминание об используемой вами оболочке. Это актуально, потому чтоreadlink может быть встроенной или внешней командой. 0xC0000022L

Ваш Ответ

21   ответ
10

Как насчет этого?

function readlink() {
  DIR=$(echo "${1%/*}")
  (cd "$DIR" && echo "$(pwd -P)")
}
это не может работать, если у вас есть символическая ссылка/usr/bin/ например, какalternatives Система любит иметь.
Это единственное решение, которое работает для меня; Мне нужно разобрать пути как../../../ - & GT;/
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"
}
Это не работает, если$target путь, он работает только в том случае, если это файл.
151

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

  1. It iterates along a sequence of symlinks until it finds an actual file.
  2. It returns that file's canonicalized name—i.e., its absolute pathname.

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

Отличное решение, но оно не работает должным образомpaths that need escapingтакие как имена файлов или каталогов сembedded spaces, Чтобы исправить это, используйтеcd "$(dirname "$TARGET_FILE")"  а такжеTARGET_FILE=$(readlink "$TARGET_FILE") а такжеTARGET_FILE=$(basename "$TARGET_FILE") в соответствующих местах в приведенном выше коде.
Ах. Да. Это не так просто, но вы можете обновить приведенный выше скрипт, чтобы справиться с этим. Я соответственно отредактирую (переписать) ответ.
Насколько я могу судить, это не сработает, если родительский каталог пути является символической ссылкой. Например. еслиfoo -> /var/cux, затемfoo/bar не будет решен, потому чтоbar не является ссылкой, хотяfoo является. troelskn
Ну, ссылка может быть где угодно в пути. Я полагаю, что сценарий может проходить по каждой части пути, но тогда он становится немного сложным. troelskn
Благодарю. Проблема состоит в том, что $ PWD предоставляет нам логический рабочий каталог, основанный на значениях символических ссылок, за которыми мы следовали. Мы можем получить настоящий физический каталог с помощью & apos; pwd -P & apos; Это должно вычислить это, преследуя & quot; .. & quot; до корня файловой системы. Я обновлю сценарий в своем ответе соответственно.
376

MacPorts и Homebrew обеспечиваютcoreutils пакет, содержащийgreadlink (GNU readlink). Благодарим Майкла Каллвейта от mackb.com.

brew install coreutils

greadlink -f file.txt
alias readlink=greadlink
@ching Сделай это вместо этого в своем.bashrc: export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
Пожалуйста, не делайте, если вы а) не пишете программное обеспечение для себя, б) не хотите связываться с другими. Напишите его так, чтобы он "просто работал" для всех.
То же самое для доморощенного; Coreutils обеспечиваетgreadlink.
I wanted GNU и другие утилиты на моем Mac, которые, как я считаю, работают хорошо, без префикса, и легко доступны из моего пользователя.PATH, Сделал выше, начиная сbrew install <package> --default-names, Много раз на вопрос на этом сайте отвечали немного, или более того, за пределами технических требований автора, но в аналогичной ситуации, и все еще был очень полезен.topbug.net/blog/2013/04/14/…
42

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

@troelskin Я не понял Perl, Python,etc. были "разрешены" !! ... в этом случае я собираюсь добавитьperl -MCwd=abs_path -le 'print abs_path readlink(shift);' на мой ответ :-)
Да,realpath это действительно то, что я хочу. Но кажется довольно неловким, что мне нужно скомпилировать двоичный файл, чтобы получить эту функцию из сценария оболочки. troelskn
python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${1}" работает с путями как'~/.symlink'
Почему бы тогда не использовать однострочник Python в сценарии оболочки? (Не сильно отличается от однострочного вызоваreadlink само по себе, не так ли?)
-2

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

/sw/sbin/readlink -f

И в чем конкретно разница между / sw / sbin и / usr / bin? И почему два бинарных файла отличаются? troelskn
Ага .. так финка содержит заменуreadlink это совместимо с GNU. Это приятно знать, но это не решает мою проблему, так как мне нужно, чтобы мой скрипт запускался на машине других людей, и я не могу требовать, чтобы они установили fink для этого. troelskn
6

Вот портативная функция оболочки, которая должна работать вANY Борн сравнимая оболочка. Это разрешит пунктуацию относительного пути & quot; .. or. & Quot; и разыменование символических ссылок.

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

@ Доминик: не удивительно, так как внутренняя часть функции не работает в своей собственной оболочке ... действительно уродливой и хрупкой.
@ Барри То же самое происходит на OSX Snow Leopard. Хуже того, последовательные вызовы real_path () объединяют вывод!
Real_path возвращает / filename вместо / path / to / filename в Mac OS X Lion в оболочке bash.
-1

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

stat -f %N $your_path

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

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
Это так же, какreadlink без-f опция - без рекурсии, без абсолютного пути - так что вы можете использоватьreadlink непосредственно.
17

Простой однострочный в 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)"
чтобы получить завершающий перевод новой строки, добавьте-l, лайкperl -MCwd -le 'print Cwd::abs_path shift' ~/non-absolute/file
1

По-настоящему независимым от платформы будет и этот R-onliner

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

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

0

я написалутилита 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.

23
  1. Install homebrew
  2. Run "brew install coreutils"
  3. Run "greadlink -f path"

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

Я не вижу никакой разницы с ответом, данным в 2010 годуstackoverflow.com/a/4031502/695671
-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

Begin Update

Это настолько частая проблема, что мы собрали библиотеку Bash 4 для бесплатного использования (лицензия MIT), которая называетсяrealpath-lib, Это предназначено для подражания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-совместимый код завершения для целей тестирования.

1

Поскольку моя работа используется людьми с не-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
}

Дополнительная проверка, которую вы можете добавить для автоматической установкидоморощенного + 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.

8

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 Xstat  может не хватать-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

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

Это верно для FreeBSD 10, но не для OS X 10.9.
Хороший улов.stat manual page цитируется для OS / X 10.9.R возможность-f% пропал, отсутствует. возможноstat -f%Y дает желаемый результат. Я буду корректировать ответ. Обратите внимание, чтоstat инструмент появился во FreeBSD в версии 4.10 и NetBSD в версии 1.6.
stat -f%Y $PATH печально ведет себя какreadlink $PATH в одиночестве.
6

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

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

# pkg install coreutils

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

$ brew install coreutils

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

brew install coreutils --with-default-names быть более конкретным. Или вы в конечном итоге получите "greadlink" вместо...
Я не вижу никакой разницы с ответами 2010 годаstackoverflow.com/a/4031502/695671 и 2012stackoverflow.com/a/9918368/695671 Вопрос на самом деле "... на Mac и, возможно, на системах BSD. Каким будет эквивалент? & Quot; Я не верю, что это очень хороший ответ на этот вопрос. Причин много, но вы можете ориентироваться на компьютеры Mac OS без возможности что-либо установить.pwd -P довольно просто, но он исчезает среди многих других ответов, в том числе повторяющихся, отсюда и отрицательный ответ.
25

Ненавижу наваливать еще одну реализацию, но мне нужна была)a portable, pure shell implementationи б)unit-test coverage, так как число крайних случаев для чего-то подобногоnon-trivial.

Увидетьмой проект на 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 adds a small check for symlink cycles, а также немного массирует вывод.

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

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

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

Это не POSIX - он использует не POSIXlocal ключевое слово
1

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

Функция get_realpath () ниже проста, ориентирована на Bash, и никаких зависимостей не требуется. Я использую только встроенные Bashecho а также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 Пб (полное раскрытие информации - это наш продукт, но мы предлагаем его бесплатно для сообщества без каких-либо ограничений). Он также может служить учебным пособием - он хорошо документирован.

Мы старались изо всех сил применять так называемый «современный Bash». практики, но Bash - большая тема, и я уверен, что всегда найдется место для улучшения. Требуется Bash 4+, но его можно настроить для работы со старыми версиями, если они все еще существуют.

17

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

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
альтернативноos.readlink().
Вот псевдоним для~/.profile: alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[1])'"
Вы должны изменить это наsys.argv[1] если вы хотите, чтобы скрипт печатал реальный путь первого аргумента.sys.argv[0] просто печатает реальный путь самого скрипта pyton, что не очень полезно.

Похожие вопросы