วิธีแก้ไขลิงก์สัญลักษณ์ในเชลล์สคริปต์


220

กำหนดเส้นทางแบบสัมบูรณ์หรือสัมพัทธ์ (ในระบบเหมือน Unix) ฉันต้องการกำหนดเส้นทางแบบเต็มของเป้าหมายหลังจากแก้ไข symlink ระดับกลางใด ๆ คะแนนโบนัสสำหรับการแก้ไข ~ เครื่องหมายของชื่อผู้ใช้ในเวลาเดียวกัน

หากเป้าหมายคือไดเรกทอรีอาจเป็นไปได้ที่จะ chdir () ลงในไดเรกทอรีแล้วเรียกใช้ getcwd () แต่ฉันต้องการทำสิ่งนี้จากเชลล์สคริปต์แทนที่จะเขียนตัวช่วย C น่าเสียดายที่ shells มีแนวโน้มที่จะพยายามซ่อนการมี symlink จากผู้ใช้ (นี่คือ bash บน OS X):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

สิ่งที่ฉันต้องการคือการแก้ไขฟังก์ชั่น () เช่นนั้นเมื่อดำเนินการจากไดเรกทอรี tmp ในตัวอย่างข้างต้นแก้ไข ("foo") == "/ Users / greg / tmp / bar"

คำตอบ:


92

ตามมาตรฐานpwd -Pควรกลับเส้นทางที่มี symlinks แก้ไข

ฟังก์ชั่น C char *getcwd(char *buf, size_t size)จากunistd.hควรมีพฤติกรรมเดียวกัน

getcwd pwd


22
มันใช้งานได้กับไดเรกทอรี (ปัจจุบัน) ใช่ไหม ถ้าเป้าหมายเป็นไฟล์มันจะไม่ให้อะไรเลย ...
dala

4
ใช้งานไม่ได้หากลิงก์เสียเนื่องจากคุณไม่สามารถทำให้เป็นเส้นทางปัจจุบันของคุณได้
Tom Howard

6
เพื่อสรุปกับประโยชน์ของการหวน: คำตอบนี้จะทำงานเฉพาะในสถานการณ์ที่ จำกัด มากคือถ้า symlink ที่น่าสนใจคือไปยังไดเรกทอรีที่จริงมีอยู่ ; บวกคุณจะต้องไปครั้งแรกก่อนที่จะเรียกcd pwd -Pในคำอื่น ๆ : มันจะไม่ช่วยให้คุณสามารถแก้ไข (ดูเป้าหมายของ) symlinks ไปยังไฟล์หรือของเสีย symlinks และสำหรับการแก้ปัญหาที่มีอยู่ symlinks ไดเรกทอรีที่คุณต้องทำผลงานเพิ่มเติม (dir เรียกคืนการทำงานก่อนหน้านี้หรือ จำกัด วงcdและpwd -Pโทร ใน subshell)
mklement0

เป็นไม่ดีฉันมองหาวิธีแก้ไขไฟล์และไม่ใช่ไดเรกทอรี
มาร์ติน

อย่างที่คนอื่น ๆ ชี้ไปนี่ไม่ได้ตอบคำถามจริงๆ คำตอบของ @ pixelbeat ด้านล่างทำ
erstaples

402
readlink -f "$path"

หมายเหตุของบรรณาธิการ: การทำงานข้างต้นทำงานร่วมกับGNU readlinkและFreeBSD / PC-BSD / OpenBSD readlinkแต่ไม่ใช่ใน OS X ตั้งแต่ 10.11
GNU readlinkเสนอตัวเลือกเพิ่มเติมที่เกี่ยวข้องเช่น-mการแก้ไข symlink ว่ามีเป้าหมายขั้นสุดท้ายหรือไม่

หมายเหตุตั้งแต่ GNU coreutils 8.15 (2012-01-06) มีโปรแกรมเรียลพา ธที่มีป้านน้อยกว่าและยืดหยุ่นกว่าด้านบน มันยังเข้ากันได้กับ FreeBSD ที่ใช้ชื่อเดียวกัน นอกจากนี้ยังมีฟังก์ชันการทำงานเพื่อสร้างเส้นทางสัมพัทธ์ระหว่างสองไฟล์

realpath $path

[นอกจากนี้ผู้ดูแลระบบด้านล่างจากความคิดเห็นโดยhalloleo - danorton]

สำหรับ Mac OS X (อย่างน้อย 10.11.x) ให้ใช้readlinkโดยไม่มี-fตัวเลือก:

readlink $path

หมายเหตุบรรณาธิการ: นี่จะไม่แก้ไข symlink ซ้ำดังนั้นจึงไม่รายงานเป้าหมายขั้นสุดท้าย เช่นได้รับ symlink aซึ่งชี้ไปbที่ซึ่งจะชี้ไปที่cสิ่งนี้จะรายงานเท่านั้นb(และจะไม่ตรวจสอบให้แน่ใจว่ามันเป็นเอาท์พุทเป็นเส้นทางที่แน่นอน )
ใช้perlคำสั่งต่อไปนี้บน OS X เพื่อเติมเต็มช่องว่างของreadlink -fฟังก์ชันที่หายไป:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"


5
สิ่งนี้ไม่ทำงานใน Mac OS X - ดูstackoverflow.com/questions/1055671/…
Bkkbrad

1
อับอายเกี่ยวกับความเข้ากันไม่ได้ของ OS X, มิฉะนั้นจะดี +1
jkp

11
บน OS X คุณสามารถติดตั้ง coreutils ด้วย homebrew มันติดตั้งมันเป็น "grealpath"
Kief

10
readlinkทำงานบน OSX แต่ต้องการไวยากรณ์อื่น: readlink $path โดยไม่ต้อง -f
halloleo

2
readlink ไม่สามารถยกเลิกการอ้างอิง symlink หลายเลเยอร์ได้ แต่จะยกเลิกการลงทะเบียนทีละเลเยอร์
Magnus

26

"pwd -P" ดูเหมือนว่าจะทำงานถ้าคุณต้องการไดเรกทอรี แต่ถ้าด้วยเหตุผลบางอย่างที่คุณต้องการชื่อของไฟล์ปฏิบัติการจริงฉันไม่คิดว่ามันจะช่วยได้ นี่คือทางออกของฉัน:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

17

หนึ่งในรายการโปรดของฉันคือ realpath foo

realpath - ส่งคืนชื่อพา ธ สัมบูรณ์ที่ได้รับการยอมรับ

realpath ขยายลิงก์สัญลักษณ์ทั้งหมดและแก้ไขการอ้างอิงถึง '/./', '/../' และอักขระพิเศษ '/' ในสตริงที่สิ้นสุดด้วยค่า null ซึ่งตั้งชื่อตามเส้นทางและ
       เก็บชื่อพา ธ สัมบูรณ์ที่เป็นที่ยอมรับในบัฟเฟอร์ในขนาด PATH_MAX ที่ตั้งชื่อตาม resol_path เส้นทางผลลัพธ์จะไม่มีลิงก์สัญลักษณ์ '/./' หรือ
       ส่วนประกอบ '/../'

บน Debian (etch and later) คำสั่งนี้มีอยู่ในแพ็คเกจจริง
Phil Ross

2
realpath อยู่ในขณะนี้ (ม.ค. 2012) ส่วนหนึ่งของ coreutils และย้อนกลับเข้ากันได้กับ debian และตัวแปร BSD
pixelbeat

1
ฉันไม่มีrealpathCentos 6 กับ GNU coreutils 8.4.31 ผมเคยเจอคนอื่น ๆ หลายUnix และ Linuxที่มี coreutils GNU บรรจุโดยไม่ต้อง realpathดังนั้นดูเหมือนว่าจะขึ้นอยู่กับมากกว่าแค่รุ่น
toxalot

ฉันชอบrealpathมากกว่าreadlinkเพราะมีตัวเลือกธงเช่น--relative-to
arr_sea

10
readlink -e [filepath]

ดูเหมือนจะเป็นสิ่งที่คุณขออย่างแน่นอน - มันยอมรับเส้นทางอาร์บิท, แก้ไข symlink ทั้งหมดและส่งคืนเส้นทาง "ของจริง" - และเป็น "มาตรฐาน * ระวัง" ที่มีแนวโน้มว่าระบบทั้งหมดมีอยู่แล้ว


เพิ่งถูกมันกัด มันไม่ทำงานบน Mac และฉันกำลังหาสิ่งทดแทน
dhill

5

อีกทางหนึ่ง:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

ฉันต้องการ cd ใน myreadlink () เพราะมันเป็นฟังก์ชั่นวนซ้ำไปที่แต่ละไดเรกทอรีจนกว่าจะพบลิงค์ หากพบลิงค์ให้ส่งคืน realpath จากนั้น sed จะแทนที่พา ธ
Keymon

5

การรวมโซลูชันที่ให้มาเข้าด้วยกันโดยรู้ว่า readlink นั้นมีอยู่ในระบบส่วนใหญ่ แต่ต้องการอาร์กิวเมนต์ที่แตกต่างกันมันใช้งานได้ดีสำหรับฉันบน OSX และ Debian ฉันไม่แน่ใจเกี่ยวกับระบบ BSD บางทีเงื่อนไขจะต้อง[[ $OSTYPE != darwin* ]]แยก-fออกจาก OSX เท่านั้น

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"

3

นี่คือวิธีที่หนึ่งจะได้รับเส้นทางที่แท้จริงไปยังไฟล์ใน MacOS / Unix โดยใช้สคริปต์ Perl แบบอินไลน์:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

เพื่อรับไดเร็กทอรีของไฟล์ symlinked:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

3

เส้นทางของคุณเป็นไดเรกทอรีหรืออาจเป็นไฟล์หรือไม่ ถ้าเป็นไดเรกทอรีมันง่าย:

(cd "$DIR"; pwd -P)

อย่างไรก็ตามหากไฟล์นั้นอาจเป็นไฟล์สิ่งนี้จะไม่ทำงาน:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

เนื่องจาก symlink อาจแก้ไขเป็นเส้นทางแบบเต็มหรือแบบเต็ม

ในสคริปต์ฉันต้องการค้นหาเส้นทางจริงเพื่อให้ฉันอาจอ้างอิงการกำหนดค่าหรือสคริปต์อื่น ๆ ที่ติดตั้งพร้อมกับมันฉันใช้สิ่งนี้:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

คุณสามารถตั้งค่าSOURCEเป็นพา ธ ไฟล์ใดก็ได้ โดยทั่วไปตราบใดที่พา ธ เป็น symlink จะแก้ไข symlink นั้น เคล็ดลับอยู่ในบรรทัดสุดท้ายของลูป ถ้า symlink SOURCEการแก้ไขเป็นที่แน่นอนก็จะใช้เป็น อย่างไรก็ตามถ้ามันเป็นแบบสัมพัทธ์มันจะเป็นการเติมDIRสำหรับมันซึ่งได้รับการแก้ไขในสถานที่จริงโดยเคล็ดลับง่ายๆที่ฉันอธิบายครั้งแรก


2
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..

สิ่งนี้จะตัดกับ (a) พา ธ ใด ๆ ที่มี whitespace หรือ shell metacharacters และ (b) symlink ที่ใช้งานไม่ได้ (ไม่ใช่ปัญหาหากคุณต้องการพา ธ พาเรนต์ของสคริปต์ที่รันอยู่สมมติว่า (a) ใช้ไม่ได้)
mklement0

หากคุณกังวลกับช่องว่างให้ใช้เครื่องหมายคำพูด
เดฟ

กรุณาทำ - แม้ว่าจะไม่ได้อยู่ (b)
mklement0

โปรดแสดงตัวอย่างข) ที่สิ่งนี้ล้มเหลว ตามนิยาม symlink ที่ใช้งานไม่ได้จะชี้ไปยังรายการไดเรกทอรีที่ไม่มีอยู่ จุดของสคริปต์นี้คือการแก้ไข symlink ในทิศทางอื่น หาก symlink เสียคุณจะไม่สามารถเรียกใช้งานสคริปต์ได้ ตัวอย่างนี้มีจุดประสงค์เพื่อสาธิตการแก้ไขสคริปต์ที่กำลังเรียกใช้งานในปัจจุบัน
เดฟ

"มีจุดประสงค์เพื่อแสดงให้เห็นถึงการแก้ไขสคริปต์ที่กำลังดำเนินการอยู่" - ซึ่งแน่นอนว่าเป็นการ จำกัด ขอบเขตของคำถามที่คุณเลือกที่จะมุ่งเน้น นั่นเป็นสิ่งที่ดีอย่างสมบูรณ์ตราบใดที่คุณพูดอย่างนั้น เนื่องจากคุณไม่ได้ฉันระบุไว้ในความคิดเห็นของฉัน โปรดแก้ไขปัญหาข้อความซึ่งเป็นปัญหาโดยไม่คำนึงถึงขอบเขตของคำตอบ
mklement0

2

หมายเหตุ: ฉันเชื่อว่านี่เป็นโซลูชันที่มั่นคงพกพาและพร้อมใช้งานซึ่งมีความยาวอย่างสม่ำเสมอด้วยเหตุผลอย่างนั้น

ด้านล่างนี้เป็นสคริปต์ / ฟังก์ชั่นที่รองรับ POSIX อย่างสมบูรณ์ซึ่งเป็นแพลตฟอร์มข้ามแพลตฟอร์ม (ทำงานบน macOS ด้วยซึ่งreadlinkยังไม่รองรับ-fตั้งแต่ 10.12 (Sierra)) - มันใช้คุณสมบัติภาษา POSIX เชลล์เท่านั้นเท่านั้นและเรียกใช้ยูทิลิตี้ POSIX ได้เท่านั้น .

เป็นการใช้งานแบบพกพาของ GNUreadlink -e (รุ่นที่เข้มงวดกว่าreadlink -f)

คุณสามารถเรียกใช้สคริปต์ด้วยshหรือแหล่งฟังก์ชั่นในbash, kshและzsh :

ตัวอย่างเช่นภายในสคริปต์คุณสามารถใช้ดังต่อไปนี้เพื่อรับไดเรกทอรีต้นกำเนิดจริงของสคริปต์ที่มีการเรียกใช้พร้อมกับ symlink ที่ได้รับการแก้ไข:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink คำจำกัดความของสคริปต์ / ฟังก์ชัน:

รหัสถูกดัดแปลงด้วยความขอบคุณจากคำตอบนี้
ฉันยังได้สร้างbashเวอร์ชันยูทิลิตี้แบบสแตนด์อโลนที่นี่ซึ่งคุณสามารถติดตั้งได้
npm install rreadlink -gหากคุณติดตั้ง Node.js

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

แทนเจนต์เกี่ยวกับความปลอดภัย:

jarno , ในการอ้างอิงถึงฟังก์ชั่นเพื่อให้แน่ใจว่า builtincommandไม่ได้ถูกเงาโดยนามแฝงหรือฟังก์ชั่นเชลล์ที่มีชื่อเดียวกัน, ถามในความคิดเห็น:

เกิดอะไรขึ้นถ้าunaliasหรือunsetและ[ถูกตั้งเป็นชื่อแทนหรือฟังก์ชั่นเปลือก?

แรงจูงใจเบื้องหลังการrreadlinkสร้างความมั่นใจว่าcommandมีความหมายดั้งเดิมคือการใช้เพื่อหลีกเลี่ยงความสะดวกสบายนามแฝงและมักใช้ในการทำเงาคำสั่งมาตรฐานในเชลล์แบบโต้ตอบเช่นการกำหนดใหม่lsเพื่อรวมตัวเลือกที่โปรดปราน

ฉันคิดว่ามันปลอดภัยที่จะพูดว่าเว้นแต่คุณจะจัดการกับสภาพแวดล้อมที่ไม่น่าเชื่อถือและเป็นอันตรายไม่ต้องกังวลเกี่ยวกับunaliasหรือunset- หรือสำหรับเรื่องwhileนั้นdo,, ... - การถูกนิยามใหม่ไม่ใช่ข้อกังวล

มีบางสิ่งที่ฟังก์ชั่นนั้นต้องพึ่งพาที่จะมีความหมายและพฤติกรรมดั้งเดิม - ไม่มีทางแก้ไข
เชลล์ที่คล้าย POSIX นั้นอนุญาตให้สร้างนิยามใหม่ของบิวอินและแม้แต่คำหลักภาษาก็คือเนื้อแท้เสี่ยงต่อความปลอดภัย (และเขียนรหัสหวาดระแวงยากทั่วไป)

ในการจัดการข้อกังวลของคุณโดยเฉพาะ:

ฟังก์ชั่นอาศัยunaliasและunsetมีความหมายดั้งเดิม ให้พวกเขานิยามใหม่เป็นฟังก์ชันเชลล์ในลักษณะที่เปลี่ยนแปลงพฤติกรรมของพวกเขาจะเป็นปัญหา การนิยามใหม่ในฐานะนามแฝงไม่จำเป็นต้องเป็นข้อกังวลเนื่องจากการอ้างชื่อคำสั่ง (เช่นส่วนหนึ่งของ) (เช่น\unalias ) จะข้ามนามแฝง

แต่อ้างเป็นไม่ได้เป็นตัวเลือกสำหรับเปลือกคำหลัก ( while, for, if, do, ... ) และในขณะที่คำหลักเปลือกทำจะมีความสำคัญมากกว่าเปลือกฟังก์ชั่นในbashและzshนามแฝงมีความสำคัญสูงสุดดังนั้นเพื่อป้องกัน redefinitions เปลือกคำหลักที่คุณต้องทำงานunaliasด้วย ชื่อของพวกเขา (แม้ว่าในนามแฝงที่ไม่ใช่การโต้ตอบ bash (เช่นสคริปต์) นามแฝงจะไม่ได้รับการขยายโดยค่าเริ่มต้น - เฉพาะถ้าshopt -s expand_aliasesเรียกอย่างชัดเจนก่อน)

เพื่อให้มั่นใจว่าunalias- ในฐานะที่เป็น builtin - มีความหมายดั้งเดิมคุณต้องใช้\unsetมันก่อนซึ่งต้องการให้unsetมีความหมายดั้งเดิม:

unsetเป็นเชลล์ในตัวดังนั้นเพื่อให้แน่ใจว่ามีการเรียกใช้เช่นนี้คุณต้องตรวจสอบให้แน่ใจว่ามันไม่ได้ถูกนิยามใหม่เป็นฟังก์ชั่นฟังก์ชั่นในขณะที่คุณสามารถข้ามฟอร์มนามแฝงด้วยการอ้างอิงคุณไม่สามารถข้ามแบบฟอร์มฟังก์ชันเชลล์ - catch 22

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


จากประสบการณ์ของฉันการอ้างอิงข้ามนามแฝงไม่ใช่ฟังก์ชันของเชลล์ซึ่งต่างจากที่คุณบอกไว้ก่อน
jarno

ฉันทดสอบว่าฉันสามารถกำหนด[เป็นนามแฝงในdashและbashและเป็นฟังก์ชั่นเปลือกbashมา
jarno

1
ฉันทำของฉันเป็นจุดแยกคำถาม
jarno

@jarno: คะแนนดี; ฉันอัพเดตคำตอบแล้ว แจ้งให้เราทราบหากคุณคิดว่ายังมีปัญหาอยู่
mklement0

1
@jarno: คุณสามารถกำหนดwhileเป็นฟังก์ชั่นในbash, kshและzsh(แต่ไม่dash) แต่มีเพียงfunction <name>ไวยากรณ์: function while { echo foo; }works ( while() { echo foo; }ไม่) อย่างไรก็ตามสิ่งนี้จะไม่ทำให้เกิดเงาwhile คำหลักเนื่องจากคำหลักมีความสำคัญสูงกว่าฟังก์ชั่น (วิธีเดียวที่จะเรียกใช้ฟังก์ชั่นนี้เป็น\while) ในbashและzshนามแฝงมีลำดับความสำคัญสูงกว่าคำหลักดังนั้นนามแฝงการกำหนดคำหลักใหม่จะทำหน้าที่เป็นเงา (แต่bashโดยค่าเริ่มต้นจะมีเฉพาะในเชลล์แบบโต้ตอบเท่านั้นเว้นแต่shopt -s expand_aliasesจะเรียกอย่างชัดเจน)
mklement0

1

เชลล์สคริปต์ทั่วไปมักจะต้องค้นหาไดเร็กทอรี "โฮม" ของตนแม้ว่าจะถูกเรียกใช้เป็น symlink สคริปต์ต้องค้นหาตำแหน่ง "ของจริง" ของพวกเขาจากเพียงแค่ $ 0

cat `mvn`

ในระบบของฉันพิมพ์สคริปต์ที่มีสิ่งต่อไปนี้ซึ่งควรเป็นคำแนะนำที่ดีในสิ่งที่คุณต้องการ

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`


1

เนื่องจากฉันพบเจอหลายครั้งในช่วงหลายปีที่ผ่านมาและคราวนี้ฉันต้องการรุ่นพกพาทุบตีบริสุทธิ์ที่ฉันสามารถใช้กับ OSX และ Linux ได้ฉันจึงไปข้างหน้าและเขียนหนึ่ง:

รุ่นชีวิตมีชีวิตอยู่ที่นี่:

https://github.com/keen99/shell-functions/tree/master/resolve_path

แต่เพื่อประโยชน์ของ SO นี่คือรุ่นปัจจุบัน (ฉันรู้สึกว่ามันผ่านการทดสอบอย่างดี แต่ฉันเปิดรับข้อเสนอแนะ!)

อาจไม่ใช่เรื่องยากที่จะทำให้เชลล์ bourne ธรรมดา (sh) ทำงานได้ แต่ฉันไม่ได้ลอง ... ฉันชอบ $ FUNCNAME มากเกินไป :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

นี่เป็นตัวอย่างคลาสสิกด้วยการชง:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

ใช้ฟังก์ชั่นนี้และมันจะคืนค่าเส้นทาง -real-:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 

คำตอบก่อนหน้าของฉันทำสิ่งเดียวกันในประมาณ 1/4 ของพื้นที่ :) มันเป็นสคริปต์เชลล์ขั้นพื้นฐานสวยและไม่คุ้มค่าของ repo คอมไพล์
เดฟ

1

เพื่อหลีกเลี่ยงความเข้ากันไม่ได้กับ Mac ฉันจึงคิด

echo `php -r "echo realpath('foo');"`

ไม่ดี แต่ข้ามระบบปฏิบัติการ


3
Python 2.6+ มีให้บริการในระบบของผู้ใช้ปลายทางมากกว่า php ดังนั้นจึงpython -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"อาจเหมาะสมกว่า ถึงเวลาที่คุณต้องใช้ Python จากเชลล์สคริปต์คุณควรใช้ Python และช่วยให้คุณไม่ต้องกังวลกับการเขียนสคริปต์เชลล์แพลตฟอร์มข้ามแพลตฟอร์ม
Jonathan Baldwin

คุณไม่ต้องการอะไรมากไปกว่า readlink, built-in, dirname และ basename
เดฟ

@Dave: dirname, basenameและreadlinkภายนอกสาธารณูปโภคไม่เปลือกตัว -ins; dirnameและbasenameเป็นส่วนหนึ่งของ POSIX readlinkไม่ใช่
mklement0

@ mklement0 - คุณพูดถูก จัดทำโดย CoreUtils หรือเทียบเท่า ฉันไม่ควรไปเยี่ยมดังนั้นหลังจากตี 1 สาระสำคัญของความคิดเห็นของฉันคือไม่จำเป็นต้องใช้ PHP หรือล่ามภาษาอื่นใดนอกเหนือจากที่ติดตั้งในระบบพื้นฐาน ฉันใช้สคริปต์ที่ฉันให้ไว้ในหน้านี้ในทุกตัวแปร linux ตั้งแต่ปี 1997 และ MacOS X ตั้งแต่ปี 2006 โดยไม่มีข้อผิดพลาด OP ไม่ขอโซลูชัน POSIX สภาพแวดล้อมเฉพาะของพวกเขาคือ Mac OS X
เดฟ

@Dave: ใช่มันเป็นไปได้ที่จะทำกับสาธารณูปโภคหุ้น แต่ก็ยากที่จะทำเช่นนั้น (ตามหลักฐานข้อบกพร่องของสคริปต์ของคุณ) หาก OS X เป็นจุดสนใจจริง ๆ แล้วคำตอบนี้ดีมาก - และง่ายกว่ามาก - เนื่องจากphpมาพร้อมกับ OS X อย่างไรก็ตามถึงแม้ว่าเนื้อหาของคำถามจะกล่าวถึง OS X ก็ไม่ได้ติดแท็กเช่นนี้และชัดเจน ผู้คนบนแพลตฟอร์มต่าง ๆ มาที่นี่เพื่อหาคำตอบดังนั้นมันจึงคุ้มค่าที่จะชี้ให้เห็นว่าแพลตฟอร์มเฉพาะ / ไม่ใช่ POSIX
mklement0

1

นี่คือตัวแก้ไข symlink ใน Bash ที่ใช้งานได้ว่าลิงก์นั้นเป็นไดเรกทอรีหรือไม่ใช่ไดเรกทอรี:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

โปรดทราบว่าทุกสิ่งcdและsetจะเกิดขึ้นใน subshell


ที่จริงแล้ว {} รอบ () นั้นไม่จำเป็นเนื่องจาก () นับว่าเป็น "คำสั่งผสม" เช่นเดียวกับ {} แต่คุณยังต้องการ () หลังจากชื่อฟังก์ชัน
Chris Cogdon

@ChrisCogdon {}รอบ ๆ()ไม่จำเป็นถ้าไม่มี()เบื้องหลังชื่อฟังก์ชั่น Bash รับฟังก์ชั่นการประกาศโดยไม่มี()เพราะเชลล์ไม่มีรายการพารามิเตอร์ในการประกาศและไม่เรียกด้วย()ดังนั้นการประกาศฟังก์ชั่นที่()ไม่สมเหตุสมผล
solidsnack

0

ที่นี่ฉันนำเสนอสิ่งที่ฉันเชื่อว่าเป็นโซลูชันข้ามแพลตฟอร์ม (อย่างน้อย Linux และ macOS) สำหรับคำตอบที่ทำงานได้ดีสำหรับฉันในปัจจุบัน

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

นี่เป็นวิธีแก้ปัญหา macOS (เท่านั้น) อาจเหมาะกับคำถามต้นฉบับมากขึ้น

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}

0

คำตอบของฉันที่นี่ Bash: จะหาเส้นทางที่แท้จริงของ symlink ได้อย่างไร

แต่สั้น ๆ มีประโยชน์มากในสคริปต์:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

เหล่านี้เป็นส่วนหนึ่งของ coreutils GNU เหมาะสำหรับใช้ในระบบ Linux

ในการทดสอบทุกอย่างเราใส่ symlink ลงใน / home / test2 /, แก้ไขสิ่งเพิ่มเติมและเรียกใช้ / เรียกมันจากไดเรกทอรีราก:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

ที่ไหน

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink

0

ในกรณีที่ไม่สามารถใช้ pwd ได้ (เช่นการเรียกสคริปต์จากตำแหน่งอื่น) ให้ใช้ realpath (โดยมีหรือไม่มี dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

ทำงานได้ทั้งเมื่อโทรผ่าน symlink (หลายรายการ) หรือเมื่อเรียกสคริปต์โดยตรง - จากทุกที่

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