Bagaimana Anda menormalkan jalur file di Bash?

204

Saya ingin mengubah /foo/bar/..ke/foo

Apakah ada perintah bash yang melakukan ini?


Sunting: dalam kasus praktis saya, direktori memang ada.

Fabien
sumber
4
Apakah itu penting jika /foo/baratau bahkan /foobenar - benar ada, atau apakah Anda hanya tertarik pada aspek manipulasi string sesuai dengan aturan nama jalur?
Chen Levy
5
@walwal ... itu agak dibuat-buat ...
Camilo Martin
2
@CamiloMartin Tidak dibikin sama sekali - itu tidak persis apa pertanyaan meminta - transformasi /foo/bar/..ke /foo, dan menggunakan bashperintah. Jika ada persyaratan lain yang tidak disebutkan, maka mungkin seharusnya ...
twalberg
14
@twalberg Anda telah melakukan terlalu banyak TDD -_- '
Camilo Martin

Jawaban:

193

jika Anda ingin mengambil bagian dari nama file dari path, "dirname" dan "basename" adalah teman Anda, dan "realpath" juga berguna.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath alternatif

Jika realpathtidak didukung oleh shell Anda, Anda dapat mencoba

readlink -f /path/here/.. 

Juga

readlink -m /path/there/../../ 

Bekerja sama dengan

realpath -s /path/here/../../

karena jalan itu tidak perlu ada untuk dinormalisasi.

Kent Fredric
sumber
5
Bagi Anda yang membutuhkan solusi OS X, lihat jawaban Adam Liss di bawah ini.
Trenton
stackoverflow.com/a/17744637/999943 Ini adalah jawaban yang sangat terkait! Saya menemukan kedua posting QA ini dalam satu hari, dan saya ingin menghubungkan mereka bersama.
phyatt
realpath tampaknya telah ditambahkan ke coreutils pada tahun 2012. Lihat riwayat file di github.com/coreutils/coreutils/commits/master/src/realpath.c .
Noah Lavine
1
Keduanya realpathdan readlinkberasal dari utilitas inti GNU, jadi kemungkinan besar Anda mendapatkan keduanya atau tidak sama sekali. Jika saya ingat dengan benar, versi readlink pada Mac sedikit berbeda dari GNU: - \
Antoine 'hashar' Musso
100

Saya tidak tahu apakah ada perintah bash langsung untuk melakukan ini, tetapi biasanya saya lakukan

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

dan itu bekerja dengan baik.

Tim Whitcomb
sumber
9
Ini akan menormalkan tetapi tidak menyelesaikan tautan lunak. Ini bisa berupa bug atau fitur. :-)
Adam Liss
4
Ini juga memiliki masalah jika $ CDPATH didefinisikan; karena "cd foo" akan beralih ke direktori "foo" yang merupakan subdirektori dari $ CDPATH, bukan hanya "foo" yang ada di direktori saat ini. Saya pikir Anda perlu melakukan sesuatu seperti: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P.
mjs
7
Jawaban Tim jelas yang paling sederhana dan paling portabel. CDPATH mudah ditangani: dir = "$ (tidak disetel CDPATH && cd" $ dir "&& pwd)"
David Blevins
2
Ini bisa sangat berbahaya ( rm -rf $normalDir) jika dirToNormalisasi tidak ada!
Frank Meulenaar
4
Ya, mungkin sebaiknya menggunakan &&komentar per @ DavidBlevins.
Elias Dorneles
54

Coba realpath. Di bawah ini adalah sumber secara keseluruhan, dengan ini disumbangkan ke domain publik.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}
Adam Liss
sumber
1
Apakah ini termasuk sebagai bagian dari instalasi standar bash? Saya mendapatkan "perintah tidak ditemukan" di sistem kami (bash 3.00.15 (1) di RHEL)
Tim Whitcomb
4
gnu.org/software/coreutils untuk readlink, tetapi realpath berasal dari paket ini: packages.debian.org/unstable/utils/realpath
Kent Fredric
8
Ini standar di ubuntu / BSD, bukan Centos / OSX
Erik Aronesty
4
Anda harus benar-benar menyerang bagian dengan "Bonus: itu perintah bash, dan tersedia di mana-mana!" berpisah tentang realpath. Itu juga kebetulan menjadi favorit saya, tetapi alih-alih mengkompilasi alat Anda dari sumber. Ini semua untuk kelas berat, dan sebagian besar waktu yang Anda inginkan readlink -f... BTW, readlinkjuga BUKAN bash builtin, tetapi bagian dari coreutilsUbuntu.
Tomasz Gandor
4
Anda mengatakan Anda menyumbangkan ini ke domain publik, tetapi tajuknya juga mengatakan "untuk penggunaan nirlaba" - apakah Anda benar-benar bermaksud menyumbangkannya ke domain publik? Itu berarti dapat digunakan untuk tujuan nirlaba komersial maupun untuk nirlaba ...
me_and
36

Solusi portabel dan dapat diandalkan adalah dengan menggunakan python, yang sudah terinstal cukup banyak di mana-mana (termasuk Darwin). Anda memiliki dua opsi:

  1. abspath mengembalikan jalur absolut tetapi tidak menyelesaikan symlink:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath mengembalikan jalur absolut dan dengan itu menyelesaikan symlink, menghasilkan jalur kanonik:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

Dalam setiap kasus, path/to/filedapat berupa jalur relatif atau absolut.

loevborg
sumber
7
Terima kasih, hanya itu yang berhasil. readlink atau realpath tidak tersedia di OS X. Python seharusnya ada di sebagian besar platform.
sorin
1
Hanya untuk memperjelas, readlinktersedia di OS X, hanya saja tidak dengan -fopsi. Solusi portabel dibahas di sini .
StvnW
3
Ini benar-benar mengejutkan pikiran saya bahwa ini adalah satu-satunya solusi yang waras jika Anda tidak ingin mengikuti tautan. Cara Unix KAKI saya.
DannoHung
34

Gunakan utilitas readlink dari paket coreutils.

MY_PATH=$(readlink -f "$0")
mattalxndr
sumber
1
BSD tidak memiliki flag -f, yang berarti bahwa ini akan gagal bahkan pada MacOS Mojave terbaru dan banyak sistem lainnya. Lupakan menggunakan -f jika Anda ingin mudah dibawa, banyak OS yang terpengaruh.
sorin
1
@sorin. Pertanyaannya bukan tentang Mac, ini tentang Linux.
mattalxndr
14

readlinkadalah standar bash untuk mendapatkan jalur absolut. Ini juga memiliki keuntungan mengembalikan string kosong jika jalur atau jalur tidak ada (diberi bendera untuk melakukannya).

Untuk mendapatkan jalur absolut ke direktori yang mungkin atau mungkin tidak ada, tetapi siapa orang tuanya ada, gunakan:

abspath=$(readlink -f $path)

Untuk mendapatkan jalur absolut ke direktori yang harus ada bersama semua orang tua:

abspath=$(readlink -e $path)

Untuk membuat kanonikalisasi jalur yang diberikan dan mengikuti symlink jika kebetulan ada, tetapi jika tidak abaikan direktori yang hilang dan tetap kembalikan jalurnya, itu adalah:

abspath=$(readlink -m $path)

Satu-satunya downside adalah bahwa readlink akan mengikuti tautan. Jika Anda tidak ingin mengikuti tautan, Anda dapat menggunakan konvensi alternatif ini:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

Itu akan chdir ke bagian direktori $ path dan mencetak direktori saat ini bersama dengan bagian file $ path. Jika gagal chdir, Anda mendapatkan string kosong dan kesalahan pada stderr.

Craig
sumber
7
readlinkadalah pilihan yang baik jika tersedia. Versi OS X tidak mendukung opsi -eatau -f. Dalam tiga contoh pertama, Anda harus memiliki tanda kutip ganda $pathuntuk menangani spasi atau wildcard dalam nama file. +1 untuk ekspansi parameter, tetapi ini memiliki kerentanan keamanan. Jika pathkosong, ini akan cdke direktori home Anda. Anda perlu tanda kutip ganda. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot
Ini hanya sebuah contoh. Jika Anda benar-benar mengandalkan keamanan, maka Anda benar-benar tidak boleh menggunakan bash atau varian shell lainnya sama sekali. Juga, bash memiliki masalah sendiri dalam hal kompatibilitas lintas platform, serta memiliki masalah dengan perubahan fungsionalitas antara versi utama. OSX hanyalah salah satu dari banyak platform dengan masalah yang berkaitan dengan skrip shell, belum lagi didasarkan pada BSD. Ketika Anda harus benar-benar multi platform, Anda harus mematuhi POSIX, jadi ekspansi parameter benar-benar keluar dari jendela. Lihatlah Solaris atau HP-UX beberapa saat.
Craig
6
Tidak ada maksud pelanggaran di sini, tetapi menunjukkan masalah yang tidak jelas seperti ini adalah penting. Saya hanya ingin jawaban cepat untuk masalah sepele ini dan saya akan percaya kode itu dengan semua / semua masukan jika bukan karena komentar di atas. Penting juga untuk mendukung OS-X dalam diskusi bash ini. Ada banyak perintah yang sayangnya tidak didukung pada OS-X, dan banyak forum menerima begitu saja ketika membahas Bash yang berarti kita akan terus mendapatkan banyak masalah lintas-platform kecuali itu ditangani lebih cepat daripada nanti.
Rebs
13

Pertanyaan lama, tetapi ada cara yang lebih sederhana jika Anda berurusan dengan nama path lengkap di level shell:

   abspath = "$ (cd" $ path "&& pwd)"

Ketika cd terjadi dalam sebuah subshell itu tidak mempengaruhi skrip utama.

Dua variasi, seandainya perintah bawaan shell Anda menerima -L dan -P, adalah:

   abspath = "$ (cd -P" $ path "&& pwd -P)" # path fisik dengan symlink yang diselesaikan
   abspath = "$ (cd -L" $ path "&& pwd -L)" # jalur logis yang mempertahankan symlink

Secara pribadi, saya jarang membutuhkan pendekatan nanti kecuali saya terpesona dengan tautan simbolis untuk beberapa alasan.

FYI: variasi untuk mendapatkan direktori awal skrip yang berfungsi meskipun skrip berubah direktori saat ini di kemudian hari.

name0 = "$ (basename" $ ​​0 ")"; #basis nama skrip
dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #memulai direct dir

Penggunaan CD memastikan Anda selalu memiliki direktori absolut, bahkan jika skrip dijalankan oleh perintah seperti ./script.sh yang, tanpa cd / pwd, sering memberikan hanya .. Tidak berguna jika skrip melakukan cd nanti.

Gilbert
sumber
8

Seperti yang dikatakan Adam Liss realpathtidak dibundel dengan setiap distribusi. Yang memalukan, karena itu adalah solusi terbaik. Kode sumber yang disediakan sangat bagus, dan saya mungkin akan mulai menggunakannya sekarang. Inilah yang saya gunakan sampai sekarang, yang saya bagikan di sini hanya untuk kelengkapan:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

Jika Anda ingin menyelesaikan symlink, ganti saja pwddengan pwd -P.

Jeet
sumber
Satu gotcha dengan pwd -Popsi untuk kasus ini ... Pertimbangkan apa yang akan terjadi jika $(basename "$1")symlink ke file di direktori lain. Satu- pwd -Psatunya menyelesaikan symlink di bagian direktori path, tetapi tidak bagian basename.
toxalot
7

Solusi terakhir saya adalah:

pushd foo/bar/..
dir=`pwd`
popd

Berdasarkan jawaban Tim Whitcomb.

orang bodoh
sumber
Saya menduga ini gagal jika argumennya bukan direktori. Misalkan saya ingin tahu ke mana / usr / bin / java mengarah ke?
Edward Falk
1
Jika Anda tahu itu file, Anda bisa pushd $(dirname /usr/bin/java)mencobanya.
schmunk
5

Bukan jawaban yang tepat tetapi mungkin pertanyaan lanjutan (pertanyaan awal tidak eksplisit):

readlinktidak apa-apa jika Anda benar-benar ingin mengikuti symlink. Tetapi ada juga kasus penggunaan hanya untuk menormalkan ./dan ../dan //urutan, yang dapat dilakukan secara sintaksis murni, tanpa mengaitkan symlinks. readlinktidak baik untuk ini, dan juga tidak realpath.

for f in $paths; do (cd $f; pwd); done

bekerja untuk jalur yang ada, tetapi istirahat untuk orang lain.

Sebuah sedskrip tampaknya menjadi taruhan yang bagus, kecuali bahwa Anda tidak dapat secara iteratif mengganti urutan ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) tanpa menggunakan sesuatu seperti Perl, yang tidak aman untuk diasumsikan pada semua sistem, atau menggunakan beberapa loop jelek untuk membandingkan output dari sedke inputnya.

FWIW, one-liner menggunakan Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths
Jesse Glick
sumber
realpathmemiliki -sopsi untuk tidak menyelesaikan tautan simbolik dan hanya menyelesaikan referensi /./, /../dan menghapus /karakter tambahan . Ketika dikombinasikan dengan -mopsi, realpathhanya beroperasi pada nama file, dan tidak menyentuh file yang sebenarnya. Ini terdengar seperti solusi yang sempurna. Namun sayang, realpathmasih banyak yang hilang pada sistem.
toxalot
Menghapus ..komponen tidak dapat dilakukan secara sintaksis ketika symlinks terlibat. /one/two/../threetidak sama dengan /one/threejika twosymlink ke /foo/bar.
jrw32982 mendukung Monica
@ jrw32982 ya seperti yang saya katakan dalam tanggapan saya ini untuk kasus penggunaan ketika kanonikisasi symlink tidak diinginkan atau dibutuhkan.
Jesse Glick
@ JesseGlick itu bukan hanya masalah apakah Anda ingin mengkanoniskan symlink atau tidak. Algoritme Anda sebenarnya menghasilkan jawaban yang salah. Agar jawaban Anda benar, Anda harus tahu apriori bahwa tidak ada symlink yang terlibat (atau mereka hanya dari bentuk tertentu). Jawaban Anda mengatakan bahwa Anda tidak ingin mengkanoniskannya, bukan karena tidak ada symlink yang terlibat di jalur.
jrw32982 mendukung Monica
Ada kasus penggunaan di mana normalisasi harus dilakukan tanpa mengasumsikan struktur direktori yang sudah ada dan sudah diperbaiki. Normalisasi URI serupa. Dalam kasus ini, ini adalah batasan yang melekat bahwa hasilnya umumnya tidak akan benar jika kebetulan ada symlink di dekat direktori di mana hasilnya nanti diterapkan.
Jesse Glick
5

Saya terlambat ke pesta, tapi ini solusi yang saya buat setelah membaca banyak utas seperti ini:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

Ini akan menyelesaikan jalur absolut $ 1, bermain bagus dengan ~, menjaga symlink di jalur tempat mereka berada, dan itu tidak akan mengacaukan dengan tumpukan direktori Anda. Ini mengembalikan jalur penuh atau tidak sama sekali jika tidak ada. Ia mengharapkan $ 1 menjadi direktori dan mungkin akan gagal jika tidak, tapi itu pemeriksaan yang mudah untuk dilakukan sendiri.

apottere
sumber
4

Banyak bicara, dan agak terlambat menjawab. Saya perlu menulis satu karena saya terjebak pada RHEL4 / 5 yang lebih tua. Saya menangani tautan absolut dan relatif, dan menyederhanakan entri //, /./ dan somedir /../.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`
alhernau
sumber
3

Coba produk perpustakaan Bash baru kami realpath-lib yang telah kami tempatkan di GitHub secara gratis dan tidak terbebani. Ini sepenuhnya didokumentasikan dan menjadi alat pembelajaran yang hebat.

Ini menyelesaikan jalur lokal, relatif dan absolut dan tidak memiliki dependensi kecuali Bash 4+; jadi itu harus bekerja di mana saja. Gratis, bersih, sederhana, dan instruktif.

Anda dapat melakukan:

get_realpath <absolute|relative|symlink|local file path>

Fungsi ini adalah inti dari perpustakaan:

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

}

Ini juga berisi fungsi untuk get_dirname, get_filename, get_ stemname dan validate_path. Cobalah di seluruh platform, dan bantu untuk memperbaikinya.

AsymLabs
sumber
2

Masalahnya realpathadalah bahwa itu tidak tersedia pada BSD (atau OSX dalam hal ini). Ini adalah resep sederhana yang diambil dari artikel yang agak lama (2009) dari Linux Journal , yang cukup portabel:

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Perhatikan varian ini juga tidak membutuhkan jalur untuk ada.

André Anjos
sumber
Namun ini tidak menyelesaikan symlink. Realpath memproses jalur yang dimulai pada root dan mengikuti symlink saat proses berlangsung. Semua ini dilakukan adalah runtuh referensi orang tua.
Martijn Pieters
2

Berdasarkan jawaban @ Andre, saya mungkin memiliki versi yang sedikit lebih baik, kalau-kalau ada seseorang yang mencari solusi berbasis manipulasi string yang bebas loop. Ini juga berguna bagi mereka yang tidak ingin melakukan dereferensi symlink, yang merupakan kelemahan dari penggunaan realpathatau readlink -f.

Ini bekerja pada versi bash 3.2.25 dan lebih tinggi.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config
ϹοδεMεδιϲ
sumber
Ini adalah ide yang bagus, tetapi saya menyia-nyiakan waktu 20 menit untuk membuatnya bekerja pada berbagai versi bash yang berbeda. Ternyata opsi shell extglob perlu dihidupkan agar ini berfungsi, dan itu tidak secara default. Ketika datang ke fungsi bash, penting untuk menentukan versi yang diperlukan dan opsi non-default karena detail ini dapat bervariasi di antara OS. Sebagai contoh, versi terbaru dari Mac OSX (Yosemite) hanya hadir dengan versi bash yang lama (3.2).
drwatsoncode
Maaf @ricovox; Saya sekarang telah memperbarui itu. Saya ingin mengetahui versi persis Bash yang Anda miliki di sana. Rumus di atas (diperbarui) bekerja pada CentOS 5.8 yang dilengkapi dengan bash 3.2.25
12οδεMεδιϲ
Maaf bila membingungkan. Kode ini DID berfungsi pada versi Mac OSX saya bash (3.2.57) setelah saya mengaktifkan ekstglob. Catatan saya tentang versi bash lebih umum (yang sebenarnya lebih banyak berlaku untuk jawaban lain di sini mengenai regex di bash).
drwatsoncode
2
Saya menghargai jawaban Anda. Saya menggunakannya sebagai basis untuk saya sendiri. Kebetulan saya perhatikan beberapa kasus di mana milik Anda gagal: (1) Jalur relatif hello/../world(2) Titik dalam nama file /hello/..world(3) Titik setelah tebasan ganda (4) Titik /hello//../worldsebelum atau sesudah tebasan ganda /hello//./worldatau /hello/.//world (5) Induk setelah saat ini: /hello/./../world/ (6) Induk setelah Induk:, /hello/../../worlddll. - Beberapa di antaranya dapat diperbaiki dengan menggunakan loop untuk melakukan koreksi sampai jalur berhenti berubah. (Hapus juga dir/../, /dir/..tapi hapus dir/..dari akhir.)
drwatsoncode
0

Berdasarkan potongan python loveborg yang sangat baik, saya menulis ini:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done
Edward Falk
sumber
0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

Ini berfungsi bahkan jika file tidak ada. Itu memang membutuhkan direktori yang berisi file untuk ada.

pengguna240515
sumber
GNU Realpath juga tidak memerlukan elemen terakhir di path, kecuali Anda menggunakanrealpath -e
Martijn Pieters
0

Saya membutuhkan solusi yang akan melakukan ketiganya:

  • Bekerja pada Mac stok. realpathdan readlink -faddons
  • Selesaikan symlinks
  • Memiliki penanganan kesalahan

Tidak ada jawaban yang memiliki # 1 dan # 2. Saya menambahkan # 3 untuk menyelamatkan orang lain dari bercukur yak lebih lanjut.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Berikut ini adalah test case pendek dengan beberapa ruang bengkok di jalur untuk sepenuhnya menggunakan kutipan

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "
David Blevins
sumber
0

Saya tahu ini adalah pertanyaan kuno. Saya masih menawarkan alternatif. Baru-baru ini saya bertemu dengan masalah yang sama dan tidak menemukan perintah yang ada dan portabel untuk melakukan itu. Jadi saya menulis skrip shell berikut yang mencakup fungsi yang dapat melakukan trik.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c

BestOfSong
sumber
0

Saya membuat fungsi builtin-only untuk menangani ini dengan fokus pada kinerja setinggi mungkin (untuk bersenang-senang). Itu tidak menyelesaikan symlink, jadi pada dasarnya sama dengan realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Catatan: Fungsi ini tidak menangani lintasan yang dimulai dengan //, karena tepat dua garis miring ganda pada awal lintasan adalah perilaku yang ditentukan implementasi. Namun, menangani /, ///dan sebagainya baik-baik saja.

Fungsi ini tampaknya menangani semua kasus tepi dengan benar, tetapi mungkin masih ada beberapa di luar sana yang belum saya tangani.

Catatan Kinerja: ketika dipanggil dengan ribuan argumen, abspathberjalan sekitar 10x lebih lambat dari realpath -sm; ketika dipanggil dengan satu argumen,abspath berjalan> 110x lebih cepat dari realpath -smpada mesin saya, sebagian besar karena tidak perlu menjalankan program baru setiap kali.

Jeffrey Cash
sumber
-1

Saya menemukan hari ini bahwa Anda dapat menggunakan stat perintah untuk menyelesaikan jalur.

Jadi untuk direktori seperti "~ / Documents":

Anda dapat menjalankan ini:

stat -f %N ~/Documents

Untuk mendapatkan path lengkap:

/Users/me/Documents

Untuk symlink, Anda dapat menggunakan opsi format% Y:

stat -f %Y example_symlink

Yang mungkin mengembalikan hasil seperti:

/usr/local/sbin/example_symlink

Opsi pemformatan mungkin berbeda pada versi lain * NIX tetapi ini bekerja untuk saya di OSX.

coldlogic
sumber
1
yang stat -f %N ~/Documentsgaris adalah ikan merah ... shell Anda mengganti ~/Documentsdengan /Users/me/Documents, dan stathanya mencetak argumen verbatim.
danwyand
-4

Solusi sederhana menggunakan node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Artisan72
sumber