วิธีพกพาเพื่อรับเส้นทางที่สมบูรณ์ของสคริปต์


29

เป็นวิธีแบบพกพาสำหรับสคริปต์ (zsh) เพื่อกำหนดเส้นทางที่แน่นอนคืออะไร?

บน Linux ฉันใช้สิ่งที่ชอบ

mypath=$(readlink -f $0)

... แต่นี่ไม่ใช่แบบพกพา (เช่นreadlinkในดาร์วินไม่รู้จัก-fธงและไม่มีอะไรเทียบเท่า) (เช่นเดียวกับการใช้readlinkสิ่งนี้คือเป็นที่ยอมรับว่าเป็นแฮ็คที่ดูคลุมเครือ)

อะไรคือวิธีพกพาที่มากขึ้น?


คำตอบ:


28

ด้วยzshมันเป็นเพียง:

mypath=$0:A

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

บ่อยครั้งสำหรับการพกพาคุณอาจต้องการperl:

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

สิ่งนั้นจะมีลักษณะเหมือน GNU readlink -fมากกว่าrealpath()(GNU readlink -e) โดยที่มันจะไม่บ่นหากไฟล์ไม่มีอยู่ตราบใดที่ dirname ใช้


หมายเหตุ: สิ่งนี้ไม่ได้ผลสำหรับคุณ.zshrc: ดูโพสต์นี้แทน
Bryce Guinta

24

ใน zsh คุณสามารถทำสิ่งต่อไปนี้:

mypath=${0:a}

หรือเพื่อรับไดเรกทอรีที่มีสคริปต์อยู่:

mydir=${0:a:h}

ที่มา: zshexpn (1) หน้าคนส่วนขยายประวัติประวัติส่วนย่อยปรับเปลี่ยน (หรือเพียงinfo -f zsh -n Modifiers)


หวาน! ฉันกำลังมองหาบางอย่างเช่นนี้มานานแล้วและอ่านทั้งหมดของ zsh manpages ค้นหา แต่มันจะไม่เกิดขึ้นกับฉันที่จะดูภายใต้ 'การขยายประวัติศาสตร์'
Vucar Timnärakrul

1
เทียบเท่า GNU ของค่อนข้างจะreadlink -f $0:A
Stéphane Chazelas

11

ฉันใช้สิ่งนี้มาหลายปีแล้ว:

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

1
ฉันชอบมัน! จนถึงตอนนี้มันค่อนข้างพกพาได้ ใช้งานได้กับ Solaris, OmniOS, Linux, Mac และแม้แต่ Cygwin บน Windows 2008
Tim Kennedy

4
โดยที่ไม่เท่ากับ GNU's readlink -fเมื่อสคริปต์นั้นเป็น symlink
Stéphane Chazelas

บน Ubuntu 16.04 กับzshถ้าผมดำเนินการนี้โดยตรงหรือใน subshell (ที่แนะนำ) ขณะที่อยู่ใน dir บ้านของฉัน ( /home/ville) /home/ville/zshก็พิมพ์ออก
วิลล์

8

รูปแบบนี้ควรจะพกพาไปยังบอร์นสไตล์ล่ามเปลือก (ทดสอบกับbash, ksh88, ksh93, zsh, mksh, dashและbusybox sh):

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

รุ่นนี้เพิ่มความเข้ากันได้กับเชลล์ AT&T ดั้งเดิม (ไม่ใช่ 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

ขอบคุณ แม้ว่าฉันคิดว่าการ$PWDยกเลิกการตั้งค่าอาจเกินความเป็นจริงคุณสามารถตั้งค่าให้เป็นกระแสแบบสัมบูรณ์cd -P .ได้ ฉันสงสัยว่ามันจะทำงานเป็นบอร์นเชล - แต่มันควรจะใช้ได้กับทุกอย่างที่คุณทดสอบก่อน ทำเพื่อฉันต่อไป
mikeserv

@moose ระบบปฏิบัติการใดที่คุณใช้อยู่
jlliagre

ใครคือกวางมูส อะไร?
mikeserv

@mikeserv moose เป็นคนเดินผ่านที่โพสต์ความคิดเห็นเกี่ยวกับปัญหาบางอย่างด้วยzshและdirnameถอนความคิดเห็นของเธอได้อย่างรวดเร็ว แต่ / ความคิดเห็นของเธอ ...
jlliagre

สคริปต์ Bourne Shell ของคุณจะไม่ทำงานกับ Bourne Shell เนื่องจาก Bourne Shell ไม่ได้ใช้ getopt () สำหรับ cd (1)
schily

4

สมมติว่าคุณหมายถึงพา ธ สัมบูรณ์จริง ๆ เช่นพา ธ จากไดเร็กทอรีรูท:

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

สิ่งนี้ใช้ได้ในทุกเชลล์สไตล์ Bourne แต่อย่างใด

หากคุณหมายถึงเส้นทางที่มีลิงก์สัญลักษณ์ทั้งหมดได้รับการแก้ไขนั่นเป็นเรื่องที่แตกต่างออกไป readlink -fใช้งานได้กับ Linux (ยกเว้นระบบ BusyBox บางส่วนที่ลอกลง), FreeBSD, NetBSD, OpenBSD และ Cygwin แต่ไม่สามารถใช้กับ OS / X, AIX, HP / UX หรือ Solaris หากคุณมีreadlinkคุณสามารถเรียกมันเป็นวง:

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
}

หากคุณไม่มีreadlinkคุณสามารถประมาณค่าได้ls -nแต่จะใช้งานlsได้ก็ต่อเมื่อไม่รวมอักขระที่ไม่สามารถพิมพ์ได้ในชื่อไฟล์

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

(ส่วนเสริมzคือในกรณีที่เป้าหมายลิงก์สิ้นสุดในการขึ้นบรรทัดใหม่การแทนที่คำสั่งจะกินหมดrealpathฟังก์ชันจะไม่จัดการกรณีดังกล่าวสำหรับชื่อไดเรกทอรีตามวิธี)


1
คุณรู้หรือไม่เกี่ยวกับlsการติดตั้งใด ๆที่ทำให้ตัวอักษรที่ไม่สามารถพิมพ์ได้นั้นมีค่าเมื่อเอาต์พุตไม่ได้ไปที่เทอร์มินัล
Stéphane Chazelas

1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(นั่นคือถ้าชื่ออยู่ใน UTF-8, latin1 จะให้คุณเป็นคนเดียว?) ฉันคิดว่าฉันเคยเห็นสิ่งนี้ใน Unices เชิงพาณิชย์ที่เก่ากว่าเช่นกัน
Gilles 'หยุดความชั่วร้าย'

@ StéphaneChazelasฉันได้แก้ไขข้อบกพร่องหลายอย่างแล้ว แต่ยังไม่ได้ทดสอบอย่างกว้างขวาง แจ้งให้เราทราบหากยังคงล้มเหลวในบางกรณี (นอกเหนือจากการไม่มีสิทธิ์ดำเนินการในบางไดเรกทอรีฉันจะไม่จัดการกับกรณีขอบนั้น)
Gilles 'หยุดความชั่วร้าย'

@Gilles - busyboxนี่คืออะไร ตามgit busybox lsไม่ได้มีการเปลี่ยนแปลงรหัสตั้งแต่ 2011 My busybox ls- circa 2013 - ไม่ทำสิ่งนี้ นี้หนึ่งประมาณ - 2012 - ไม่ นี่อาจอธิบายได้ว่าทำไม คุณได้สร้างbusyboxด้วยการสนับสนุน Unicode - เพื่อรวมการสนับสนุน wchar? คุณอาจต้องลองอีกครั้งเพื่อตรวจสอบตัวเลือกการสร้างในmkinitcpio busyboxแพ็คเกจ
mikeserv

Gilles - ฉันเชื่อว่าฉันเริ่มต้นผิดคำตอบนี้ - หรืออย่างน้อยก็เป็นส่วนหนึ่งของมัน ในขณะที่ฉันเชื่อมั่นว่าชื่อไฟล์ของคุณที่คลุมเครือนั้นจะผิดพลาดไปอย่างแน่นอน แต่poor_mans_readlinkของคุณก็ทำได้ดีมาก หากคุณจะทำให้ฉันมีความเมตตาในการแก้ไข - การแก้ไขใด ๆ ที่จะทำ - และส่งเสียงหลังจากนั้นฉันควรจะย้อนกลับการลงคะแนนของฉันเกี่ยวกับเรื่องนี้
mikeserv

1

ให้คุณมีสิทธิ์ในการดำเนินการไดเรกทอรีปัจจุบัน - หรือในไดเรกทอรีที่คุณดำเนินการเชลล์สคริปต์ของคุณ - cdถ้าคุณต้องการเส้นทางที่แน่นอนไปยังไดเรกทอรีทั้งหมดที่คุณต้องการ

ขั้นตอนที่ 10 ของcdข้อมูลจำเพาะ

ถ้า-Pตัวเลือกอยู่ในผลกระทบที่ตัวแปรสภาพแวดล้อมจะถูกกำหนดให้เป็นสตริงที่จะถูกส่งออกโดย$PWD pwd -Pหากมีการอนุญาตไม่เพียงพอในไดเร็กทอรีใหม่หรือบนพาเรนต์ใด ๆ ของไดเร็กทอรีนั้นเพื่อกำหนดไดเร็กทอรีการทำงานปัจจุบันค่าของ$PWDตัวแปรสภาวะแวดล้อมจะไม่ถูกระบุ

และบน pwd -P

ชื่อพา ธ ที่เขียนไปยังเอาต์พุตมาตรฐานจะต้องไม่มีส่วนประกอบใด ๆ ที่อ้างถึงไฟล์ของลิงค์สัญลักษณ์ประเภท หากมีหลายชื่อพา ธ ที่pwdยูทิลิตีสามารถเขียนไปยังเอาต์พุตมาตรฐานได้เริ่มต้นหนึ่งด้วยอักขระเดี่ยว / สแลชและอย่างน้อยหนึ่งเริ่มต้นด้วยอักขระสอง / สแลชแล้วมันจะเขียนชื่อพา ธ เริ่มต้นด้วยอักขระเดียว / เครื่องหมายทับ ชื่อพา ธ จะต้องไม่มีอักขระที่ไม่จำเป็น / สแลชหลังจากอักขระหนึ่งหรือสอง / สแลชนำหน้า

เป็นเพราะcd -Pมีการตั้งค่าไดเรกทอรีการทำงานปัจจุบันเป็นสิ่งที่pwd -Pควรพิมพ์และที่cd -จะพิมพ์$OLDPWDงานดังต่อไปนี้:

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

เอาท์พุท

/home/mikeserv/test/ln

รอมันอยู่...

cd -P . ; cd . ; cd -

เอาท์พุท

/home/mikeserv/test/dir

และเมื่อฉันพิมพ์ฉันพิมพ์cd - กำหนดทันทีที่ฉันเป็นเส้นทางที่แน่นอนไป- ดังนั้นฉันไม่ต้องการตัวแปรอื่น ๆ และอันที่จริงฉันควรจะไม่จำเป็นต้องต่อท้ายแต่มีพฤติกรรมที่ระบุการตั้งค่าไปในเปลือกโต้ตอบเมื่อ เป็นตกแต่ง ดังนั้นมันจึงเป็นเพียงนิสัยที่ดีในการพัฒนา$OLDPWDcd$PWDcd -P . $PWD/.$PWD$HOMEcd

ดังนั้นเพียงทำตามข้างบนเส้นทาง${0%/*}ควรมากกว่าเพียงพอที่จะตรวจสอบ$0เส้นทางของ แต่ในกรณีที่$0ตัวเองเป็น soft-link คุณอาจไม่สามารถเปลี่ยนไดเรกทอรีเป็นโชคไม่ดี

นี่คือฟังก์ชั่นที่จะจัดการกับสิ่งนั้น:

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                                    
}

มันพยายามทำมากที่สุดเท่าที่จะทำได้ในเชลล์ปัจจุบัน - โดยไม่ต้องเรียกใช้ subshell - แม้ว่าจะมี subshells ที่เรียกใช้สำหรับข้อผิดพลาดและซอฟต์ลิงก์ที่ไม่ได้ชี้ไปที่ไดเรกทอรี มันขึ้นอยู่กับเปลือกที่เข้ากันได้กับ POSIX และ POSIX ที่เข้ากันได้lsเช่นเดียวกับ_function()เนมสเปซที่สะอาด มันจะยังคงทำงานได้ดีโดยไม่ต้องใช้หลังแม้ว่ามันอาจจะเขียนทับunsetฟังก์ชันเชลล์ปัจจุบันบางส่วนในกรณีนั้น โดยทั่วไปแล้วการพึ่งพาเหล่านี้ทั้งหมดควรมีความน่าเชื่อถืออยู่ในเครื่อง Unix

เรียกว่ามีหรือไม่มีข้อโต้แย้งสิ่งแรกที่มันจะถูกรีเซ็ตเป็นค่า$PWDมาตรฐานของมัน - มันแก้ไขลิงก์ใด ๆ ในนั้นไปยังเป้าหมายของพวกเขาตามความจำเป็น เรียกว่าไม่มีข้อโต้แย้งและที่เกี่ยวกับมัน แต่เรียกกับพวกเขาและมันจะแก้ไขและกำหนดเป็นที่ยอมรับเส้นทางสำหรับแต่ละหรืออื่นพิมพ์ข้อความไปstderrทำไม

เพราะส่วนใหญ่ทำงานในเชลล์ปัจจุบันมันควรจะสามารถจัดการกับรายการอาร์กิวเมนต์ของความยาวใด ๆ นอกจากนี้ยังมองหา$_zdlmตัวแปร(ซึ่งเป็นunsets เมื่อมันผ่าน)และพิมพ์ค่า C-escaped ทันทีทางด้านขวาของแต่ละอาร์กิวเมนต์ของมันซึ่งแต่ละครั้งจะถูกตามด้วย\nอักขระ ewline เดียวเสมอ

มันเปลี่ยนแปลงไดเรกทอรีจำนวนมาก แต่นอกเหนือจากการตั้งค่าเป็นค่ามาตรฐานของมันจะไม่ส่งผลกระทบ$PWDถึงแม้ว่า$OLDPWDจะไม่สามารถนับได้เมื่อมันผ่าน

มันพยายามที่จะออกจากการขัดแย้งแต่ละครั้งโดยเร็วที่สุด มันพยายามครั้งแรกที่จะเข้ามาcd ถ้ามันจะสามารถพิมพ์เส้นทางที่ยอมรับข้อโต้แย้งที่จะ$1 stdoutหากไม่สามารถตรวจสอบได้ว่า$1มีอยู่และไม่ใช่ลิงค์ซอฟต์ หากเป็นจริงจะพิมพ์ออกมา

ด้วยวิธีนี้จะจัดการกับอาร์กิวเมนต์ประเภทไฟล์ใด ๆ ที่เชลล์มีสิทธิ์ที่จะอยู่เว้นแต่$1จะเป็นลิงค์สัญลักษณ์ที่ไม่ได้ชี้ไปที่ไดเรกทอรี ในกรณีนั้นมันจะเรียกwhileloop ใน subshell

มันโทรlsไปอ่านลิงค์ ไดเร็กทอรีปัจจุบันจะต้องเปลี่ยนเป็นค่าเริ่มต้นก่อนเพื่อให้สามารถจัดการพา ธ อ้างอิงใด ๆ ได้อย่างน่าเชื่อถือดังนั้นใน subshell การทดแทนคำสั่งฟังก์ชันจะทำหน้าที่:

cd -...ls...echo /

มันแถบจากด้านซ้ายของls's ->ผลผลิตน้อยที่สุดเท่าที่มันจะต้องมีชื่ออย่างเต็มที่การเชื่อมโยงและสตริง ในขณะที่ฉันพยายามหลีกเลี่ยงการทำเช่นนี้ในตอนแรกshiftและ$IFSปรากฎว่านี่เป็นวิธีการที่เชื่อถือได้มากที่สุดเท่าที่จะทำได้ นี่เป็นสิ่งเดียวกับที่poor_mans_readlinkของ Gilles ทำและทำได้ดีมาก

มันจะทำซ้ำขั้นตอนนี้ในลูปจนกว่าชื่อไฟล์ที่ส่งคืนมาจากlsลิงก์ไม่ใช่ซอฟต์ลิงก์แน่นอน ณ จุดนั้นมันเป็นที่ยอมรับเส้นทางที่เป็นมาก่อนด้วยcdแล้วพิมพ์

ตัวอย่างการใช้งาน:

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

เอาท์พุท

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

หรืออาจจะ ...

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

zdlm=\\0 zpath * | cat -A

เอาท์พุท

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

0

สิ่งที่เกี่ยวกับการเห็นอกเห็นใจหนึ่งซับเมื่อมีงูหลามพร้อมที่จะป้องกันไม่ให้นิยามอัลกอริทึม?

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

เช่นเดียวกับhttps://stackoverflow.com/a/7305217

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