سؤال

بالنظر إلى المسار المطلق أو النسبي (في نظام يشبه يونكس)، أود تحديد المسار الكامل للهدف بعد حل أي روابط رمزية وسيطة.نقاط إضافية لحل تدوين اسم المستخدم في نفس الوقت.

إذا كان الهدف عبارة عن دليل، فقد يكون من الممكن إدخال chdir() في الدليل ثم الاتصال بـ getcwd()، لكنني أريد حقًا القيام بذلك من برنامج نصي Shell بدلاً من كتابة مساعد C.لسوء الحظ، تميل الأصداف إلى محاولة إخفاء وجود الروابط الرمزية عن المستخدم (وهذا هو الحال في نظام التشغيل OS X):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

ما أريده هو دالة Resolve() بحيث عند تنفيذها من دليل tmp في المثال أعلاه، تكون Resolve("foo") == "/Users/greg/tmp/bar".

هل كانت مفيدة؟

المحلول

وفقا للمعايير ، pwd -P يجب أن يُرجع المسار مع حل الارتباطات الرمزية.

وظيفة ج char *getcwd(char *buf, size_t size) من unistd.h يجب أن يكون لها نفس السلوك.

com.getcwd الأشخاص ذوي الإعاقة

نصائح أخرى

readlink -f "$path"

ملحوظة المحرر:ما سبق يعمل مع جنو readlink و فري بي إس دي/ بي سي- بي إس دي/ أوبن بي إس دي readlink, ، لكن لا على OS X اعتبارًا من 10.11.
جنو readlink يقدم خيارات إضافية ذات صلة، مثل -m لحل الارتباط الرمزي سواء كان الهدف النهائي موجودًا أم لا.

لاحظ أنه منذ إصدار GNU coreutils 8.15 (2012-01-06)، يوجد com.realpath البرنامج المتاح أقل منفرجة وأكثر مرونة من المذكور أعلاه.كما أنه متوافق مع أداة FreeBSD التي تحمل نفس الاسم.ويتضمن أيضًا وظيفة إنشاء مسار نسبي بين ملفين.

realpath $path

[إضافة المشرف أدناه من التعليق بواسطة مرحبادانورتون]

بالنسبة لنظام التشغيل Mac OS X (حتى الإصدار 10.11.x على الأقل)، استخدم readlink بدون ال -f خيار:

readlink $path

ملحوظة المحرر:لن يؤدي هذا إلى حل الارتباطات الرمزية بشكل متكرر وبالتالي لن يبلغ عن ذروة هدف؛على سبيل المثال، الارتباط الرمزي المعطى a الذي يشير إلى b, ، والذي بدوره يشير إلى c, ، وهذا سوف يقدم تقريرا فقط b (ولن نتأكد من أنه تم إخراجه كملف المسار المطلق).
استخدم ما يلي perl الأمر على OS X لملء الفجوة المفقودة readlink -f وظائف:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

يبدو أن "pwd -P" يعمل إذا كنت تريد الدليل فقط، ولكن إذا كنت تريد لسبب ما اسم الملف الفعلي القابل للتنفيذ، فلا أعتقد أن هذا يساعد.هذا هو الحل الخاص بي:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

واحدة من المفضلة هي realpath foo

realpath - return the canonicalized absolute pathname

realpath  expands  all  symbolic  links  and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and
       stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path.  The resulting path will have no symbolic link, '/./' or
       '/../' components.
readlink -e [filepath]

يبدو أن ما تطلبه بالضبط - إنه يقبل مسارًا أرابراري ، ويحل جميع الارتباطات ، ويعيد المسار "الحقيقي" - وهو "قياسي *nix" الذي من المحتمل أن يكون لدى جميع الأنظمة بالفعل

طريق اخر:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

بتجميع بعض الحلول المقدمة معًا، مع العلم أن رابط القراءة متاح في معظم الأنظمة، ولكنه يحتاج إلى وسائط مختلفة، فإن هذا يعمل جيدًا بالنسبة لي على OSX وDebian.لست متأكدًا من أنظمة BSD.ربما يجب أن يكون الشرط [[ $OSTYPE != darwin* ]] لاستبعاد -f من OSX فقط.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"

ملحوظة:أعتقد أن هذا حل قوي ومحمول وجاهز، وهو موجود دائمًا طويل لهذا السبب بالذات.

أدناه هو أ البرنامج النصي / الوظيفة المتوافقة تمامًا مع POSIX هذا هو السبب عبر منصة (يعمل على نظام التشغيل macOS أيضًا، والذي readlink لا يزال لا يدعم -f اعتبارًا من 10.12 (سييرا)) - يستخدم فقط ميزات لغة الصدفة POSIX ومكالمات المرافق المتوافقة مع POSIX فقط.

إنها التنفيذ المحمول لـ GNU readlink -e (النسخة الأكثر صرامة من readlink -f).

أنت تستطيع تشغيل النصي مع sh أو مصدر وظيفة في bash, ksh, ، و zsh:

على سبيل المثال، داخل البرنامج النصي، يمكنك استخدامه على النحو التالي للحصول على دليل الأصل الحقيقي للبرنامج النصي قيد التشغيل، مع حل الارتباطات الرمزية:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink تعريف البرنامج النصي/الوظيفة:

تم تعديل الكود بامتنان من هذه الإجابة.
لقد قمت أيضًا بإنشاء ملف bashإصدار فائدة قائم بذاته هنا, ، والتي يمكنك التثبيت بها
npm install rreadlink -g, ، إذا كان Node.js مثبتًا لديك.

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

ظل على الأمن:

جارنو, ، في إشارة إلى الوظيفة التي تضمن ذلك المدمج command لم يتم تظليله بواسطة اسم مستعار أو وظيفة Shell بنفس الاسم، يسأل في تعليق:

ماذا إذا unalias أو unset و [ يتم تعيينها كأسماء مستعارة أو وظائف شل؟

الدافع وراء rreadlink هل تضمن ذلك command معناه الأصلي هو استخدامه للتجاوز (حميد) راحة غالبًا ما تُستخدم الأسماء المستعارة والوظائف لتظليل الأوامر القياسية في الأصداف التفاعلية، مثل إعادة التعريف ls لتضمين الخيارات المفضلة.

أعتقد أنه من الآمن أن نقول ذلك إلا إذا كنت تتعامل مع بيئة ضارة غير موثوقة، مما يثير القلق unalias أو unset - أو، في هذا الصدد، while, do, ، ...- إعادة التعريف ليست مصدر قلق.

هنالك شئ ما التي يجب أن تعتمد عليها الوظيفة للحصول على معناها وسلوكها الأصليين - لا توجد طريقة للتغلب على ذلك.
تسمح الأصداف المشابهة لـ POSIX بإعادة تعريف العناصر المدمجة وحتى الكلمات الرئيسية للغة متأصل خطر أمني (وكتابة التعليمات البرمجية بجنون العظمة أمر صعب بشكل عام).

لمعالجة مخاوفك على وجه التحديد:

تعتمد الوظيفة على unalias و unset لها معناها الأصلي.إعادة تعريفهم على أنهم وظائف القشرة بطريقة تغير سلوكهم ستكون مشكلة؛إعادة تعريف باعتبارها الاسم المستعار ليس بالضرورة مصدر قلق ، لأن نقلا عن (جزء من) اسم الأمر (على سبيل المثال، \unalias) يتجاوز الأسماء المستعارة.

ومع ذلك، نقلا عن لا خيار للقذيفة الكلمات الدالة (while, for, if, do, ، ...) وبينما تكون الكلمات الأساسية لـ Shell لها الأسبقية على Shell المهام, ، في bash و zsh الأسماء المستعارة لها الأسبقية القصوى، لذلك يجب عليك تشغيلها للحماية من إعادة تعريف الكلمات الرئيسية unalias بأسمائهم (على الرغم من وجودها في غير تفاعلية bash القذائف (مثل البرامج النصية) هي الأسماء المستعارة لا موسعة بشكل افتراضي - فقط إذا shopt -s expand_aliases يُطلق عليه صراحة أولاً).

لتتآكد من ذلك unalias - باعتباره مدمجًا - له معناه الأصلي، ويجب عليك استخدامه \unset عليه أولا، وهو ما يقتضي ذلك unset لها معناها الأصلي:

unset هي قذيفة مدمج, ، لذا للتأكد من أنه تم استدعاؤه على هذا النحو، يجب عليك التأكد من عدم إعادة تعريفه على أنه وظيفة.بينما يمكنك تجاوز نموذج الاسم المستعار مع الاقتباس، لا يمكنك تجاوز نموذج دالة الصدفة - التقط 22.

وبالتالي، إلا إذا كنت تستطيع الاعتماد عليها unset للحصول على معناها الأصلي، حسب ما أستطيع قوله، لا توجد طريقة مضمونة للدفاع ضد كل إعادة التعريفات الخبيثة.

غالبًا ما يتعين على نصوص shell الشائعة العثور على الدليل "الرئيسي" الخاص بها حتى لو تم استدعاؤها كرابط رمزي.وبالتالي يجب على البرنامج النصي العثور على موضعه "الحقيقي" بدءًا من 0 دولار فقط.

cat `mvn`

على نظامي، يقوم بطباعة برنامج نصي يحتوي على ما يلي، والذي يجب أن يكون تلميحًا جيدًا لما تحتاجه.

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..

جرب هذا:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

نظرًا لأنني واجهت هذا عدة مرات على مر السنين، وهذه المرة كنت بحاجة إلى إصدار bash محمول خالص يمكنني استخدامه على OSX وlinux، فقد تقدمت وكتبت واحدًا:

النسخة الحية تعيش هنا:

https://github.com/keen99/shell-functions/tree/master/resolve_path

ولكن من أجل SO، إليك الإصدار الحالي (أشعر أنه تم اختباره جيدًا..ولكنني منفتح على تلقي التعليقات!)

قد لا يكون من الصعب جعل الأمر يعمل مع bourne Shell (sh)، لكنني لم أحاول... أحب $FUNCNAME كثيرًا.:)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

هنا مثال كلاسيكي، وذلك بفضل الشراب:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

استخدم هذه الوظيفة وستعيد المسار -الحقيقي-:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 

للتغلب على عدم توافق نظام Mac، توصلت إلى

echo `php -r "echo realpath('foo');"`

ليست رائعة ولكنها متقاطعة مع نظام التشغيل

إليك كيفية الحصول على المسار الفعلي للملف في MacOS/Unix باستخدام برنامج Perl النصي المضمن:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

وبالمثل، للحصول على دليل الملف المرتبط رمزيًا:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

هل مسارك دليل أم قد يكون ملفًا؟إذا كان دليلاً، فالأمر بسيط:

(cd "$DIR"; pwd -P)

ومع ذلك، إذا كان ملفًا، فلن ينجح هذا:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

لأن الارتباط الرمزي قد يتحول إلى مسار نسبي أو كامل.

في البرامج النصية، أحتاج إلى العثور على المسار الحقيقي، حتى أتمكن من الرجوع إلى التكوين أو البرامج النصية الأخرى المثبتة معه، أستخدم هذا:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

هل يمكن أن تحدد SOURCE إلى أي مسار الملف.في الأساس، طالما أن المسار عبارة عن رابط رمزي، فإنه يحل هذا الارتباط الرمزي.الحيلة موجودة في السطر الأخير من الحلقة.إذا كان الارتباط الرمزي الذي تم حله مطلقًا، فسيتم استخدامه كـ SOURCE.ومع ذلك، إذا كان الأمر نسبيًا، فسيتم تقديمه مسبقًا DIR لذلك، والذي تم تحويله إلى موقع حقيقي من خلال الحيلة البسيطة التي وصفتها لأول مرة.

أعتقد أن هذه هي "الطريقة الحقيقية والنهائية لحل الارتباط الرمزي" سواء كان دليلاً أو غير دليل، باستخدام Bash:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P "$link"
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

لاحظ أن جميع cd و set الأشياء تحدث في غلاف فرعي.

أقدم هنا ما أعتقد أنه حل مشترك بين الأنظمة الأساسية (Linux وmacOS على الأقل) للإجابة التي تعمل بشكل جيد بالنسبة لي حاليًا.

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

إليك حل macOS (فقط؟).ربما أكثر ملاءمة للسؤال الأصلي.

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top