Contoh:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
Bagaimana cara membuat keajaiban (semoga kode tidak terlalu rumit ...)?
bash
shell
path
relative-path
absolute-path
Paul Tarjan
sumber
sumber
realpath --relative-to=$absolute $current
.Jawaban:
Menggunakan realpath dari GNU coreutils 8.23 adalah yang paling sederhana, saya pikir:
Sebagai contoh:
sumber
$ realpath --relative-to="${PWD}" "$file"
berguna jika Anda ingin jalur relatif ke direktori kerja saat ini./usr/bin/nmap/
-path tetapi tidak untuk/usr/bin/nmap
: darinmap
ke/tmp/testing
itu hanya../../
dan tidak 3 kali../
. Namun itu berhasil, karena melakukan..
di rootfs adalah/
.--relative-to=…
mengharapkan direktori dan TIDAK MEMERIKSA. Itu berarti bahwa Anda berakhir dengan "../" tambahan jika Anda meminta path relatif ke file (seperti contoh ini tampaknya dilakukan, karena/usr/bin
jarang atau tidak pernah berisi direktori dannmap
biasanya biner)memberi:
sumber
relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'
bekerja paling alami dan andal.Ini adalah perbaikan yang diperbaiki dan berfungsi penuh dari solusi berperingkat terbaik saat ini dari @pini (yang sayangnya hanya menangani beberapa kasus)
Pengingat: tes '-z' jika string adalah panjang nol (= kosong) dan tes '-n' jika string tidak kosong.
Kasus uji:
sumber
source=$1; target=$2
dengansource=$(realpath $1); target=$(realpath $2)
realpath
dianjurkan, atausource=$(readlink -f $1)
dll. Jika jalan setapak tidak tersedia (bukan standar)$source
dan$target
menyukai ini: `if [[-e $ 1]]; lalu source = $ (readlink -f $ 1); sumber lain = $ 1; fi jika [[-e $ 2]]; lalu target = $ (readlink -f $ 2); selain itu target = $ 2; Dengan cara itu, fungsi tersebut dapat menangani jalur relatif yang ada / nyata serta direktori fiksi.readlink
memiliki-m
opsi yang hanya melakukan itu;)sumber
Ini dibangun untuk Perl sejak tahun 2001, sehingga bekerja pada hampir setiap sistem yang dapat Anda bayangkan, bahkan VMS .
Juga, solusinya mudah dimengerti.
Jadi untuk contoh Anda:
... akan bekerja dengan baik.
sumber
say
belum tersedia dalam perl untuk log, tetapi dapat digunakan secara efektif di sini.perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"
danperl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"
. Skrip perl satu baris pertama menggunakan satu argumen (asal adalah direktori kerja saat ini). Skrip perl satu baris kedua menggunakan dua argumen.perl
dapat ditemukan hampir di mana-mana, meskipun jawabannya masih satu baris.Menganggap bahwa Anda telah menginstal: bash, pwd, dirname, echo; lalu relpath adalah
Saya telah mendapatkan jawaban dari pini dan beberapa ide lainnya
Catatan : Ini membutuhkan kedua jalur untuk menjadi folder yang ada. File tidak akan berfungsi.
sumber
Python
os.path.relpath
sebagai fungsi shellTujuan dari
relpath
latihan ini adalah untuk meniruos.path.relpath
fungsi Python 2.7 (tersedia dari Python versi 2.6 tetapi hanya berfungsi dengan baik di 2.7), seperti yang diusulkan oleh xni . Akibatnya, beberapa hasil mungkin berbeda dari fungsi yang disediakan dalam jawaban lain.(Saya belum menguji dengan jalur baru di jalur hanya karena itu memecah validasi berdasarkan panggilan
python -c
dari ZSH. Itu pasti akan mungkin dengan beberapa upaya.)Mengenai "sihir" di Bash, saya sudah menyerah mencari sihir di Bash sejak lama, tetapi sejak itu saya menemukan semua sihir yang saya butuhkan, dan kemudian beberapa, di ZSH.
Akibatnya, saya mengusulkan dua implementasi.
Implementasi pertama bertujuan untuk sepenuhnya mematuhi POSIX . Saya telah mengujinya dengan
/bin/dash
di Debian 6.0.6 "Squeeze". Ini juga berfungsi dengan baik/bin/sh
pada OS X 10.8.3, yang sebenarnya adalah Bash versi 3.2 yang berpura-pura menjadi shell POSIX.Implementasi kedua adalah fungsi shell ZSH yang kuat terhadap beberapa garis miring dan gangguan lainnya di jalur. Jika Anda memiliki ZSH tersedia, ini adalah versi yang disarankan, bahkan jika Anda memanggilnya dalam bentuk skrip yang disajikan di bawah ini (yaitu dengan shebang of
#!/usr/bin/env zsh
) dari shell lain.Akhirnya, saya telah menulis skrip ZSH yang memverifikasi output dari
relpath
perintah yang ditemukan dalam$PATH
memberikan test case yang disediakan dalam jawaban lain. Saya menambahkan beberapa bumbu pada tes tersebut dengan menambahkan beberapa spasi, tab, dan tanda baca seperti! ? *
di sana-sini dan juga melemparkan tes lain dengan karakter UTF-8 eksotis yang ditemukan di vim-powerline .Fungsi shell POSIX
Pertama, fungsi shell yang sesuai dengan POSIX. Ini bekerja dengan berbagai jalur, tetapi tidak membersihkan beberapa garis miring atau menyelesaikan symlink.
Fungsi shell ZSH
Sekarang,
zsh
versi yang lebih kuat . Jika Anda ingin menyelesaikan argumen ke jalur nyata à larealpath -f
(tersedia dalamcoreutils
paket Linux ), ganti:a
baris on 3 dan 4 dengan:A
.Untuk menggunakan ini di zsh, hapus baris pertama dan terakhir dan letakkan di direktori yang ada di
$FPATH
variabel Anda .Skrip uji
Akhirnya, skrip uji. Ia menerima satu opsi, yaitu
-v
untuk mengaktifkan keluaran verbose.sumber
/
aku takut.Skrip shell di atas terinspirasi oleh pini (Terima kasih!) Ini memicu bug dalam modul penyorotan sintaks Stack Overflow (setidaknya dalam bingkai pratinjau saya). Jadi tolong abaikan jika penyorotan salah.
Beberapa catatan:
Kecuali untuk urutan backslash yang disebutkan, baris terakhir dari fungsi "relPath" menghasilkan pathnames yang kompatibel dengan python:
Baris terakhir dapat diganti (dan disederhanakan) dengan baris
Saya lebih suka yang terakhir karena
Nama file dapat ditambahkan secara langsung ke jalur yang diperoleh oleh relPath, misalnya:
Tautan simbolik dalam direktori yang sama yang dibuat dengan metode ini tidak memiliki yang jelek
"./"
untuk nama file.Daftar kode untuk uji regresi (cukup tambahkan ke skrip shell):
sumber
Tidak banyak jawaban di sini yang praktis untuk digunakan setiap hari. Karena sangat sulit untuk melakukan ini dengan benar dalam bash murni, saya menyarankan solusi yang andal berikut ini (mirip dengan satu saran yang terkubur dalam komentar):
Kemudian, Anda bisa mendapatkan jalur relatif berdasarkan direktori saat ini:
atau Anda dapat menentukan bahwa path relatif ke direktori yang diberikan:
Kerugiannya adalah ini membutuhkan python, tetapi:
Perhatikan bahwa solusi yang memasukkan
basename
ataudirname
mungkin tidak selalu lebih baik, karena mereka mengharuskancoreutils
untuk diinstal. Jika seseorang memilikibash
solusi murni yang dapat diandalkan dan sederhana (daripada rasa ingin tahu yang berbelit-belit), saya akan terkejut.sumber
Skrip ini memberikan hasil yang benar hanya untuk input yang jalur absolut atau jalur relatif tanpa
.
atau..
:sumber
Saya hanya akan menggunakan Perl untuk tugas yang tidak terlalu sepele ini:
sumber
perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"
agar absolut dan terkini dikutip.relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current")
. Ini memastikan nilai tidak bisa, sendiri, berisi kode perl!Sedikit perbaikan pada jawaban kasku dan Pini , yang memainkan lebih baik dengan spasi dan memungkinkan melewati jalur relatif:
sumber
test.sh:
Pengujian:
sumber
readlink
di$(pwd)
.Namun solusi lain, pure
bash
+ GNUreadlink
agar mudah digunakan dalam konteks berikut:Ini berfungsi di hampir semua Linux saat ini. Jika
readlink -m
tidak berhasil di sisi Anda, cobareadlink -f
saja. Lihat juga https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 untuk kemungkinan pembaruan:Catatan:
*
atau?
.ln -s
:relpath / /
memberi.
dan bukan string kosongrelpath a a
memberia
, bahkan jikaa
kebetulan direktorireadlink
itu diperlukan untuk mengkanonik jalur.readlink -m
itu berfungsi untuk jalur yang belum ada juga.Pada sistem lama, di mana
readlink -m
tidak tersedia,readlink -f
gagal jika file tidak ada. Jadi Anda mungkin perlu beberapa solusi seperti ini (belum diuji!):Ini tidak benar-benar benar jika
$1
mencakup.
atau..
untuk jalur yang tidak ada (seperti dalam/doesnotexist/./a
), tetapi harus mencakup sebagian besar kasus.(Ganti di
readlink -m --
atas denganreadlink_missing
.)Edit karena downvote berikut
Berikut ini adalah tes, bahwa fungsi ini memang benar:
Bingung? Ya, inilah hasil yang benar ! Bahkan jika Anda berpikir itu tidak sesuai dengan pertanyaan, inilah buktinya ini benar:
Tanpa keraguan,
../bar
adalah jalur relatif yang tepat dan satu-satunya yang benar dari halamanbar
dilihat dari halamanmoo
. Segala sesuatu yang lain akan salah.Ini sepele untuk mengadopsi output ke pertanyaan yang tampaknya mengasumsikan, yaitu
current
direktori:Ini mengembalikan persis, apa yang diminta.
Dan sebelum Anda mengangkat alis, inilah varian yang sedikit lebih kompleks dari
relpath
(tempat perbedaan kecil), yang seharusnya juga berfungsi untuk URL-Sintaks (sehingga trailing dapat/
bertahan, berkat beberapa-bash
magik):Dan berikut ini cek untuk memperjelas: Ini benar-benar berfungsi seperti yang diperintahkan.
Dan di sini adalah bagaimana ini dapat digunakan untuk memberikan hasil yang diinginkan dari pertanyaan:
Jika Anda menemukan sesuatu yang tidak berfungsi, beri tahu saya di komentar di bawah. Terima kasih.
PS:
Mengapa argumen
relpath
"terbalik" berbeda dengan semua jawaban lain di sini?Jika Anda berubah
untuk
maka Anda dapat meninggalkan parameter ke-2, sehingga BASE adalah direktori saat ini / URL / apa pun. Itu hanya prinsip Unix, seperti biasa.
Jika Anda tidak suka itu, silakan kembali ke Windows. Terima kasih.
sumber
Sayangnya, jawaban Mark Rushakoff (sekarang dihapus - itu merujuk kode dari sini ) tampaknya tidak berfungsi dengan benar ketika diadaptasi ke:
Pemikiran yang diuraikan dalam komentar dapat disempurnakan untuk membuatnya bekerja dengan benar untuk sebagian besar kasus. Saya akan berasumsi bahwa skrip mengambil argumen sumber (di mana Anda berada) dan argumen target (di mana Anda ingin pergi), dan bahwa keduanya adalah nama path absolut atau keduanya relatif. Jika satu mutlak dan relatif lain, hal yang paling mudah adalah dengan awalan nama relatif dengan direktori kerja saat ini - tetapi kode di bawah ini tidak melakukan itu.
Awas
Kode di bawah ini hampir berfungsi dengan benar, tetapi tidak sepenuhnya benar.
xyz/./pqr
'.xyz/../pqr
'../
' dari jalur.Kode Dennis lebih baik karena memperbaiki 1 dan 5 - tetapi memiliki masalah yang sama 2, 3, 4. Gunakan kode Dennis (dan pilih di depan ini) karena itu.
(NB: POSIX menyediakan panggilan sistem
realpath()
yang menyelesaikan nama path sehingga tidak ada symlink yang tersisa di dalamnya. Menerapkannya ke nama input, dan kemudian menggunakan kode Dennis akan memberikan jawaban yang benar setiap kali. Ini sepele untuk menulis kode C yang membungkusrealpath()
- Saya sudah melakukannya - tapi saya tidak tahu utilitas standar yang melakukannya.)Untuk ini, saya menemukan Perl lebih mudah digunakan daripada shell, meskipun bash memiliki dukungan yang layak untuk array dan mungkin bisa melakukan ini juga - latihan untuk pembaca. Jadi, diberi dua nama yang kompatibel, pisahkan masing-masing menjadi komponen:
Jadi:
Skrip uji (tanda kurung berisi kosong dan tab):
Output dari skrip uji:
Script Perl ini bekerja cukup menyeluruh pada Unix (tidak memperhitungkan semua kompleksitas nama jalur Windows) dalam menghadapi input aneh. Ini menggunakan modul
Cwd
dan fungsinyarealpath
untuk menyelesaikan jalur nama sebenarnya yang ada, dan melakukan analisis tekstual untuk jalur yang tidak ada. Dalam semua kasus kecuali satu, itu menghasilkan output yang sama dengan skrip Dennis. Kasus yang menyimpang adalah:Kedua hasil tersebut setara - hanya saja tidak identik. (Outputnya berasal dari versi skrip tes yang sedikit dimodifikasi - skrip Perl di bawah ini hanya mencetak jawaban, bukan input dan jawaban seperti pada skrip di atas.) Sekarang: haruskah saya menghilangkan jawaban yang tidak berfungsi? Mungkin...
sumber
/home/part1/part2
untuk/
memiliki salah satu terlalu banyak../
. Kalau tidak, skrip saya cocok dengan output Anda, kecuali skrip saya menambahkan yang tidak perlu.
di akhir tempat tujuan.
dan saya tidak menggunakan./
di awal yang turun tanpa naik.readlink /usr/bin/vi
memberi/etc/alternatives/vi
, tapi itu symlink lain - sedangkanreadlink -f /usr/bin/vi
memberi/usr/bin/vim.basic
, yang merupakan tujuan akhir dari semua symlink ...Saya menganggap pertanyaan Anda sebagai tantangan untuk menulis ini dalam kode shell "portabel", yaitu
Ini berjalan pada semua shell POSIX (zsh, bash, ksh, ash, busybox, ...). Itu bahkan berisi testuite untuk memverifikasi operasinya. Kanonalisasi nama path dibiarkan sebagai latihan. :-)
sumber
Solusi Saya:
sumber
Ini versi saya. Ini berdasarkan jawaban oleh @Offirmo . Saya membuatnya kompatibel dengan Dash dan memperbaiki kegagalan testcase berikut:
./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../..f/g/"
Sekarang:
CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../../../def/g/"
Lihat kode:
sumber
Tebak yang satu ini akan melakukan triknya juga ... (dilengkapi dengan tes bawaan) :)
OK, beberapa overhead yang diharapkan, tapi kami melakukan Bourne shell di sini! ;)
sumber
Script ini hanya berfungsi pada nama path. Tidak memerlukan file apa pun untuk eksis. Jika jalur yang dilewati tidak absolut, perilaku ini agak tidak biasa, tetapi harus bekerja seperti yang diharapkan jika kedua jalur tersebut relatif.
Saya hanya mengujinya pada OS X, jadi mungkin tidak portabel.
sumber
Jawaban ini tidak membahas bagian Bash dari pertanyaan, tetapi karena saya mencoba menggunakan jawaban dalam pertanyaan ini untuk mengimplementasikan fungsi ini di Emacs saya akan membuangnya di sana.
Emacs sebenarnya memiliki fungsi untuk ini di luar kotak:
sumber
relpath
fungsi) berperilaku identik denganfile-relative-name
untuk kasus uji yang Anda berikan.Berikut skrip shell yang melakukannya tanpa memanggil program lain:
sumber: http://www.ynform.org/w/Pub/Relpath
sumber
Saya membutuhkan sesuatu seperti ini tetapi yang memutuskan tautan simbolik juga. Saya menemukan bahwa pwd memiliki flag -P untuk tujuan itu. Sepotong skrip saya ditambahkan. Ini dalam fungsi dalam skrip shell, maka $ 1 dan $ 2. Nilai hasil, yang merupakan jalur relatif dari START_ABS ke END_ABS, ada dalam variabel UPDIRS. Script cd masuk ke setiap direktori parameter untuk mengeksekusi pwd -P dan ini juga berarti bahwa parameter path relatif ditangani. Cheers, Jim
sumber