Cách di động để có được đường dẫn tuyệt đối của tập lệnh?


29

Một cách di động cho một tập lệnh (zsh) để xác định đường dẫn tuyệt đối của nó là gì?

Trên Linux tôi sử dụng một cái gì đó như

mypath=$(readlink -f $0)

... nhưng đây không phải là di động. (Ví dụ, readlinktrên darwin không nhận ra -fcờ, cũng không có bất kỳ tương đương nào.) (Ngoài ra, sử dụng readlinkcho việc này là, một sự thừa nhận khá khó hiểu.)

Cách di động hơn là gì?


Câu trả lời:


28

Với zsh, đó chỉ là:

mypath=$0:A

Bây giờ đối với các shell khác, mặc dù realpath()readlink()là các hàm tiêu chuẩn (sau này là một cuộc gọi hệ thống) realpathreadlinkkhông phải là lệnh tiêu chuẩn, mặc dù một số hệ thống có một hoặc một hoặc cả hai với các hành vi và tính năng khác nhau.

Như thường lệ, đối với tính di động, bạn có thể muốn sử dụng perl:

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

Điều đó sẽ hoạt động giống như GNU readlink -fhơn realpath()(GNU readlink -e) ở chỗ nó sẽ không khiếu nại nếu tệp không tồn tại miễn là dirname của nó tồn tại.


Lưu ý: điều này không làm việc cho bạn .zshrc: Thay vào đó hãy xem bài đăng này .
Bryce Guinta

24

Trong zsh bạn có thể làm như sau:

mypath=${0:a}

Hoặc, để có được thư mục chứa tập lệnh:

mydir=${0:a:h}

Nguồn: trang man zshExn (1), phần MỞ RỘNG LỊCH SỬ, Sửa đổi tiểu mục (hoặc đơn giản info -f zsh -n Modifiers).


Ngọt! Tôi đã tìm kiếm đôi khi như thế này từ lâu và đọc toàn bộ các trang web zsh đang tìm kiếm nó, nhưng nó sẽ không bao giờ xảy ra với tôi để xem trong phần 'Mở rộng lịch sử'.
Vucar Timnärakrul

1
Tương đương của GNU của readlink -fthà $0:A.
Stéphane Chazelas

11

Tôi đã sử dụng điều này trong nhiều năm nay:

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

1
tôi thích nó! cho đến nay, đó là khá di động. hoạt động trên Solaris, OmniOS, Linux, Mac và thậm chí Cygwin trên Windows 2008
Tim Kennedy

4
Trường hợp nó không tương đương với GNU readlink -flà khi chính tập lệnh là một liên kết tượng trưng.
Stéphane Chazelas

Trên Ubuntu 16.04 với zsh, nếu tôi thực hiện điều này trực tiếp hoặc trong lớp con (như được đề xuất) trong khi trong thư mục nhà của tôi ( /home/ville), nó sẽ in ra /home/ville/zsh.
Ville

8

Cú pháp này nên được cầm tay với bất kỳ phong cách vỏ thông dịch viên Bourne (thử nghiệm với bash, ksh88, ksh93, zsh, mksh, dashbusybox sh):

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

Phiên bản này thêm khả năng tương thích với vỏ AT & T Bourne cũ (không phải 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

cảm ơn. mặc dù, tôi nghĩ rằng việc không đặt $PWDcó thể là quá mức cần thiết - bạn chỉ có thể đặt nó thành dòng điện tuyệt đối như thế nào cd -P .. Tôi nghi ngờ rằng nó sẽ hoạt động trong bounceshell - nhưng nó sẽ hoạt động trong tất cả những cái bạn đã thử nghiệm đầu tiên. dù sao cũng làm cho tôi
mikeerv

@moose Bạn đang chạy hệ điều hành nào?
jlliagre

nai là ai gì?
mikeerv

@mikeerv moose là một người qua đường đã đăng một bình luận về một số vấn đề với zshdirnamenhưng nhanh chóng rút bình luận của cô ấy / cô ấy ...
jlliagre

Tập lệnh Bourne Shell của bạn sẽ không hoạt động với Bourne Shell vì Bourne Shell không sử dụng getopt () cho cd (1).
schily

4

Giả sử bạn thực sự có nghĩa là đường dẫn tuyệt đối, tức là một đường dẫn từ thư mục gốc:

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

Điều này hoạt động trong bất kỳ vỏ kiểu Bourne, nhân tiện.

Nếu bạn có nghĩa là một đường dẫn với tất cả các liên kết tượng trưng được giải quyết, đó là một vấn đề khác. readlink -fhoạt động trên Linux (không bao gồm một số hệ thống BusyBox bị loại bỏ), FreeBSD, NetBSD, OpenBSD và Cygwin, nhưng không hoạt động trên OS / X, AIX, HP / UX hoặc Solaris. Nếu bạn có readlink, bạn có thể gọi nó trong một vòng lặp:

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
}

Nếu bạn không có readlink, bạn có thể ước chừng nó ls -n, nhưng điều này chỉ hoạt động nếu lskhông mang bất kỳ ký tự không in được nào trong tên tệp.

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

(Phần bổ sung zlà trong trường hợp mục tiêu liên kết kết thúc trong một dòng mới, bằng cách nào đó thay thế lệnh sẽ ăn hết. realpathHàm này không xử lý trường hợp đó cho tên thư mục, nhân tiện.)


1
Bạn có biết bất kỳ lstriển khai nào mà các ký tự không thể in được khi đầu ra không đi đến một thiết bị đầu cuối.
Stéphane Chazelas

1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(đó là nếu tên đó là UTF-8, latin1 cung cấp cho bạn một bản duy nhất ?). Tôi nghĩ rằng tôi cũng đã thấy điều đó trên các Unices thương mại cũ.
Gilles 'SO- ngừng trở nên xấu xa'

@ StéphaneChazelas Tôi đã sửa một số lỗi nhưng không được kiểm tra rộng rãi. Hãy cho tôi biết nếu nó vẫn thất bại trong một số trường hợp (ngoài việc thiếu quyền thực thi trong một số thư mục, tôi sẽ không giải quyết trường hợp cạnh đó).
Gilles 'SO- đừng trở nên xấu xa'

@Gilles - busyboxđây là cái gì ? theo git busybox ls đã không có thay đổi mã kể từ năm 2011. My busybox ls- vào khoảng năm 2013 - không làm điều này. Đây một - circa 2012 - thực hiện . Điều này có thể giải thích tại sao. Bạn đã xây dựng busyboxsự hỗ trợ Unicode của mình - bao gồm hỗ trợ wchar chưa? Bạn có thể muốn thử, khác để kiểm tra các tùy chọn xây dựng trong mkinitcpio busyboxgói.
mikeerv

Gilles - Tôi tin rằng ban đầu tôi đánh giá sai câu trả lời này - hoặc ít nhất là một phần của nó. Mặc dù tôi tin chắc rằng câu thần chú xáo trộn của bạn là hoàn toàn ngụy biện, nhưng chắc chắn poor_mans_readlink của bạn được thực hiện rất tốt. Nếu bạn sẽ làm cho tôi sự tử tế khi thực hiện một chỉnh sửa - bất kỳ chỉnh sửa nào cũng được thực hiện - và sau đó đưa tôi ra, tôi muốn đảo ngược phiếu bầu của mình về điều này.
mikeerv

1

Miễn là bạn có quyền thực thi trên thư mục hiện tại - hoặc trên thư mục mà bạn đã thực thi tập lệnh shell của mình - nếu bạn muốn một đường dẫn tuyệt đối đến thư mục, tất cả những gì bạn cần là cd.

Bước 10 của cdthông số kỹ thuật

Nếu -Ptùy chọn có hiệu lực, $PWDbiến môi trường sẽ được đặt thành chuỗi sẽ được xuất theo pwd -P. Nếu không có đủ quyền trên thư mục mới hoặc trên bất kỳ cha mẹ nào của thư mục đó, để xác định thư mục làm việc hiện tại, giá trị của $PWDbiến môi trường là không xác định.

Và hơn thế nữa pwd -P

Tên đường dẫn được ghi vào đầu ra tiêu chuẩn sẽ không chứa bất kỳ thành phần nào đề cập đến các tệp có liên kết tượng trưng loại. Nếu có nhiều tên đường dẫn mà pwdtiện ích có thể ghi vào đầu ra tiêu chuẩn, một bắt đầu bằng một ký tự / dấu gạch chéo và một hoặc nhiều bắt đầu bằng hai / ký tự gạch chéo, thì nó sẽ viết tên đường dẫn bắt đầu bằng một ký tự gạch chéo / đơn. Tên đường dẫn không được chứa bất kỳ ký tự / dấu gạch chéo nào sau một hoặc hai ký tự gạch chéo.

Đó là bởi vì cd -Pcó để thiết lập thư mục làm việc hiện tại với những gì pwd -Pkhác nên in rằng cd -có in $OLDPWDrằng các công việc sau:

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

ĐẦU RA

/home/mikeserv/test/ln

chờ đã ...

cd -P . ; cd . ; cd -

ĐẦU RA

/home/mikeserv/test/dir

Và khi tôi in với cd -tôi đang in $OLDPWD. cdthiết lập $PWDngay khi tôi cd -P . $PWDbây giờ là một đường dẫn tuyệt đối /- vì vậy tôi không cần bất kỳ biến nào khác. Và trên thực tế, tôi nên thậm chí không cần các dấu .nhưng có một hành vi cụ thể của thiết lập lại $PWDđể $HOMEtrong một vỏ tương tác khi cdlà không trang trí. Vì vậy, nó chỉ là một thói quen tốt để phát triển.

Vì vậy, chỉ cần thực hiện các thao tác trên trên đường dẫn trong ${0%/*}là quá đủ để xác minh $0đường dẫn, nhưng trong trường hợp $0chính nó là một liên kết mềm, có lẽ bạn không thể thay đổi thư mục vào đó.

Đây là một chức năng sẽ xử lý rằng:

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                                    
}

Nó cố gắng làm nhiều nhất có thể trong shell hiện tại - mà không cần gọi một subshell - mặc dù có các subshell được gọi cho các lỗi và các liên kết mềm không trỏ đến các thư mục. Nó phụ thuộc vào lớp vỏ tương thích POSIX và tương thích POSIX lscũng như _function()không gian tên sạch . Nó vẫn sẽ hoạt động tốt mà không cần cái sau, mặc dù nó có thể ghi đè lên unsetmột số hàm shell hiện tại trong trường hợp đó. Nhìn chung, tất cả các phụ thuộc này đều có sẵn trên máy Unix.

Được gọi có hoặc không có đối số, điều đầu tiên nó được đặt lại $PWDthành giá trị chính tắc của nó - nó giải quyết bất kỳ liên kết nào trong đó đến các mục tiêu của chúng khi cần thiết. Được gọi mà không có đối số và đó là về nó; nhưng được gọi với họ và nó sẽ giải quyết và chuẩn hóa đường dẫn cho mỗi hoặc nếu không thì in một tin nhắn stderrtại sao không.

Bởi vì nó chủ yếu hoạt động trong trình bao hiện tại nên nó có thể xử lý một danh sách đối số có độ dài bất kỳ. Nó cũng tìm kiếm $_zdlmbiến số (cũng unsetlà khi nó thông qua) và in giá trị thoát C của nó ngay bên phải mỗi đối số của nó, mỗi đối số luôn được theo sau bởi một \nký tự ewline.

Nó thực hiện rất nhiều thay đổi thư mục, nhưng, ngoài việc đặt nó thành giá trị chính tắc của nó, nó không ảnh hưởng $PWD, mặc dù $OLDPWDkhông thể bằng bất kỳ phương tiện nào được tính khi nó đi qua.

Nó cố gắng bỏ từng đối số của nó ngay khi có thể. Đầu tiên nó cố gắng cdvào $1. Nếu nó có thể in đường dẫn chính tắc của đối số tới stdout. Nếu nó không thể kiểm tra nó $1tồn tại và không phải là một liên kết mềm. Nếu đúng, nó in.

Theo cách này, nó xử lý bất kỳ đối số loại tệp nào mà shell có quyền truy cập trừ khi $1là một liên kết tượng trưng không trỏ đến một thư mục. Trong trường hợp đó, nó gọi whilevòng lặp trong một subshell.

Nó gọi lsđể đọc liên kết. Thư mục hiện tại phải được thay đổi thành giá trị ban đầu trước tiên để xử lý một cách đáng tin cậy bất kỳ đường dẫn tham chiếu nào và do đó, trong lệnh thay thế lệnh con, hàm này thực hiện:

cd -...ls...echo /

Nó thoát khỏi bên trái của lsđầu ra ít nhất là phải chứa đầy đủ tên và chuỗi của liên kết ->. Lúc đầu tôi đã cố gắng tránh làm điều này với shift$IFShóa ra đây là phương pháp đáng tin cậy nhất gần như tôi có thể hình dung. Đây là điều tương tự poor_mans_readlink của Gilles - và nó được thực hiện tốt.

Nó sẽ lặp lại quá trình này trong một vòng lặp cho đến khi tên tệp được trả về lschắc chắn không phải là một liên kết mềm. Tại thời điểm đó, nó chuẩn hóa đường dẫn đó như trước với cdsau đó in.

Ví dụ sử dụng:

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

ĐẦU RA

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

Hoặc có thể ...

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

zdlm=\\0 zpath * | cat -A

ĐẦU RA

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.