Cara mengatasi tautan simbolis dalam skrip shell

220

Diberikan jalur absolut atau relatif (dalam sistem mirip Unix), saya ingin menentukan jalur lengkap target setelah menyelesaikan setiap symlink perantara. Poin bonus untuk menyelesaikan notasi ~ nama pengguna pada saat yang bersamaan.

Jika targetnya adalah direktori, mungkin chdir () masuk ke direktori dan kemudian memanggil getcwd (), tapi saya benar-benar ingin melakukan ini dari skrip shell daripada menulis C helper. Sayangnya, shell memiliki kecenderungan untuk mencoba menyembunyikan keberadaan symlink dari pengguna (ini bash di 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
$

Yang saya inginkan adalah fungsi resol () yang ketika dijalankan dari direktori tmp pada contoh di atas, resolus ("foo") == "/ Users / greg / tmp / bar".

Greg Hewgill
sumber

Jawaban:

92

Menurut standar, pwd -Pharus mengembalikan jalur dengan symlink diselesaikan.

Fungsi C char *getcwd(char *buf, size_t size)dari unistd.hharus memiliki perilaku yang sama.

getcwd pwd

kauppi
sumber
22
Ini hanya berfungsi untuk direktori (saat ini)? Jika targetnya adalah file, itu tidak akan memberikan apa-apa ...
dala
4
Tidak berfungsi jika tautan rusak karena Anda tidak dapat menjadikannya jalur Anda saat ini
Tom Howard
6
Untuk meringkas dengan manfaat belakang: Jawaban ini hanya berfungsi dalam keadaan yang sangat terbatas, yaitu jika symlink of interest adalah ke direktori yang benar-benar ada ; ditambah, Anda harus cdmelakukannya terlebih dahulu, sebelum menelepon pwd -P. Dengan kata lain: itu tidak akan memungkinkan Anda untuk menyelesaikan (lihat target) symlink ke file atau dari symlink yang rusak , dan untuk menyelesaikan symlink direktori yang ada Anda harus melakukan pekerjaan tambahan (mengembalikan direktori kerja sebelumnya atau melokalkan direktori kerja sebelumnya atau memanggil cddan pwd -Pmemanggil dalam subkulit).
mklement0
buruk saya mencari cara untuk menyelesaikan file dan bukan direktori.
Martin
Seperti yang telah ditunjukkan orang lain, ini tidak benar-benar menjawab pertanyaan. @ jawaban pixelbeat di bawah ini tidak.
erstaples
401
readlink -f "$path"

Catatan editor: Di atas berfungsi dengan GNU readlink dan FreeBSD / PC-BSD / OpenBSD readlink , tetapi tidak pada OS X pada 10.11.
GNU readlink menawarkan opsi tambahan yang terkait, seperti -muntuk menyelesaikan symlink apakah ada target akhir atau tidak.

Catatan sejak GNU coreutils 8.15 (2012-01-06), ada program realpath yang tersedia yang kurang tumpul dan lebih fleksibel daripada yang di atas. Ini juga kompatibel dengan util FreeBSD dengan nama yang sama. Ini juga mencakup fungsionalitas untuk menghasilkan jalur relatif antara dua file.

realpath $path

[Tambahan admin di bawah ini dari komentar oleh halloleo - danorton ]

Untuk Mac OS X (setidaknya melalui 10.11.x), gunakan readlinktanpa -fopsi:

readlink $path

Catatan Editor: Ini tidak akan menyelesaikan symlink secara rekursif dan karenanya tidak akan melaporkan target akhir ; misalnya, diberi symlink ayang menunjuk ke b, yang pada gilirannya menunjuk ke c, ini hanya akan melaporkanb (dan tidak akan memastikan bahwa itu adalah output sebagai jalur absolut ).
Gunakan perlperintah berikut pada OS X untuk mengisi celah readlink -ffungsionalitas yang hilang :
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

pixelbeat
sumber
5
Ini tidak berfungsi di Mac OS X - lihat stackoverflow.com/questions/1055671/...
Bkkbrad
1
malu tentang ketidakcocokan OS X, jika tidak baik +1
jkp
11
Pada OS X Anda dapat menginstal coreutils dengan homebrew. Menginstalnya sebagai "grealpath".
Kief
10
readlinkbekerja pada OSX, tapi perlu sintaks lain: readlink $path tanpa itu -f.
halloleo
2
readlink gagal untuk menghilangkan referensi beberapa lapisan symlink, itu hanya melakukan satu layer pada satu waktu
Magnus
26

"pwd -P" tampaknya berfungsi jika Anda hanya ingin direktori, tetapi jika karena alasan tertentu Anda ingin nama executable aktual saya tidak berpikir itu membantu. Inilah solusi saya:

#!/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
tlrobinson
sumber
17

Salah satu favorit saya adalah realpath foo

realpath - mengembalikan pathname absolut yang dikanonikalisasi

realpath memperluas semua tautan simbolis dan menyelesaikan referensi ke karakter '/./', '/../' dan ekstra '/' dalam string null yang diakhiri dengan nama path dan
       menyimpan pathname absolut yang dikanonikalisasi dalam buffer ukuran PATH_MAX yang dinamai oleh resolved_path. Jalur yang dihasilkan tidak akan memiliki tautan simbolis, '/./' atau
       Komponen '/../'.
Gregory
sumber
Pada Debian (etch dan yang lebih baru) perintah ini tersedia dalam paket realpath.
Phil Ross
2
realpath sekarang (Jan 2012) bagian dari coreutils dan kompatibel dengan varian debian dan BSD
pixelbeat
1
Saya tidak realpathmenggunakan Centos 6 dengan GNU coreutils 8.4.31. Saya telah menemukan beberapa orang lain di Unix & Linux yang memiliki GNU coreutils tanpa paket realpath. Jadi sepertinya tergantung pada lebih dari sekedar versi.
toxalot
Saya lebih suka realpathlebih readlinkkarena menawarkan bendera pilihan seperti--relative-to
arr_sea
10
readlink -e [filepath]

tampaknya persis seperti yang Anda minta - ia menerima jalur arbirary, menyelesaikan semua symlink, dan mengembalikan jalur "nyata" - dan itu "standar * nix" yang kemungkinan semua sistem sudah memiliki

Chuck Kollars
sumber
Baru saja digigit olehnya. Ini tidak berfungsi di Mac dan saya mencari penggantinya.
dhill
5

Cara lain:

# 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|"; } 
Keymon
sumber
Saya perlu cd di myreadlink (), karena ini adalah fungsi rekursif, pergi ke setiap direktori hingga menemukan tautan. Jika ia menemukan tautan, mengembalikan jalur real, dan kemudian sed akan menggantikan jalur.
Keymon
5

Menyatukan beberapa solusi yang diberikan, mengetahui bahwa readlink tersedia di sebagian besar sistem, tetapi membutuhkan argumen yang berbeda, ini berfungsi baik untuk saya di OSX dan Debian. Saya tidak yakin tentang sistem BSD. Mungkin kondisinya perlu [[ $OSTYPE != darwin* ]]dikecualikan -fdari OSX saja.

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

Berikut ini cara mendapatkan jalur sebenarnya ke file di MacOS / Unix menggunakan skrip inline Perl:

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

Demikian pula, untuk mendapatkan direktori file yang disinkronkan:

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

Apakah jalur Anda direktori, atau mungkin itu file? Jika itu direktori, sederhana saja:

(cd "$DIR"; pwd -P)

Namun, jika itu mungkin file, maka ini tidak akan berfungsi:

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

karena symlink mungkin menyelesaikan ke jalur relatif atau penuh.

Pada skrip saya perlu menemukan path yang sebenarnya, sehingga saya dapat merujuk konfigurasi atau skrip lain yang diinstal bersama dengannya, saya menggunakan ini:

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

Anda dapat mengatur SOURCEjalur file apa pun. Pada dasarnya, selama path adalah symlink, ia menyelesaikan symlink tersebut. Triknya ada di baris terakhir loop. Jika symlink yang diselesaikan mutlak, ia akan menggunakannya sebagai SOURCE. Namun, jika itu relatif, itu akan menambah DIRuntuk itu, yang diselesaikan menjadi lokasi nyata dengan trik sederhana yang saya jelaskan pertama kali.

Daniel C. Sobral
sumber
2
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"))/..
Dave
sumber
Ini akan terputus dengan (a) jalur apa pun yang berisi meta karakter whitespace atau shell dan (b) symlink yang rusak (bukan masalah dengan jika Anda hanya ingin jalur induk skrip yang berjalan, dengan asumsi (a) tidak berlaku).
mklement0
Jika Anda khawatir dengan whitespace gunakan tanda kutip.
Dave
Silakan lakukan - meskipun itu tidak akan membahas (b).
mklement0
Tolong tunjukkan saya contoh b) di mana ini gagal. Menurut definisi, symlink yang rusak menunjuk ke entri direktori yang tidak ada. Inti dari skrip ini adalah untuk menyelesaikan symlink ke arah lain. Jika symlink rusak, Anda tidak akan menjalankan skrip. Contoh ini dimaksudkan untuk menunjukkan penyelesaian skrip yang sedang dijalankan.
Dave
"dimaksudkan untuk menunjukkan penyelesaian skrip yang sedang dijalankan" - memang, yang merupakan penyempitan dari ruang lingkup pertanyaan yang telah Anda pilih untuk difokuskan; itu baik-baik saja, selama Anda mengatakannya. Karena Anda tidak melakukannya, saya menyatakannya dalam komentar saya. Harap perbaiki masalah penawaran, yang merupakan masalah terlepas dari ruang lingkup jawabannya.
mklement0
2

Catatan: Saya percaya ini menjadi solusi yang solid, portabel, siap pakai, yang selalu panjang untuk alasan itu.

Di bawah ini adalah script / fungsi yang sepenuhnya kompatibel dengan POSIX yang karena itu lintas platform (berfungsi pada macOS juga, yang readlinkmasih belum mendukung -fpada 10.12 (Sierra)) - ini hanya menggunakan fitur bahasa shell POSIX dan hanya panggilan utilitas yang sesuai dengan POSIX .

Ini adalah implementasi portabel dari GNUreadlink -e (versi yang lebih ketat readlink -f).

Anda dapat menjalankan skrip dengansh atau sumber yang fungsi dalam bash, kshdanzsh :

Misalnya, di dalam skrip Anda dapat menggunakannya sebagai berikut untuk mendapatkan direktori asli skrip yang menjalankan asalnya, dengan symlink yang diselesaikan:

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

rreadlink definisi skrip / fungsi:

Kode diadaptasi dengan rasa terima kasih dari jawaban ini .
Saya juga membuat bashversi utilitas yang berdiri sendiri di sini , yang dapat Anda instal
npm install rreadlink -g, jika Node.js telah diinstal.

#!/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 "$@"

Garis singgung tentang keamanan:

jarno , mengacu pada fungsi yang memastikan bahwa builtin commandtidak dibayangi oleh fungsi alias atau shell dengan nama yang sama, bertanya dalam komentar:

Bagaimana jika unaliasatau unsetdan [ditetapkan sebagai alias atau fungsi shell?

Motivasi di balik rreadlinkmemastikan bahwa commandmemiliki makna aslinya adalah menggunakannya untuk mem-bypass (jinak) alias kenyamanan dan fungsi yang sering digunakan untuk membayangi perintah standar dalam shell interaktif, seperti mendefinisikan ulangls untuk menyertakan opsi favorit.

Saya pikir aman untuk mengatakan bahwa kecuali Anda sedang berurusan dengan, lingkungan berbahaya tidak terpercaya, mengkhawatirkan unaliasatau unset- atau, dalam hal ini, while, do, ... - yang didefinisikan ulang tidak perhatian.

Ada sesuatu yang fungsi harus andalkan untuk memiliki makna dan perilaku aslinya - tidak ada jalan lain untuk itu.
Kerang seperti POSIX memungkinkan pendefinisian ulang builtin dan bahkan kata kunci bahasa secara inheren risiko keamanan (dan menulis kode paranoid sulit pada umumnya).

Untuk mengatasi masalah Anda secara khusus:

Fungsi ini bergantung pada unaliasdan unsetmemiliki makna aslinya. Setelah mereka didefinisikan ulang sebagai fungsi shell dengan cara yang mengubah perilaku mereka akan menjadi masalah; redefinisi sebagai alias tidak selalu menjadi perhatian, karena mengutip (bagian dari) nama perintah (misalnya, \unalias) mem-bypass alias.

Namun, mengutip adalah bukan pilihan bagi shell kata kunci ( while, for, if, do, ...) dan sementara kata kunci shell melakukan take didahulukan atas shell fungsi , di bashdan zshalias memiliki hak tertinggi, sehingga untuk menjaga terhadap redefinitions shell-kata kunci Anda harus menjalankan unaliasdengan nama mereka (meskipun dalam shell non-interaktif bash (seperti skrip) alias tidak diperluas secara default - hanya jika shopt -s expand_aliasessecara eksplisit disebut pertama).

Untuk memastikan bahwa unalias- sebagai builtin - memiliki makna aslinya, Anda harus menggunakannya \unsetterlebih dahulu, yang mengharuskan yang unsetmemiliki makna aslinya:

unsetadalah shell builtin , jadi untuk memastikan bahwa itu dipanggil seperti itu, Anda harus memastikan bahwa itu sendiri tidak didefinisikan ulang sebagai suatu fungsi . Meskipun Anda bisa mem-bypass formulir alias dengan mengutip, Anda tidak bisa mem-bypass formulir fungsi shell - catch 22.

Jadi, kecuali Anda dapat mengandalkan unsetuntuk memiliki makna aslinya, dari apa yang dapat saya katakan, tidak ada cara yang pasti untuk mempertahankan diri dari semua definisi-ulang berbahaya.

mklement0
sumber
Dalam pengalaman saya, mengutip memotong alias, bukan fungsi shell, tidak seperti yang Anda katakan pertama kali.
jarno
Saya menguji bahwa saya dapat mendefinisikan [sebagai alias di dashdan bashdan sebagai fungsi shell di bash.
jarno
1
Saya menjadikan poin saya sebagai pertanyaan terpisah .
jarno
@jarno: Poin bagus; Saya telah memperbarui jawaban saya; beri tahu saya jika menurut Anda masih ada masalah.
mklement0
1
@jarno: Anda dapat mendefinisikan whilesebagai fungsi di bash,, kshdan zsh(tetapi tidak dash), tetapi hanya dengan function <name>sintaks: function while { echo foo; }berfungsi ( while() { echo foo; }tidak). Namun, ini tidak akan membayangi while kata kunci , karena kata kunci memiliki prioritas lebih tinggi daripada fungsi (satu-satunya cara untuk memanggil fungsi ini adalah sebagai \while). Di bashdan zsh, alias memiliki prioritas lebih tinggi dari kata kunci, jadi alias redefinisi kata kunci memang membayangi mereka (tetapi bashsecara default hanya di shell interaktif , kecuali jika shopt -s expand_aliasessecara eksplisit disebut).
mklement0
1

Script shell umum sering harus menemukan direktori "home" mereka walaupun mereka dipanggil sebagai symlink. Jadi skrip harus menemukan posisi "asli" mereka hanya dari $ 0.

cat `mvn`

pada sistem saya mencetak skrip yang berisi berikut ini, yang seharusnya menjadi petunjuk yang baik pada apa yang Anda butuhkan.

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`
Hugo
sumber
1

Coba ini:

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

Karena saya telah mengalami ini berkali-kali selama bertahun-tahun, dan kali ini saya membutuhkan versi portabel bash murni yang dapat saya gunakan di OSX dan linux, saya melanjutkan dan menulis satu:

Versi hidup tinggal di sini:

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

tetapi demi SO, inilah versi saat ini (saya merasa sudah teruji dengan baik..tapi saya terbuka untuk umpan balik!)

Mungkin tidak sulit untuk membuatnya bekerja untuk shell bourne biasa (sh), tapi saya tidak mencoba ... Saya suka $ FUNCNAME terlalu banyak. :)

#!/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
}

inilah contoh klasik, terima kasih untuk menyeduh:

%% 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

gunakan fungsi ini dan itu akan mengembalikan jalur -real-:

%% 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 
tajam
sumber
Jawaban saya sebelumnya melakukan hal yang persis sama di sekitar 1/4 ruang :) Ini cukup dasar skrip shell dan tidak layak git repo.
Dave
1

Untuk mengatasi ketidakcocokan Mac, saya datang dengan

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

Tidak hebat selain cross OS

Clemens Tolboom
sumber
3
Python 2.6+ tersedia di lebih banyak sistem pengguna akhir daripada php, jadi python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"mungkin akan lebih masuk akal. Namun, pada saat Anda perlu menggunakan Python dari skrip shell, Anda mungkin harus menggunakan Python dan menyelamatkan diri Anda dari sakit kepala skrip lintas platform silang.
Jonathan Baldwin
Anda tidak memerlukan apa pun selain readlink, dirname, dan nama file bawaan bawaan.
Dave
@Dave: dirname,, basenamedan readlinkmerupakan utilitas eksternal , bukan shell built-in; dirnamedan basenamemerupakan bagian dari POSIX, readlinkbukan.
mklement0
@ mklement0 - Anda benar. Mereka disediakan oleh CoreUtils atau yang setara. Saya tidak boleh mengunjungi SO setelah jam 1 pagi. Inti dari komentar saya adalah bahwa tidak diperlukan PHP atau juru bahasa lain selain yang diinstal dalam sistem basis. Saya telah menggunakan skrip yang saya berikan di halaman ini pada setiap varian linux sejak 1997 dan MacOS X sejak 2006 tanpa kesalahan. OP tidak meminta solusi POSIX. Lingkungan spesifik mereka adalah Mac OS X.
Dave
@ Dave: Ya, itu adalah mungkin untuk melakukannya dengan utilitas saham, tetapi juga sulit untuk melakukannya (yang dibuktikan dengan kekurangan naskah Anda). Jika OS X benar-benar fokus, maka jawaban ini baik-baik saja - dan jauh lebih sederhana - mengingat yang phpdatang dengan OS X. Namun, meskipun tubuh pertanyaan menyebutkan OS X, itu tidak ditandai seperti itu, dan menjadi jelas bahwa orang-orang di berbagai platform datang ke sini untuk mendapatkan jawaban, jadi ada baiknya menunjukkan apa platform-spesifik / non-POSIX.
mklement0
1

Ini adalah penyelesai symlink di Bash yang berfungsi apakah tautannya adalah direktori atau bukan-direktori:

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

Perhatikan bahwa semua cddan sethal-hal terjadi di subkulit.

solidsnack
sumber
Sebenarnya, {} di sekitar () tidak perlu, karena () dianggap sebagai "perintah majemuk", sama seperti {}. Tetapi Anda masih perlu () setelah nama fungsi.
Chris Cogdon
@ ChrisCogdon The {}sekitar ()tidak perlu jika tidak ada di ()belakang nama fungsi. Bash menerima deklarasi fungsi tanpa ()karena shell tidak memiliki daftar parameter dalam deklarasi dan tidak melakukan panggilan dengan ()sehingga deklarasi fungsi dengan ()tidak masuk akal.
solidsnack
0

Di sini saya menyajikan apa yang saya yakini sebagai solusi lintas platform (Linux dan setidaknya MacOS) untuk jawaban yang bekerja dengan baik untuk saya saat ini.

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

Berikut ini adalah solusi macOS (hanya?). Mungkin lebih cocok untuk pertanyaan awal.

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
}
RuneImp
sumber
0

Jawaban saya di sini Bash: bagaimana cara mendapatkan jalur symlink yang sebenarnya?

tetapi singkatnya sangat berguna dalam skrip:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

Ini adalah bagian dari GNU coreutils, cocok untuk digunakan dalam sistem Linux.

Untuk menguji semuanya, kami menaruh symlink ke / home / test2 /, mengubah beberapa hal tambahan dan menjalankan / memanggilnya dari direktori root:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

Dimana

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
Arunas Bartisius
sumber
0

Jika pwd tidak dapat digunakan (mis. Memanggil skrip dari lokasi lain), gunakan realpath (dengan atau tanpa dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Berfungsi baik saat memanggil melalui (banyak) symlink (s) atau ketika langsung memanggil skrip - dari lokasi mana pun.

nakwa
sumber