เส้นทางสัมบูรณ์ของสคริปต์ Bash กับ OS X


104

ฉันกำลังพยายามหาเส้นทางที่แน่นอนไปยังสคริปต์ที่กำลังทำงานอยู่บน OS X

ฉันเห็นการตอบกลับreadlink -f $0มากมาย อย่างไรก็ตามเนื่องจาก OS X readlinkเหมือนกับ BSD จึงไม่สามารถใช้งานได้ (ใช้ได้กับเวอร์ชันของ GNU)

มีวิธีแก้ปัญหาแบบสำเร็จรูปหรือไม่?





16
$( cd "$(dirname "$0")" ; pwd -P )
Jason S

คำตอบ:


95

มีrealpath()ฟังก์ชัน C ที่ใช้งานได้ แต่ฉันไม่เห็นอะไรเลยในบรรทัดคำสั่ง นี่คือการเปลี่ยนที่รวดเร็วและสกปรก:

#!/bin/bash

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

realpath "$0"

สิ่งนี้จะพิมพ์เส้นทางคำต่อคำหากขึ้นต้นด้วย/. ถ้าไม่ใช่มันจะต้องเป็นพา ธ สัมพัทธ์มันจึง$PWDนำหน้าไปข้างหน้า #./ส่วนแถบออกจากด้านหน้าของ./$1


1
ฉันยังสังเกตเห็นฟังก์ชัน C แต่ไม่พบไบนารีหรืออะไรเลย อย่างไรก็ตามฟังก์ชั่นของคุณก็เหมาะกับความต้องการของฉันเป็นอย่างดี ขอบคุณ!
The Mighty Rubber Duck

7
โปรดทราบว่าสิ่งนี้ไม่ได้หักล้างลิงก์สัญลักษณ์
Adam Vandenberg

18
realpath ../somethingผลตอบแทน$PWD/../something
Lri

สิ่งนี้ใช้ได้ผลสำหรับฉัน แต่ฉันเปลี่ยนชื่อเป็น "realpath_osx ()" เนื่องจากฉันอาจต้องส่งสคริปต์ทุบตีนี้ไปยังลินุกซ์เชลล์และไม่ต้องการให้มันชนกับ realpath "จริง"! (ฉันแน่ใจว่ามีวิธีที่หรูหรากว่านี้ แต่ฉันเป็นคนทุบตี n00b)
Andrew Theken

8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell

117

สามขั้นตอนง่ายๆเหล่านี้จะแก้ปัญหานี้และปัญหาอื่น ๆ ของ OS X:

  1. ติดตั้งHomebrew
  2. brew install coreutils
  3. grealpath .

(3) อาจเปลี่ยนเป็นเพียงrealpathดู (2) เอาต์พุต


3
วิธีนี้แก้ปัญหาได้ แต่ต้องติดตั้งสิ่งที่คุณอาจไม่ต้องการหรือต้องการหรืออาจไม่ได้อยู่ในระบบเป้าหมายเช่น OS X
Jason S

7
@JasonS GNU Coreutils ดีเกินไปที่จะไม่ใช้ รวมถึงระบบสาธารณูปโภคที่ยอดเยี่ยมมากมาย เป็นเรื่องที่ดีมากที่เคอร์เนล Linux ไม่มีประโยชน์หากไม่มีมันและทำไมบางคนถึงเรียกมันว่า GNU / Linux Coreutils น่ากลัว คำตอบที่ดีควรเป็นคำตอบที่เลือก

7
@omouse คำถามกล่าวถึง 'โซลูชันนอกกรอบ' โดยเฉพาะ (สำหรับ OS X) ไม่ว่า coreutils จะยอดเยี่ยมแค่ไหน แต่ก็ไม่ได้ 'นอกกรอบ' บน OS X ดังนั้นคำตอบจึงไม่ใช่สิ่งที่ดี ไม่ใช่คำถามว่า coreutils ดีแค่ไหนหรือดีกว่า OS X หรือไม่มีโซลูชันแบบ 'นอกกรอบ' ที่ใช้$( cd "$(dirname "$0")" ; pwd -P )งานได้ดีสำหรับฉัน
Jason S

2
"คุณลักษณะ" อย่างหนึ่งของ OS X คือไดเร็กทอรีและชื่อไฟล์ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่ เป็นผลให้ถ้าคุณมีไดเรกทอรีที่เรียกว่าXXXและใครบางคนcd xxxจากนั้นจะกลับมาpwd .../xxxยกเว้นสำหรับคำตอบนี้ทั้งหมดของการแก้ปัญหาดังกล่าวข้างต้นส่งกลับเมื่อสิ่งที่คุณต้องการจริงๆคือxxx XXXขอบคุณ!
Andrew

1
ฉันไม่รู้ว่าการคัดลอกการวางและการสร้างโค้ดใหม่ ๆ อย่างไม่มีที่สิ้นสุดนั้นดีกว่าโซลูชันตัวจัดการแพ็คเกจที่มีอยู่อย่างไร หากคุณจำเป็นrealpathแล้วสิ่งที่เกิดขึ้นเมื่อคุณเกือบจะแน่นอนจะต้องมีรายการอื่น ๆ จากcoreutils? เขียนฟังก์ชันเหล่านั้นใหม่ใน bash ด้วยหรือไม่? : P
Ezekiel Victor

28

ฮึ. ฉันพบว่าคำตอบก่อนหน้านี้ค่อนข้างต้องการเหตุผลบางประการ: โดยเฉพาะอย่างยิ่งคำตอบเหล่านี้ไม่สามารถแก้ไขลิงก์สัญลักษณ์หลายระดับได้และคำตอบเหล่านี้เป็น "Bash-y" อย่างยิ่ง ขณะที่คำถามเดิมไม่ชัดเจนขอ "ทุบตีสคริปต์" ก็ยังทำให้การพูดถึง Mac OS X ของ BSD readlinkเหมือนไม่ใช่ ต่อไปนี้เป็นความพยายามในการพกพาที่สมเหตุสมผล (ฉันได้ตรวจสอบด้วย bash เป็น 'sh' และ dash) แก้ไขจำนวนลิงก์สัญลักษณ์โดยพลการ และควรใช้กับช่องว่างในพา ธ แม้ว่าฉันจะไม่แน่ใจในพฤติกรรมว่ามีพื้นที่สีขาวซึ่งเป็นชื่อฐานของยูทิลิตี้หรือไม่ดังนั้นบางทีอืมหลีกเลี่ยงสิ่งนั้น

#!/bin/sh
realpath() {
  OURPWD=$PWD
  cd "$(dirname "$1")"
  LINK=$(readlink "$(basename "$1")")
  while [ "$LINK" ]; do
    cd "$(dirname "$LINK")"
    LINK=$(readlink "$(basename "$1")")
  done
  REALPATH="$PWD/$(basename "$1")"
  cd "$OURPWD"
  echo "$REALPATH"
}
realpath "$@"

หวังว่าจะมีประโยชน์กับใครบางคน


1
ฉันอยากจะแนะนำให้ใช้localสำหรับตัวแปรที่กำหนดไว้ภายในฟังก์ชันเท่านั้นเพื่อไม่ให้เนมสเปซทั่วโลกเป็นมลพิษ เช่นlocal OURPWD=.... ทำงานอย่างน้อยสำหรับทุบตี
Michael Paesold

นอกจากนี้โค้ดไม่ควรใช้ตัวพิมพ์ใหญ่สำหรับตัวแปรส่วนตัว ตัวแปรตัวพิมพ์ใหญ่สงวนไว้สำหรับการใช้งานระบบ
tripleee

ขอบคุณสำหรับสคริปต์ ในกรณีที่ลิงก์และไฟล์จริงมีชื่อฐานที่แตกต่างกันอาจเป็นความคิดที่ดีที่จะเพิ่มBASENAME=$(basename "$LINK") ภายใน while และใช้สิ่งนั้นในตัวตั้งค่า LINK ตัวที่สองและตัวตั้งค่า REALPATH
stroborobo

สิ่งนี้ไม่ได้จัดการ symlinks และการ..อ้างอิงผู้ปกครองในแบบที่realpathต้องการ เมื่อcoreutilsติดตั้งhomebrew ln -s /var/log /tmp/linkexampleแล้วลองrealpath /tmp/linkexample/../; ภาพพิมพ์/private/varนี้ แต่ฟังก์ชันของคุณสร้างขึ้น/tmp/linkexample/..แทนเนื่องจาก..ไม่ใช่ symlink
Martijn Pieters

10

ตัวแปรที่เป็นมิตรกับบรรทัดคำสั่งเพิ่มเติมของโซลูชัน Python:

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

2
ในกรณีที่มีใครบ้าพอที่จะเริ่มล่าม python สำหรับคำสั่งเดียว ...
Bachsau

2
ฉันบ้าพอ แต่ใช้python -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain

7

ฉันกำลังมองหาวิธีแก้ปัญหาสำหรับใช้ในสคริปต์การจัดเตรียมระบบกล่าวคือเรียกใช้ก่อนที่จะติดตั้ง Homebrew ด้วยซ้ำ หากขาดวิธีแก้ปัญหาที่เหมาะสมฉันก็แค่ยกเลิกงานไปเป็นภาษาข้ามแพลตฟอร์มเช่น Perl:

script_abspath=$(perl -e 'use Cwd "abs_path"; print abs_path(@ARGV[0])' -- "$0")

บ่อยขึ้นสิ่งที่เราต้องการคือไดเร็กทอรีที่มี:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")

เยี่ยมมาก! Perl ดีสำหรับค่าใช้จ่ายเล็กน้อย! FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')")คุณอาจจะลดความซับซ้อนของรุ่นแรกที่จะ เหตุผลใด ๆ ที่ต่อต้าน?
F Pereira

1
@FPereira การสร้างรหัสโปรแกรมจากสตริงที่ผู้ใช้จัดหาโดยไม่ใช้ Escape ไม่ใช่ความคิดที่ดี ''ไม่สามารถกันกระสุนได้ จะแตกหาก$0มีใบเสนอราคาเดียวตัวอย่างเช่น ตัวอย่างง่ายๆ: ลองใช้เวอร์ชันของคุณ/tmp/'/test.shและโทร/tmp/'/test.shตามเส้นทางแบบเต็ม
4ae1e1

หรือง่ายกว่า/tmp/'.shนั้น
4ae1e1

6

เนื่องจากมีrealpathตามที่คนอื่น ๆ ชี้ให้เห็น:

// realpath.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[])
{
  if (argc > 1) {
    for (int argIter = 1; argIter < argc; ++argIter) {
      char *resolved_path_buffer = NULL;
      char *result = realpath(argv[argIter], resolved_path_buffer);

      puts(result);

      if (result != NULL) {
        free(result);
      }
    }
  }

  return 0;
}

Makefile:

#Makefile
OBJ = realpath.o

%.o: %.c
      $(CC) -c -o $@ $< $(CFLAGS)

realpath: $(OBJ)
      gcc -o $@ $^ $(CFLAGS)

จากนั้นคอมไพล์makeและใส่ซอฟต์ลิงค์ด้วย:
ln -s $(pwd)/realpath /usr/local/bin/realpath


เราทำได้gcc realpath.c -o /usr/local/bin/realpathไหม
Alexander Mills

1
@AlexanderMills คุณไม่ควรเรียกใช้คอมไพเลอร์ในฐานะรูท แล้วถ้าคุณไม่ทำคุณจะไม่มีสิทธิ์เขียนถึง/usr/local/bin
tripleee


3
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnameจะให้ชื่อไดเร็กทอรีของ/path/to/fileเช่น/path/to.

cd /path/to; pwd ทำให้แน่ใจว่าเส้นทางนั้นแน่นอน

basenameจะให้เพียงชื่อไฟล์ใน/path/to/fileเช่นfile.


แม้ว่ารหัสนี้อาจตอบคำถาม แต่การให้บริบทเพิ่มเติมเกี่ยวกับสาเหตุและ / หรือวิธีที่รหัสนี้ตอบคำถามช่วยเพิ่มมูลค่าในระยะยาว
Igor F.

1

ดังที่คุณเห็นด้านบนฉันถ่ายภาพนี้เมื่อประมาณ 6 เดือนที่แล้ว ฉันลืมเรื่องนี้ไปโดยสิ้นเชิงจนกระทั่งพบว่าตัวเองต้องการสิ่งที่คล้ายกันอีกครั้ง ฉัน ตกใจมากที่เห็นว่ามันเป็นพื้นฐานแค่ไหน ฉันได้สอนตัวเองให้เขียนโค้ดอย่างเข้มข้นมาประมาณหนึ่งปีแล้ว แต่ฉันมักจะรู้สึกว่าบางทีฉันอาจจะไม่ได้เรียนรู้อะไรเลยเมื่อสิ่งต่างๆเลวร้ายที่สุด

ฉันจะลบ 'วิธีแก้ปัญหา' ด้านบน แต่ฉันชอบที่จะเป็นบันทึกว่าฉันได้เรียนรู้มากแค่ไหนในช่วงสองสามเดือนที่ผ่านมา

แต่ฉันพูดนอกเรื่อง ฉันนั่งลงและทำงานทั้งหมดเมื่อคืนนี้ คำอธิบายในความคิดเห็นควรเพียงพอ หากคุณต้องการติดตามสำเนาที่ฉันกำลังดำเนินการอยู่คุณสามารถทำตามส่วนสำคัญนี้ นี่อาจเป็นสิ่งที่คุณต้องการ

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

1
ดูเหมือนว่าลิงก์ส่วนสำคัญจะเสีย
Alex

คำตอบนี้ใช้ได้: stackoverflow.com/a/46772980/1223975 .... คุณควรใช้สิ่งนั้น
Alexander Mills

โปรดทราบว่าการใช้งานของคุณไม่ได้แก้ไขลิงก์สัญลักษณ์ก่อน..การอ้างอิงระดับบนสุด เช่น/foo/link_to_other_directory/..ได้รับการแก้ไขแล้ว/fooแทนที่จะเป็นพาเรนต์ของเส้นทางใดก็ตามที่ symlink /foo/link_to_other_directoryชี้ไป readlink -fและrealpathแก้ไขคอมโพเนนต์พา ธ แต่ละรายการโดยเริ่มต้นที่รูทและอัพเดตเป้าหมายลิงก์ที่เตรียมไว้ล่วงหน้าไปยังส่วนที่เหลือที่กำลังประมวลผล ฉันได้เพิ่มคำตอบสำหรับคำถามนี้โดยนำตรรกะนั้นมาใช้ใหม่
Martijn Pieters

1

realpath สำหรับ Mac OS X

realpath() {
    path=`eval echo "$1"`
    folder=$(dirname "$path")
    echo $(cd "$folder"; pwd)/$(basename "$path"); 
}

ตัวอย่างกับเส้นทางที่เกี่ยวข้อง:

realpath "../scripts/test.sh"

ตัวอย่างกับโฮมโฟลเดอร์

realpath "~/Test/../Test/scripts/test.sh"

1
ทางออกง่ายๆที่ดีฉันเพิ่งพบข้อแม้หนึ่งข้อเมื่อเรียกใช้มัน..ไม่ได้ให้คำตอบที่ถูกต้องดังนั้นฉันจึงเพิ่มการตรวจสอบว่าเส้นทางที่กำหนดเป็นไดเร็กทอรีหรือไม่: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
herbert

ไม่ได้ผลสำหรับฉันในขณะที่"$(dirname $(dirname $(realpath $0)))"ทำงานดังนั้นต้องการอย่างอื่น ...
Alexander Mills

ใช้ไร้ประโยชน์echoจะยังไม่ได้รับการปั่นหัว
tripleee

สิ่งนี้ไม่ได้แก้ไข symlinks ในแบบที่ realpath ต้องการ มันแก้ไข..การอ้างอิงผู้ปกครองก่อนที่จะแก้ไข symlinks ไม่ใช่หลังจาก; ลองนี้กับ homebrew coreutilsติดตั้งสร้างการเชื่อมโยงกับการln -s /var/log /tmp/linkexampleทำงานแล้วrealpath /tmp/linkexample/../; ภาพพิมพ์/private/varนี้ แต่ฟังก์ชันของคุณจะสร้างขึ้น/tmp/linkexample/..แทนเนื่องจากpwdยังคงแสดงอยู่/tmp/linkexampleหลังซีดี
Martijn Pieters

1

ใน macOS วิธีแก้ปัญหาเดียวที่ฉันพบสิ่งนี้ที่จัดการกับลิงก์สัญลักษณ์ได้อย่างน่าเชื่อถือคือการใช้realpathไฟล์. เนื่องจากสิ่งนี้ต้องการbrew install coreutilsฉันจึงทำขั้นตอนนั้นโดยอัตโนมัติ การใช้งานของฉันมีลักษณะดังนี้:

#!/usr/bin/env bash

set -e

if ! which realpath >&/dev/null; then
  if ! which brew >&/dev/null; then
    msg="ERROR: This script requires brew. See https://brew.sh for installation instructions."
    echo "$(tput setaf 1)$msg$(tput sgr0)" >&2
    exit 1
  fi
  echo "Installing coreutils/realpath"
  brew install coreutils >&/dev/null
fi

thisDir=$( dirname "`realpath "$0"`" )
echo "This script is run from \"$thisDir\""


ข้อผิดพลาดนี้หากไม่ได้brewติดตั้ง แต่คุณสามารถติดตั้งได้เช่นกัน ฉันไม่รู้สึกสบายใจที่จะทำบางสิ่งที่ม้วนงอรหัสทับทิมโดยพลการจากเน็ตโดยอัตโนมัติ

หมายเหตุว่านี้รูปแบบอัตโนมัติต่อ Oleg Mikheev ของคำตอบ


การทดสอบที่สำคัญอย่างหนึ่ง

การทดสอบที่ดีอย่างหนึ่งของโซลูชันเหล่านี้คือ:

  1. วางโค้ดไว้ในไฟล์สคริปต์ที่ไหนสักแห่ง
  2. ในไดเร็กทอรีอื่น symlink ( ln -s) ไปยังไฟล์นั้น
  3. เรียกใช้สคริปต์จากลิงก์สัญลักษณ์นั้น

โซลูชันนั้นยกเลิกการอ้างอิง symlink และให้ไดเร็กทอรีดั้งเดิมแก่คุณหรือไม่? ถ้าเป็นเช่นนั้นก็ใช้ได้


0

ดูเหมือนว่าจะใช้ได้กับ OSX ไม่ต้องใช้ไบนารีใด ๆ และถูกดึงออกมาจากที่นี่

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

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

0

ฉันชอบสิ่งนี้:

#!/usr/bin/env bash
function realpath() {
    local _X="$PWD"
    local _LNK=$1
    cd "$(dirname "$_LNK")"
    if [ -h "$_LNK" ]; then
        _LNK="$(readlink "$_LNK")"
        cd "$(dirname "$_LNK")"
    fi
    echo "$PWD/$(basename "$_LNK")"
    cd "$_X"
}

0

ผมจำเป็นต้องมีrealpathการเปลี่ยนใน OS X หนึ่งที่ดำเนินการอย่างถูกต้องในเส้นทางที่มี symlinks และการอ้างอิงผู้ปกครองเช่นเดียวreadlink -fหากว่า ซึ่งรวมถึงการแก้ไขลิงก์สัญลักษณ์ในเส้นทางก่อนที่จะแก้ไขการอ้างอิงระดับบนสุด เช่นหากคุณติดตั้งcoreutilsขวดhomebrew แล้วให้เรียกใช้:

$ ln -s /var/log/cups /tmp/linkeddir  # symlink to another directory
$ greadlink -f /tmp/linkeddir/..      # canonical path of the link parent
/private/var/log

โปรดทราบว่าreadlink -fได้แก้ไขแล้ว/tmp/linkeddir ก่อนที่จะแก้ไขการ..อ้างอิง dir หลัก แน่นอนว่าไม่มีreadlink -fบน Mac เช่นกัน

ดังนั้นในฐานะที่เป็นส่วนหนึ่งของการใช้งาน bash สำหรับrealpathฉันนำสิ่งที่canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)เรียกใช้ฟังก์ชัน GNUlib มาใช้ใหม่ใน Bash 3.2; นี่คือการเรียกใช้ฟังก์ชันที่ GNU readlink -fทำให้:

# shellcheck shell=bash
set -euo pipefail

_contains() {
    # return true if first argument is present in the other arguments
    local elem value

    value="$1"
    shift

    for elem in "$@"; do 
        if [[ $elem == "$value" ]]; then
            return 0
        fi
    done
    return 1
}

_canonicalize_filename_mode() {
    # resolve any symlink targets, GNU readlink -f style
    # where every path component except the last should exist and is
    # resolved if it is a symlink. This is essentially a re-implementation
    # of canonicalize_filename_mode(path, CAN_ALL_BUT_LAST).
    # takes the path to canonicalize as first argument

    local path result component seen
    seen=()
    path="$1"
    result="/"
    if [[ $path != /* ]]; then  # add in current working dir if relative
        result="$PWD"
    fi
    while [[ -n $path ]]; do
        component="${path%%/*}"
        case "$component" in
            '') # empty because it started with /
                path="${path:1}" ;;
            .)  # ./ current directory, do nothing
                path="${path:1}" ;;
            ..) # ../ parent directory
                if [[ $result != "/" ]]; then  # not at the root?
                    result="${result%/*}"      # then remove one element from the path
                fi
                path="${path:2}" ;;
            *)
                # add this component to the result, remove from path
                if [[ $result != */ ]]; then
                    result="$result/"
                fi
                result="$result$component"
                path="${path:${#component}}"
                # element must exist, unless this is the final component
                if [[ $path =~ [^/] && ! -e $result ]]; then
                    echo "$1: No such file or directory" >&2
                    return 1
                fi
                # if the result is a link, prefix it to the path, to continue resolving
                if [[ -L $result ]]; then
                    if _contains "$result" "${seen[@]+"${seen[@]}"}"; then
                        # we've seen this link before, abort
                        echo "$1: Too many levels of symbolic links" >&2
                        return 1
                    fi
                    seen+=("$result")
                    path="$(readlink "$result")$path"
                    if [[ $path = /* ]]; then
                        # if the link is absolute, restart the result from /
                        result="/"
                    elif [[ $result != "/" ]]; then
                        # otherwise remove the basename of the link from the result
                        result="${result%/*}"
                    fi
                elif [[ $path =~ [^/] && ! -d $result ]]; then
                    # otherwise all but the last element must be a dir
                    echo "$1: Not a directory" >&2
                    return 1
                fi
                ;;
        esac
    done
    echo "$result"
}

ซึ่งรวมถึงการตรวจจับ symlink แบบวงกลมออกหากเห็นเส้นทางเดียวกัน (ตัวกลาง) สองครั้ง

หากสิ่งที่คุณต้องการคือreadlink -fคุณสามารถใช้สิ่งต่อไปนี้:

readlink() {
    if [[ $1 != -f ]]; then  # poor-man's option parsing
        # delegate to the standard readlink command
        command readlink "$@"
        return
    fi

    local path result seenerr
    shift
    seenerr=
    for path in "$@"; do
        # by default readlink suppresses error messages
        if ! result=$(_canonicalize_filename_mode "$path" 2>/dev/null); then
            seenerr=1
            continue
        fi
        echo "$result"
    done
    if [[ $seenerr ]]; then
        return 1;
    fi
}

สำหรับrealpathฉันยังต้องการ--relative-toและ--relative-baseการสนับสนุนซึ่งให้เส้นทางสัมพัทธ์แก่คุณหลังจากการกำหนดมาตรฐาน:

_realpath() {
    # GNU realpath replacement for bash 3.2 (OS X)
    # accepts --relative-to= and --relative-base options
    # and produces canonical (relative or absolute) paths for each
    # argument on stdout, errors on stderr, and returns 0 on success
    # and 1 if at least 1 path triggered an error.

    local relative_to relative_base seenerr path

    relative_to=
    relative_base=
    seenerr=

    while [[ $# -gt 0 ]]; do
        case $1 in
            "--relative-to="*)
                relative_to=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            "--relative-base="*)
                relative_base=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            *)
                break;;
        esac
    done

    if [[
        -n $relative_to
        && -n $relative_base
        && ${relative_to#${relative_base}/} == "$relative_to"
    ]]; then
        # relative_to is not a subdir of relative_base -> ignore both
        relative_to=
        relative_base=
    elif [[ -z $relative_to && -n $relative_base ]]; then
        # if relative_to has not been set but relative_base has, then
        # set relative_to from relative_base, simplifies logic later on
        relative_to="$relative_base"
    fi

    for path in "$@"; do
        if ! real=$(_canonicalize_filename_mode "$path"); then
            seenerr=1
            continue
        fi

        # make path relative if so required
        if [[
            -n $relative_to
            && ( # path must not be outside relative_base to be made relative
                -z $relative_base || ${real#${relative_base}/} != "$real"
            )
        ]]; then
            local common_part parentrefs

            common_part="$relative_to"
            parentrefs=
            while [[ ${real#${common_part}/} == "$real" ]]; do
                common_part="$(dirname "$common_part")"
                parentrefs="..${parentrefs:+/$parentrefs}"
            done

            if [[ $common_part != "/" ]]; then
                real="${parentrefs:+${parentrefs}/}${real#${common_part}/}"
            fi
        fi

        echo "$real"
    done
    if [[ $seenerr ]]; then
        return 1
    fi
}

if ! command -v realpath > /dev/null 2>&1; then
    # realpath is not available on OSX unless you install the `coreutils` brew
    realpath() { _realpath "$@"; }
fi

ฉันรวมการทดสอบหน่วยไว้ในคำขอตรวจสอบรหัสของฉันสำหรับรหัสนี้


-2

จากการสื่อสารกับผู้แสดงความคิดเห็นฉันยอมรับว่ามันยากมากและไม่มีทางที่จะใช้งาน realpath ได้โดยสิ้นเชิงกับ Ubuntu

แต่รุ่นต่อไปนี้สามารถจัดการกับเคสเข้ามุมได้คำตอบที่ดีที่สุดไม่สามารถตอบสนองความต้องการประจำวันของฉันบน macbook ได้ ใส่รหัสนี้ลงใน ~ / .bashrc ของคุณและจำไว้ว่า:

  • arg สามารถเป็นไฟล์หรือ dir ได้ 1 ไฟล์เท่านั้นไม่มีสัญลักษณ์แทน
  • ไม่มีช่องว่างใน dir หรือชื่อไฟล์
  • อย่างน้อยไฟล์หรือ dir หลักของ dir ก็มีอยู่
  • อย่าลังเลที่จะใช้ .. / สิ่งเหล่านี้ปลอดภัย

    # 1. if is a dir, try cd and pwd
    # 2. if is a file, try cd its parent and concat dir+file
    realpath() {
     [ "$1" = "" ] && return 1

     dir=`dirname "$1"`
     file=`basename "$1"`

     last=`pwd`

     [ -d "$dir" ] && cd $dir || return 1
     if [ -d "$file" ];
     then
       # case 1
       cd $file && pwd || return 1
     else
       # case 2
       echo `pwd`/$file | sed 's/\/\//\//g'
     fi

     cd $last
    }

คุณต้องการหลีกเลี่ยงการใช้echoไฟล์. ก็pwdเหมือนกับการecho $(pwd)ไม่ต้องวางไข่สำเนาที่สองของเชลล์ นอกจากนี้การไม่อ้างถึงอาร์กิวเมนต์echoเป็นจุดบกพร่อง (คุณจะสูญเสียช่องว่างที่นำหน้าหรือต่อท้ายอักขระช่องว่างภายในใด ๆ ที่อยู่ติดกันและมีการขยายสัญลักษณ์แทนเป็นต้น) ดูเพิ่มเติมstackoverflow.com/questions/10067266/…
tripleee

นอกจากนี้พฤติกรรมสำหรับเส้นทางที่ไม่มีอยู่จริงก็เป็นบั๊กกี้ แต่ฉันเดาว่านั่นคือสิ่งที่ประโยค "แต่จำไว้" อาจจะพยายามพูด แม้ว่าพฤติกรรมบน Ubuntu จะไม่พิมพ์ไดเร็กทอรีปัจจุบันเมื่อคุณร้องขอrealpathไดเร็กทอรีที่ไม่มีอยู่
tripleee

เพื่อความสอดคล้องอาจต้องการdir=$(dirname "$1"); file=$(basename "$1")แทนที่จะใช้ไวยากรณ์ backtick ที่ล้าสมัย นอกจากนี้โปรดสังเกตการอ้างถึงข้อโต้แย้งที่ถูกต้องอีกครั้ง
tripleee

คำตอบที่อัปเดตของคุณดูเหมือนจะล้มเหลวในการแก้ไขข้อบกพร่องจำนวนมากและเพิ่มคำตอบใหม่
tripleee

โปรดให้บางกรณีที่ล้มเหลวโดยเฉพาะฉันเนื่องจากการทดสอบทั้งหมดที่ฉันทำในเดสก์ท็อป ubuntu 18.04 นั้นโอเคขอบคุณ
occia
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.