Cara portabel untuk mendapatkan jalur absolut skrip?

29

Apa cara portabel untuk skrip (zsh) untuk menentukan jalur absolutnya?

Di Linux saya menggunakan sesuatu seperti

mypath=$(readlink -f $0)

... tapi ini tidak portabel. (Misalnya, readlinkpada darwin tidak mengenali -fbendera, juga tidak memiliki yang setara.) (Juga, menggunakan readlinkuntuk ini, memang, peretasan yang tampak tidak jelas.)

Apa cara yang lebih portabel?

kjo
sumber
1
Lihat juga Akankah $ 0 selalu menyertakan path ke skrip?
Stéphane Chazelas

Jawaban:

28

Dengan zsh, hanya saja:

mypath=$0:A

Sekarang untuk shell lain, meskipun realpath()dan readlink()merupakan fungsi standar (yang terakhir menjadi panggilan sistem), realpathdan readlinkbukan perintah standar, meskipun beberapa sistem memiliki satu atau yang lain atau keduanya dengan berbagai perilaku dan fitur yang ditetapkan.

Seperti sering, untuk portabilitas, Anda mungkin ingin menggunakan perl:

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}

Itu akan berperilaku lebih seperti GNU readlink -fdaripada realpath()(GNU readlink -e) dalam hal itu tidak akan mengeluh jika file tersebut tidak ada asalkan dirname tidak.

Stéphane Chazelas
sumber
Catatan: ini tidak berfungsi untuk Anda .zshrc: Lihat posting ini sebagai gantinya.
Bryce Guinta
24

Di zsh Anda dapat melakukan hal berikut:

mypath=${0:a}

Atau, untuk mendapatkan direktori tempat skrip berada:

mydir=${0:a:h}

Sumber: halaman manual zshexpn (1), bagian EKSPANSI SEJARAH, Pengubah ayat (atau sederhana info -f zsh -n Modifiers).

mrmenken
sumber
Manis! Saya telah mencari-cari sesuatu seperti ini selama berabad-abad dan membaca sejumlah besar halaman manual zsh mencarinya, tetapi tidak akan pernah terpikir oleh saya untuk mencari di bawah 'Perluasan sejarah'.
Vucar Timnärakrul
1
Setara dengan GNU readlink -flebih suka $0:A.
Stéphane Chazelas
11

Saya telah menggunakan ini selama beberapa tahun sekarang:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
pengguna17591
sumber
1
saya suka itu! sejauh ini, itu cukup portabel. bekerja pada Solaris, OmniOS, Linux, Mac, dan bahkan Cygwin pada Windows 2008.
Tim Kennedy
4
Di mana itu tidak setara dengan GNU readlink -fadalah ketika skrip itu sendiri adalah symlink.
Stéphane Chazelas
Pada Ubuntu 16.04 dengan zsh, jika saya menjalankan ini baik secara langsung atau dalam subshell (seperti yang disarankan) saat di dir home saya ( /home/ville), ia mencetak /home/ville/zsh.
Ville
8

Sintaks ini harus portabel untuk setiap Bourne shell gaya interpreter (diuji dengan bash, ksh88, ksh93, zsh, mksh, dashdan busybox sh):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

Versi ini menambahkan kompatibilitas ke shell Bourne AT&T legacy (non POSIX):

mypath=`dirname "$0"`
mypath=`exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
echo mypath=$mypath
Jlliagre
sumber
Terima kasih. meskipun, saya pikir unsetting $PWDmungkin berlebihan - Anda bisa mengaturnya menjadi arus absolut seperti cd -P .. Saya ragu itu akan bekerja di bourneshell - tetapi harus bekerja di semua yang Anda uji untuk yang pertama. tetap melakukannya untuk saya.
mikeserv
@moose OS apa yang Anda jalankan?
jlliagre
siapa rusa besar? apa?
mikeserv
@mikeserv moose adalah pejalan kaki yang memposting komentar tentang beberapa masalah dengan zshdan dirnametetapi dengan cepat menarik komentarnya ...
jlliagre
Skrip Bourne Shell Anda tidak akan berfungsi dengan Bourne Shell karena Bourne Shell tidak menggunakan getopt () untuk cd (1).
schily
4

Dengan asumsi Anda benar-benar berarti jalur absolut, yaitu jalur dari direktori root:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

Ngomong-ngomong, ini bekerja di shell gaya Bourne.

Jika Anda maksud jalur dengan semua tautan simbolik diselesaikan, itu masalah yang berbeda. readlink -fbekerja di Linux (tidak termasuk beberapa sistem BusyBox yang dipreteli), FreeBSD, NetBSD, OpenBSD dan Cygwin, tetapi tidak pada OS / X, AIX, HP / UX atau Solaris. Jika sudah readlink, Anda bisa menyebutnya dalam satu lingkaran:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

Jika tidak punya readlink, Anda bisa memperkirakannya dengan ls -n, tetapi ini hanya berfungsi jika lstidak memotong-motong karakter yang tidak dapat dicetak dalam nama file.

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(Ekstra zdalam hal target tautan berakhir pada baris baru, yang substitusi perintahnya akan habis. realpathFungsi tidak menangani kasus itu untuk nama direktori, by the way.)

Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Apakah Anda tahu lsimplementasi yang membuat karakter yang tidak dapat dicetak ketika output tidak pergi ke terminal.
Stéphane Chazelas
1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(jika namanya ada di UTF-8, latin1 memberi Anda satu ?). Saya pikir saya juga pernah melihatnya di Unives komersial yang lebih tua.
Gilles 'SO- berhenti menjadi jahat'
@ StéphaneChazelas Saya sudah memperbaiki beberapa bug tetapi tidak diuji secara ekstensif. Beritahu saya jika masih gagal dalam beberapa kasus (selain kurangnya izin eksekusi di beberapa direktori, saya tidak akan berurusan dengan kasus tepi itu).
Gilles 'SANGAT berhenti menjadi jahat'
@Gilles - apa busyboxini? menurut git busybox ls belum ada perubahan kode sejak 2011. My busybox ls- sekitar 2013 - tidak melakukan hal ini. Ini satu - sekitar tahun 2012 - tidak . Ini mungkin menjelaskan mengapa. Sudahkah Anda membangun busyboxdengan dukungan Unicode - untuk menyertakan dukungan wchar? Anda mungkin ingin mencobanya, lain untuk memeriksa opsi build dalam mkinitcpio busyboxpaket.
mikeserv
Gilles - Saya percaya saya awalnya salah menilai jawaban ini - atau setidaknya sebagian darinya. Sementara saya sangat percaya mantra nama file mangling Anda sebagai kesalahan total, pasti poor_mans_readlink Anda dilakukan dengan sangat baik. Jika Anda akan melakukan saya kebaikan melakukan edit - edit apa pun akan dilakukan - dan mem-ping saya sesudahnya, saya ingin membalikkan suara saya dalam hal ini.
mikeserv
1

Asalkan Anda telah menjalankan izin pada direktori saat ini - atau pada direktori dari mana Anda mengeksekusi skrip shell Anda - jika Anda ingin jalur absolut ke direktori yang Anda butuhkan cd.

Langkah 10 dari cdspesifikasi

Jika -Popsi ini berlaku, $PWDvariabel lingkungan harus diatur ke string yang akan dihasilkan oleh pwd -P. Jika ada cukup izin pada direktori baru, atau pada induk dari direktori itu, untuk menentukan direktori kerja saat ini, nilai $PWDvariabel lingkungan tidak ditentukan.

Dan terus pwd -P

Pathname yang ditulis ke output standar tidak boleh mengandung komponen apa pun yang merujuk pada file dari tautan simbolik tipe. Jika ada beberapa nama path yang pwddapat ditulis utilitas ke output standar, satu dimulai dengan karakter tunggal / slash dan satu atau lebih dimulai dengan dua karakter / slash, maka itu akan menulis nama path yang dimulai dengan karakter single / slash. Pathname tidak boleh mengandung karakter slash / karakter slash yang tidak perlu setelah satu atau dua karakter slash.

Itu karena cd -Pharus mengatur direktori kerja saat ini untuk apa yang pwd -Pseharusnya dicetak dan yang cd -harus mencetak $OLDPWDbahwa berikut berfungsi:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

KELUARAN

/home/mikeserv/test/ln

menunggu untuk itu...

cd -P . ; cd . ; cd -

KELUARAN

/home/mikeserv/test/dir

Dan ketika saya mencetak dengan cd -saya mencetak $OLDPWD. cdset $PWDsegera setelah saya cd -P . $PWDsekarang menjadi jalur absolut untuk /- jadi saya tidak perlu variabel lain. Dan sebenarnya, saya bahkan tidak perlu membuntuti, .tetapi ada perilaku pengaturan ulang yang ditentukan$PWD ke $HOMEdalam shell interaktif ketika cdtanpa hiasan. Jadi itu hanya kebiasaan yang baik untuk dikembangkan.

Jadi hanya melakukan hal di atas pada jalur di ${0%/*}harus lebih dari cukup untuk memverifikasi$0 path, tetapi dalam kasus itu $0sendiri merupakan soft-link, Anda mungkin tidak dapat mengubah direktori ke dalamnya, sayangnya.

Berikut adalah fungsi yang akan mengatasinya:

zpath() { cd -P . || return
    _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; }
    _cd()  { cd -P "$1" ; } >/dev/null 2>&1
    while [ $# -gt 0 ] && _cd .
    do  if     _cd "$1" 
        then   _out
        elif ! [ -L "$1" ] && [ -e "$1" ] 
        then   _cd "${1%/*}"; _out "$1"
        elif   [ -L "$1" ]
        then   ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ]
                 do    set " $1" "$(_cd -; ls -nd -- "$1"; echo /)"
                       set -- "${2#*"$1" -> }"
                 done; _out "$1" 
    );  else   ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" )
    fi; _cd -; shift; done
    unset -f _out _cd; unset -v _zdlm                                    
}

Ia berusaha keras untuk melakukan sebanyak mungkin dalam shell saat ini - tanpa menggunakan subkulit - meskipun ada subkulit yang dipanggil untuk kesalahan dan tautan lunak yang tidak mengarah ke direktori. Itu tergantung pada shell yang kompatibel dengan POSIX dan yang kompatibel lsdengan POSIX dan juga clean_function() namespace yang . Ini masih akan berfungsi dengan baik tanpa yang terakhir, meskipun mungkin menimpa unsetbeberapa fungsi shell saat ini dalam kasus itu. Secara umum semua dependensi ini harus cukup andal tersedia pada mesin Unix.

Dipanggil dengan atau tanpa argumen, hal pertama yang dilakukannya adalah reset $PWD ke nilai kanoniknya - ia memutuskan semua tautan di dalamnya ke target mereka sebagaimana diperlukan. Disebut tanpa argumen dan hanya itu; tetapi dipanggil dengan mereka dan itu akan menyelesaikan dan mengkanonik jalur untuk masing-masing atau mencetak pesan stderrmengapa tidak

Karena sebagian besar beroperasi di shell saat ini, ia harus dapat menangani daftar argumen dengan panjang berapa pun. Itu juga mencari $_zdlmvariabel (yang juga unsetketika itu melalui) dan mencetak nilai C-escaped langsung ke kanan setiap argumennya, yang masing-masing selalu diikuti juga oleh satu\n karakter ewline .

Itu tidak banyak direktori berubah, tapi, selain pengaturan ke nilai kanonik, itu tidak mempengaruhi $PWD, meskipun$OLDPWD tidak dengan cara apa pun dapat diandalkan ketika sedang lewat.

Ia mencoba untuk keluar dari setiap argumennya sesegera mungkin. Pertama kali mencoba cdmasuk $1. Jika itu bisa mencetak jalur kanonik argumen untuk stdout. Jika tidak dapat memeriksa apakah $1ada dan bukan tautan lunak. Jika benar, itu akan dicetak.

Dengan cara ini ia menangani argumen tipe file apa pun yang shell memiliki izin untuk mengatasinya kecuali $1tautan simbolis yang tidak mengarah ke direktori. Dalam hal ini ia memanggil whileloop dalam subkulit.

Itu panggilan lsuntuk membaca tautan. Direktori saat ini harus diubah ke nilai awalnya terlebih dahulu agar dapat menangani semua jalur referensi dengan andal, dan dalam substitusi perintah, subshell berfungsi:

cd -...ls...echo /

Itu strip dari kiri lsoutput sesedikit yang diperlukan untuk sepenuhnya berisi nama tautan dan string ->. Meskipun pada awalnya saya mencoba untuk menghindari melakukan ini dengan shiftdan $IFSternyata ini adalah metode yang paling dapat diandalkan sedekat yang saya bisa. Ini adalah hal yang sama dengan poor_mans_readlink Gilles - dan ini dilakukan dengan baik.

Ini akan mengulangi proses ini dalam satu lingkaran sampai nama file kembali dari lsjelas bukan tautan lunak. Pada titik itu kanonik jalur yang seperti sebelumnya dengan cdkemudian dicetak.

Contoh penggunaan:

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

KELUARAN

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/

Atau mungkin ...

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\\0 zpath * | cat -A

KELUARAN

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...
mikeserv
sumber
0

bagaimana dengan satu-liner simpatik ketika ada python tersedia untuk mencegah mendefinisikan ulang suatu algoritma?

function readlink { python -c "import os.path; print os.path.realpath('$1')"; }

sama seperti https://stackoverflow.com/a/7305217

Antonio Daniel Rodriguez
sumber