Как я могу получить поведение readlink -f GNU на Mac? - PullRequest
330 голосов
/ 29 июня 2009

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

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

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

Ответы [ 22 ]

392 голосов
/ 27 октября 2010

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

brew install coreutils

greadlink -f file.txt
162 голосов
/ 13 июля 2009

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

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

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

42 голосов
/ 12 июля 2009

Вас могут заинтересовать 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) для переноса вызова библиотеки: 1010 *

#!/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 этого скрипта должна быть короче. :)

25 голосов
/ 09 апреля 2014

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

См. мой проект на 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")
}

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

24 голосов
/ 29 марта 2012
  1. Установить доморощенный
  2. Запустите "brew install coreutils"
  3. Запустите "greadlink -f путь"

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

21 голосов
/ 04 июля 2014

Простой однострочный в 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)"
17 голосов
/ 05 ноября 2009

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

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
11 голосов
/ 28 февраля 2012

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

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}
8 голосов
/ 17 сентября 2014

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

В некоторых версиях stat для OS X может отсутствовать опция -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

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

7 голосов
/ 26 апреля 2010

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

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

...