Komut dosyasının mutlak yolunu elde etmenin taşınabilir yolu?


29

(Zsh) betiğinin mutlak yolunu belirlemesinin taşınabilir bir yolu nedir?

Linux'ta böyle bir şey kullanıyorum

mypath=$(readlink -f $0)

... ama bu taşınabilir değil. (Örneğin, readlinkDarwin'de -fbayrağını tanımıyor veya hiçbir eşdeğeri yok.) (Ayrıca, bunun için kullanmak readlinkkuşkusuz, belirsiz görünen bir kesmektir.)

Daha taşınabilir bir yol nedir?


Yanıtlar:


28

İle zsh, sadece:

mypath=$0:A

Şimdi diğer kabukları için olsa realpath()ve readlink()standart fonksiyonları (ikincisi bir sistem çağrısı olmak üzere) vardır, realpathve readlinkbazı sistemler bir veya çeşitli davranış ve özellik seti ile diğer veya her ikisine de sahip olsa, standart komut değildir.

Sık sık, taşınabilirlik için başvurmak isteyebilirsiniz perl:

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

Bu, GNU'lara (GNU ) readlink -fgöre olduğundan daha fazla davranır , çünkü dosya, dizin adı olduğu sürece mevcut olmazsa şikayet etmez.realpath()readlink -e


Not: bu sizin için çalışmıyor .zshrc: Bunun yerine bu yazıya bakın .
Bryce Guinta

24

Zsh'de aşağıdakileri yapabilirsiniz:

mypath=${0:a}

Veya, betiğin bulunduğu dizini almak için:

mydir=${0:a:h}

Kaynak: zshexpn (1) man sayfası, bölüm TARİHİ GENİŞLETME, alt bölüm değiştiricileri (veya sadece info -f zsh -n Modifiers).


Tatlı! Uzun zamandır böyle bir şey arıyordum ve onu arayan zsh man sayfalarının tamamını okudum, ancak 'Geçmiş genişlemesi' altında bakmak bana asla gelmezdi.
Vucar Timnärakrul

1
GNU'ların eşdeğeri readlink -folur $0:A.
Stéphane Chazelas

11

Bunu birkaç yıldır kullanıyorum:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")

1
bunu sevdim! Şimdiye kadar, bu oldukça taşınabilir. Windows 2008'de Solaris, OmniOS, Linux, Mac ve hatta Cygwin üzerinde çalışıyor.
Tim Kennedy

4
GNU'ların eşdeğeri olmadığı readlink -f, betiğin kendisinin bir işaret olduğu zamandır.
Stéphane Chazelas

Ubuntu 16.04 ile zsh, eğer bunu ev dir ( /home/ville) dizinimdeyken doğrudan ya da alt kabukta (önerildiği gibi) uygularsam , yazdırır /home/ville/zsh.
Ville

8

Bu sözdizimi (test herhangi Bourne kabuğu tarzı tercüman taşınabilir olması gerektiği bash, ksh88, ksh93, zsh, mksh, dashve busybox sh):

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

Bu sürüm eski AT&T Bourne kabuğuna (POSIX dışı) uyumluluk ekler:

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

Teşekkürler. Yine de, rahatsız etmenin fazlaca $PWDolabileceğini düşünüyorum - sadece mutlak akıma benzer şekilde ayarlayabilirsiniz cd -P .. Bunun bourneshell'de işe yarayacağından şüpheliyim - ancak ilk defa test ettiğinizlerin hepsinde de çalışması gerekiyor. Yine de benim için yapar.
mikeserv,

@moose Hangi işletim sistemini kullanıyorsunuz?
jlliagre

geyik kim ne?
mikeserv

@mikeserv geyik bazı konu hakkında bir yorum yayınladı geçen birisi olduğunu zshve dirnamehir çekilme hızla / onu comment ... ama
jlliagre

Bourne Shell betiğiniz bir Bourne Shell ile çalışmaz, çünkü Bourne Shell getopt () 'ı cd (1) için kullanmaz.
schily

4

Gerçekten mutlak yolu, yani kök dizininden bir yolu kastettiğinizi varsayalım:

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

Bu arada, herhangi bir Bourne tarzı kabuğun içinde çalışır.

Tüm sembolik bağların çözüldüğü bir yolu kastediyorsanız, bu farklı bir konudur. readlink -fLinux (bazı soyulmuş BusyBox sistemleri hariç), FreeBSD, NetBSD, OpenBSD ve Cygwin'de çalışır, ancak OS / X, AIX, HP / UX veya Solaris'te çalışmaz. Eğer varsa readlink, bunu bir döngüde çağırabilirsiniz:

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
}

Eğer yoksa readlink, yaklaşık olarak değerlendirebilirsiniz ls -n, ancak bu yalnızca lsdosya adında yazdırılamayan herhangi bir karakteri karışmazsa çalışır .

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

(Ek zolarak, bağlantı hedefi yeni bir satırda biterse, komut değiştirme işlemi başka türlü yiyebilir. realpathİşlev, bu durumda dizin adları için bu durumda işlemez.)


1
Çıktı bir terminale gitmediğinde yazdırılamayan karakterleri yöneten herhangi bir lsuygulama biliyor musunuz?
Stéphane Chazelas

1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(eğer isim UTF-8 ise, latin1 size bir tane verir ?). Sanırım bunu eski ticari birimlerde de gördüm.
Gilles 'SO- kötülük olmayı bırak'

@ StéphaneChazelas Birkaç hatayı düzelttim ancak kapsamlı bir şekilde test etmedim. Bazı durumlarda hala başarısız olursa bana bildirin (bazı dizinlerde uygulama izni bulunmaması dışında, bu uç dava ile ilgilenmeyeceğim).
Gilles 'SO- kötülük yapmayı bırak'

@Gilles - busyboxbu nedir? git'e göre busybox ls2011'den beri bir kod değişikliği olmadı. Benim busybox ls- 2013 dolaylarında - bu şeyi yapmıyor. Bu bir - 2012 dolaylarında - yapar . Bu nedenini açıklayabilir. busyboxWico desteğini de içermek üzere Unicode desteğiyle mi inşa ettiniz ? mkinitcpio busyboxPaketteki yapı seçeneklerini kontrol etmek için başka bir yolla gitmek isteyebilirsiniz .
mikeserv

Gilles - Başlangıçta bu cevabı yanlış değerlendirdiğime inanıyorum - veya en azından bir kısmı. Sıkı bir şekilde isimlendiren dosya isimlerinin mantra olduğuna kesin olarak inanıyorum, ancak kesinlikle poor_mans_readlink'iniz çok iyi bir şekilde yapılıyor . Bana bir düzenleme yapmanın nezaketini yaparsanız - herhangi bir düzenleme yapacaktır - ve sonra beni ping ederek, bu konuda oyumu tersine çevirmek istiyorum.
mikeserv,

1

Geçerli dizinde - veya kabuk komut dosyasını yürüttüğünüz dizinde - yürütmek için izin almanız şartıyla, tüm ihtiyacınız olan bir dizine mutlak bir yol istiyorsanız cd.

Adım 10 cdbireyin spec

Eğer -Pseçenek etkin, $PWDortam değişkeni tarafından çıkış olacaktır dizeye ayarlanır edilecektir pwd -P. Geçerli çalışma dizinini belirlemek için yeni dizinde veya bu dizinin herhangi bir üstünde yeterli izin yoksa, $PWDortam değişkeninin değeri belirtilmez.

Ve üzerinde pwd -P

Standart çıktıya yazılan yol adı, sembolik bağlantı tipi dosyalara atıfta bulunan hiçbir bileşen içermemelidir. pwdYardımcı programın standart çıktıya yazabileceği birden fazla yol varsa , biri tek / eğik çizgi karakteriyle, diğeri iki veya eğik çizgi karakteriyle başlıyorsa, o zaman tek / eğik çizgi karakteriyle başlayan yol adını yazmalıdır. Yol adı, bir veya iki satır / eğik çizgi karakterinden sonra gereksiz / eğik çizgi içermemelidir.

Çünkü bu cd -Pne Geçerli çalışma dizini ayarlamak zorundadır pwd -Paksi yazdırmalısınız ve o cd -yazdırmak zorunda $OLDPWDolduğunu şu işleri:

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

ÇIKTI

/home/mikeserv/test/ln

Bunun için bekle...

cd -P . ; cd . ; cd -

ÇIKTI

/home/mikeserv/test/dir

Ve yazdırdığımda da cd -baskı yapıyorum $OLDPWD. cdsetleri $PWDyakında kadar cd -P . $PWDmutlak yolu artık /ben başka değişkenleri gerekmez -. Ve aslında, izlemeye bile ihtiyacım olmamalı, .ancak süslenmemişken etkileşimli bir kabuğa sıfırlama $PWDkonusunda belirli bir davranış var . Bu yüzden sadece geliştirmek için iyi bir alışkanlık.$HOMEcd

Bu yüzden, sadece yukarıdakileri yolda yapmak, yolunu ${0%/*}doğrulamak için fazlasıyla yeterli olmalıdır $0, ancak $0yumuşak bir link olması durumunda, muhtemelen dizini değiştiremezsiniz, ne yazık ki.

İşte bunu idare edecek bir fonksiyon:

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                                    
}

Geçerli kabukta yapabileceği kadarını yapmaya çalışır - bir alt kabuk çağırmadan - hatalara ve alt dizinlere işaret etmeyen yumuşak bağlantılara çağrılan alt kabuklar olsa da. POSIX uyumlu bir kabuğa, POSIX uyumlu lsve temiz bir _function()ad alanına bağlıdır. Bu durumda hala geçerli olmaz, ancak unsetbu durumda bazı mevcut kabuğun işlevlerinin üzerine yazabilir . Genel olarak, tüm bu bağımlılıklar bir Unix makinesinde oldukça güvenilir bir şekilde bulunmalıdır.

Argümanlarla veya argümanlar olmadan çağrılan ilk şey $PWD, kanonik değerine sıfırlanır - buradaki hedeflerine gereken bağlantıları gerektiği gibi çözer. Argümanlar olmadan denir ve bu konuda; ancak onlarla çağrılır ve her biri için yolu çözer ve kurallara uygun hale getirir ya da stderrneden olmasın diye bir mesaj yazdırır .

Çoğunlukla mevcut kabukta çalıştığı için, herhangi bir uzunlukta bir argüman listesiyle başa çıkabilmesi gerekir. Aynı zamanda $_zdlmdeğişkeni de ( unsetiçinden geçtiğinde de gelir) arar ve C-kaçan değerini hemen her birinin tek bir \nsatır sonu karakteri ile takip ettiği argümanlarının her birinin hemen sağına yazdırır.

Çok fazla dizin değiştirme işlemi gerçekleştirir, ancak, onu kanonik değerine ayarlamaktan başka, etkilenmez $PWD, ancak $OLDPWDhiçbir zaman esasında sayılmaz.

Her argümanından olabildiğince kısa sürede vazgeçmeye çalışır. Bu ilk çalışır cdiçine $1. Mümkünse, argümanın kanonik yolunu yazdırır stdout. Yapamıyorsa, $1var olup olmadığını kontrol eder ve yumuşak bir bağlantı değildir. Eğer doğruysa yazdırır.

Bu şekilde $1, bir dizine işaret etmeyen sembolik bir bağlantı olmadığı sürece , kabuğun ele alma izni olan herhangi bir dosya tipi argümanını ele alır . Bu durumda whilebir alt kabuktaki döngü çağırır .

lsBağlantıyı okumak için çağırır . Herhangi bir referans yolunu güvenilir bir şekilde ele almak için ilk önce geçerli dizinin başlangıç ​​değeriyle değiştirilmesi gerekir ve bu nedenle, komut değiştirme alt kabuğunda işlev aşağıdakileri yapar:

cd -...ls...echo /

lsBağlantının adını ve dizesini tam olarak içermesi gerektiği kadar çıktısının solundan çıkarır ->. Ben ilk kaçınmak çalıştık de ile bunu yaparken shiftve $IFSçıkıyor bu gibi bicimlendirebilirim yakın olarak en güvenilir yöntemdir. Bu, Gilles'in poor_mans_readlink'in yaptığı şeydir - ve iyi yapılır.

Dönen dosya lskesinlikle yumuşak bir bağlantı olmadan bu işlemi bir döngüde tekrarlayacaktır . Bu noktada, o yolu daha önce olduğu gibi onaylar ve cdardından yazdırır.

Örnek kullanım:

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 \
    . ..      

ÇIKTI

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

Ya da muhtemelen ...

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

zdlm=\\0 zpath * | cat -A

ÇIKTI

/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' -> ...

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.