web-dev-qa-db-ja.com

MacでGNUのreadlink -fの動作を取得するにはどうすればよいですか?

Linuxでは、readlinkユーティリティは、追加のリンクに続くオプション-fを受け入れます。これはMacおよびおそらくBSDベースのシステムでは動作しないようです。同等のものは何でしょうか?

デバッグ情報は次のとおりです。

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

readlink -fは2つのことを行います:

  1. 実際のファイルが見つかるまで、シンボリックリンクのシーケンスに沿って繰り返します。
  2. そのファイルの 正規化された 名前-つまり、絶対パス名。

必要に応じて、Vanillaの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

これにはエラー処理は含まれないことに注意してください。特に重要なのは、シンボリックリンクサイクルを検出しないことです。これを行う簡単な方法は、ループを一巡した回数をカウントし、1,000などの非常に大きな数に達した場合に失敗することです。

pwd -Pの代わりに$PWDを使用するように編集しました。

このスクリプトは、./script_name filenameのように呼び出され、-fなしで、$1$2に変更して、-f filenameのように使用できるようにします。GNU readlink。

162
Keith Smith

MacPortsとHomebrewは、greadlink(GNU readlink)を含むcoreutilsパッケージを提供します。 mackb.comのMichael Kallweitt投稿の功績。

brew install coreutils

greadlink -f file.txt
388
tomyjwu

realpath(3) 、またはPythonの os.path.realpath に興味があるかもしれません。 2つはまったく同じではありません。 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)ポータブルで純粋なシェル実装、およびb)単体テストカバレッジ、このようなもののエッジケースの数はnon-trivialです。

テストと完全なコードについては、 Githubでの私のプロジェクト を参照してください。実装の概要は次のとおりです。

キース・スミスが率直に指摘するように、readlink -fは2つのことを行います。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 path」を実行します

greadlinkは、-fを実装するgnu readlinkです。 macportsなどを使用することもできます。私はhomebrewを好みます。

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 には、NetBSDから派生したstatのバージョンがあります。

フォーマットスイッチを使用して出力を調整できます(上記のリンクのマニュアルページを参照)。

%  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はファイルに対応する絶対パス名を表示します。

編集:

Perlを使用できる場合(Darwin/OS XにはPerlの最新バージョンがインストールされています):

Perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt

働くでしょう。

8
G. Cito

以下は、ANYBourneに匹敵するシェルで動作する移植可能なシェル関数です。相対パスの句読点「..または。」を解決します。シンボリックリンクを逆参照します。

何らかの理由で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"
}

また、ここで誰かが興味を持っている場合に備えて、100%純粋なシェルコードでbasenameとdirnameを実装する方法があります。

## 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スタイル)ライセンスの条件の下でライセンスされています。ライセンスのコピーは、私のサイトへの上記のハイパーリンクをたどって見つけることができます。

7
masta

この問題を解決し、HomebrewまたはFreeBSDがインストールされているMacでreadlinkの機能を有効にする最も簡単な方法は、「c​​oreutils」パッケージをインストールすることです。特定のLinuxディストリビューションおよび他のPOSIX OSでも必要になる場合があります。

たとえば、FreeBSD 11では、次を呼び出してインストールしました。

# pkg install coreutils

Homebrewを使用したMacOSでは、コマンドは次のようになります。

$ brew install coreutils

なぜ他の答えがそんなに複雑なのかはよくわかりませんが、それだけです。ファイルは別の場所にはなく、まだインストールされていないだけです。

6
AveryFreeman

更新の開始

これは頻繁に発生する問題であるため、realpath-libと呼ばれるBash 4ライブラリを無料で使用するために作成しました(MITライセンス)。これは、デフォルトでreadlink -fをエミュレートするように設計されており、特定のUNIXシステムで動作することを確認する2つのテストスイートと(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>を実際に模倣するには、$ 1ではなく$ 2を使用する必要があります。

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 "$@"
  else
    readlink "$@"
  fi
}

function globalSed () {
  # Use gsed if on macOS; otherwise use normal sed
  if [[ $OSTYPE == darwin* ]]; then
    gsed "$@"
  else
    sed "$@"
  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ビルトインechocdのみを使用します。方法の各段階ですべてがテストされ、エラー条件が返されるため、かなり安全です。

シンボリックリンクを追跡したくない場合は、スクリプトの先頭にset -Pを配置しますが、それ以外の場合はcdは、デフォルトでシンボリックリンクを解決する必要があります。 {絶対|相対|シンボリックリンク| 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 4+が必要ですが、まだ残っている場合は古いバージョンで動作するようにすることができます。

1
AsymLabs

OS X用のrealpathユーティリティ と書いたので、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

0
user454322

説明

coreutilsはbrewパッケージで、GNU/Linuxコアユーティリティをインストールします。これらのユーティリティは、Mac OSX実装に対応しているため、これらを使用できます。

Mac osxシステムには、Linux coreutils(「Core Utilities」)に似ているように見えるプログラムやユーティリティがありますが、いくつかの点で異なっています(フラグが異なるなど)。

これは、これらのツールのMac OSX実装が異なるためです。元のGNU/Linuxのような動作を得るには、coreutilsパッケージ管理システムを介してbrewパッケージをインストールできます。

これにより、対応するコアユーティリティがインストールされ、接頭辞gが付けられます。例えば。 readlinkには、対応するgreadlinkプログラムがあります。

readlinkをGNU readlinkgreadlink)実装のように実行するには、coreutilsのインストール後に簡単なエイリアスを作成できます。

実装

  1. Brewをインストールする

https://brew.sh/ の指示に従ってください

  1. Coreutilsパッケージをインストールする

brew install coreutils

  1. エイリアスを作成する

エイリアスは〜/ .bashrc、〜/ .bash_profile、またはbashエイリアスの保持に慣れている場所に配置できます。私は個人的に〜/ .bashrcに保存します

alias readlink=greadlink

Gmv、gdu、gdfなどの他のcoreutilsに対して同様のエイリアスを作成できます。しかし、MacマシンのGNU動作は、ネイティブcoreutilsを使用する他のユーザーを混乱させたり、Macシステムで予期しない動作をする可能性があることに注意してください。

0
yosefrow