it-swarm.com.ru

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

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

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

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

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

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

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

160
Keith Smith

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

brew install coreutils

greadlink -f file.txt
388
tomyjwu

Вас могут заинтересовать 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 __== '__main__':
    print realpath(sys.argv[1])

По иронии судьбы версия C этого скрипта должна быть короче. :)

42
Miles

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

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

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

25
Michael Kropat
  1. Установить доморощенный
  2. Запустите "brew install coreutils"
  3. Запустите "greadlink -f путь"

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

24
Andrew

Простой однострочный в 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)"
20
JinnKo

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

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
17
James

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

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}
11
Peeeech

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

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

8
G. Cito

Вот переносимая функция Shell, которая должна работать в ЛЮБОМ Bourne-сопоставимом Shell. Это разрешит пунктуацию относительного пути ".. или." и разыменование символических ссылок.

Если по какой-либо причине у вас нет команды 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% чистом коде Shell:

## 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##*/}"
}

Вы можете найти обновленную версию этого кода Shell на моем сайте Google: http://sites.google.com/site/jdisnard/realpath

Правка: Этот код лицензируется в соответствии с условиями 2-пунктовой (стиль FreeBSD) лицензии. Копию лицензии можно найти, перейдя по указанной выше гиперссылке на мой сайт.

7
masta

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

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

# pkg install coreutils

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

$ brew install coreutils

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

6
AveryFreeman

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

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

3
AsymLabs

Ответов уже много, но у меня не получилось ни одного ... Так что я сейчас и пользуюсь.

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"
}
3
estani

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

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

Чтобы фактически имитировать readlink -f <path>, нужно использовать $ 2 вместо $ 1.

1
Holger Brandl

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

1
clounie

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

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

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

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

1
AsymLabs

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

0
user454322

Объяснение

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.

0
yosefrow