วิธีรับไดเรกทอรีซอร์สของสคริปต์ Bash จากภายในสคริปต์นั้น


4950

ฉันจะรับพา ธ ของไดเรกทอรีที่สคริปต์Bashอยู่ภายในสคริปต์นั้นได้อย่างไร

ฉันต้องการใช้สคริปต์ Bash เป็นตัวเรียกใช้งานสำหรับแอปพลิเคชันอื่น ฉันต้องการเปลี่ยนไดเรกทอรีการทำงานเป็นไดเรกทอรี Bash script ดังนั้นฉันสามารถทำงานกับไฟล์ในไดเรกทอรีนั้นได้เช่น:

$ ./application

69
ไม่มีวิธีแก้ปัญหาในปัจจุบันทำงานหากมีการขึ้นบรรทัดใหม่ที่ท้ายชื่อไดเรกทอรี - พวกเขาจะถูกปล้นโดยการทดแทนคำสั่ง การทำงานรอบนี้คุณสามารถผนวกอักขระที่ไม่ใช่การขึ้นบรรทัดใหม่ภายในเปลี่ยนตัวคำสั่ง - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- และลบออกได้โดยไม่ต้องแทนคำสั่ง DIR="${DIR%x}"-
l0b0

80
@ jpmc26 มีสองสถานการณ์ที่พบบ่อยมาก: อุบัติเหตุและการก่อวินาศกรรม mkdir $'\n'สคริปต์ไม่ควรล้มเหลวในรูปแบบที่คาดเดาไม่ได้เพียงเพราะใครบางคนบางได้
l0b0

24
ใครก็ตามที่ยอมให้ผู้คนก่อวินาศกรรมในระบบของพวกเขาด้วยวิธีนี้ไม่ควรปล่อยให้ใครมาทุบตีเพื่อตรวจจับปัญหาดังกล่าว ... จ้างคนที่มีความสามารถทำผิดพลาดน้อยลง ฉันไม่เคยมีใน 25 ปีของการใช้ทุบตีเห็นสิ่งนี้เกิดขึ้นได้ทุกที่ .... นี่คือเหตุผลที่เรามีสิ่งต่าง ๆ เช่น perl และการฝึกฝนเช่นการตรวจสอบที่ไม่ดี (ฉันอาจจะถูกไล่ออกเพราะบอกว่า :)
osirisgothra

3
@ l0b0 พิจารณาว่าคุณต้องการการป้องกันแบบเดียวกันdirnameและไดเรกทอรีอาจเริ่มต้นด้วย-(เช่น--help) DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. บางทีนี่มันเกินจริงเหรอ?
คะแนน _ ต่ำกว่า

55
ฉันแนะนำให้อ่านคำถามที่พบบ่อยเกี่ยวกับเรื่องนี้ของBash
Rany Albeg Wein

คำตอบ:


6569
#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

เป็นหนึ่งซับที่มีประโยชน์ซึ่งจะให้ชื่อไดเรกทอรีแบบเต็มของสคริปต์ไม่ว่าจะถูกเรียกจากที่ใด

มันจะทำงานได้ตราบใดที่องค์ประกอบสุดท้ายของเส้นทางที่ใช้ในการค้นหาสคริปต์ไม่ใช่ symlink (ลิงก์ไดเรกทอรีจะใช้ได้) หากคุณต้องการแก้ไขลิงก์ใด ๆ ไปยังสคริปต์ด้วยคุณจำเป็นต้องใช้โซลูชันหลายบรรทัด:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && 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
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

สุดท้ายนี้จะทำงานร่วมกับการรวมกันของนามแฝงใด ๆsource, bash -c, symlinks ฯลฯ

ระวัง: หากคุณcdไปยังไดเรกทอรีอื่นก่อนเรียกใช้ตัวอย่างข้อมูลผลลัพธ์อาจไม่ถูกต้อง!

นอกจากนี้ระวัง$CDPATHgotchasและ stderr ด้านผลเอาท์พุทหากผู้ใช้มีการเขียนทับซีดีอย่างชาญฉลาดเพื่อเปลี่ยนเส้นทางการส่งออกไปยัง stderr แทน (รวมถึงลำดับการหลบหนีเช่นเมื่อโทรupdate_terminal_cwd >&2บน Mac) การเพิ่ม>/dev/null 2>&1เมื่อสิ้นสุดcdคำสั่งของคุณจะดูแลความเป็นไปได้ทั้งสองอย่าง

เพื่อให้เข้าใจถึงวิธีการทำงานลองใช้แบบฟอร์ม verbose เพิ่มเติมนี้:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET="$(readlink "$SOURCE")"
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE="$TARGET"
  else
    DIR="$( dirname "$SOURCE" )"
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

และมันจะพิมพ์สิ่งที่ชอบ:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

27
คุณสามารถหลอมรวมวิธีการนี้กับคำตอบโดย user25866 ที่จะมาถึงวิธีการแก้ปัญหาที่ทำงานร่วมกับที่source <script>และ:bash <script> DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Dan Molding

21
บางครั้งcdพิมพ์บางสิ่งบางอย่างเพื่อ STDOUT! เช่นถ้าคุณมี$CDPATH .เพื่อให้ครอบคลุมกรณีนี้ให้ใช้DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468

182
คำตอบที่ยอมรับนี้ไม่เป็นไรมันใช้ไม่ได้กับ symlink และซับซ้อนเกินไป dirname $(readlink -f $0)เป็นคำสั่งที่เหมาะสม ดูgist.github.com/tvlooy/cbfbdb111a4ebad8b93eเพื่อรับ testcase
tvlooy

167
@tvlooy IMO คำตอบของคุณไม่เป็นไปตามที่ต้องการเนื่องจากมันล้มเหลวเมื่อมีช่องว่างในเส้นทาง ตรงกันข้ามกับอักขระขึ้นบรรทัดใหม่ซึ่งไม่น่าเป็นไปได้หรือแม้กระทั่งเรื่องผิดปกติ dirname "$(readlink -f "$0")"ไม่ได้เพิ่มความซับซ้อนและเป็นตัวชี้วัดที่ยุติธรรมมากขึ้นสำหรับปัญหาจำนวนเล็กน้อย
Adrian Günter

10
@ tvlooy ความคิดเห็นของคุณไม่รองรับ macOS (หรืออาจจะเป็น BSD โดยทั่วไป) ในขณะที่คำตอบที่ยอมรับคือ จะช่วยให้readlink -f $0 readlink: illegal option -- f
Alexander Ljungberg

875

ใช้dirname "$0":

#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"

การใช้pwdเพียงอย่างเดียวจะไม่ทำงานหากคุณไม่ได้เรียกใช้สคริปต์จากไดเรกทอรีที่มีอยู่

[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp

25
เพื่อความสะดวกในการพกพานอกจากทุบตี $ 0 อาจไม่เพียงพอเสมอไป คุณอาจต้องแทนที่ "type -p $ 0" เพื่อให้งานนี้หากพบคำสั่งบนเส้นทาง
Darron

10
@Darron: คุณสามารถใช้ได้ก็ต่อtype -pเมื่อสคริปต์สามารถเรียกใช้งานได้ สิ่งนี้สามารถเปิดรูที่บอบบางหากสคริปต์ถูกเรียกใช้โดยใช้bash test2.shและมีสคริปต์อื่นที่มีชื่อเดียวกันซึ่งสามารถเรียกใช้งานได้จากที่อื่น
D.Shawley

90
@Darron: แต่เนื่องจากคำถามถูกติดแท็กbashและบรรทัด hash-bang ระบุไว้อย่างชัดเจน/bin/bashฉันจะบอกว่ามันค่อนข้างปลอดภัยที่จะขึ้นอยู่กับ bashisms
Joachim Sauer

34
+1 แต่ปัญหากับการใช้คือว่าถ้าไดเรกทอรีไดเรกทอรีปัจจุบันคุณจะได้รับdirname $0 .ไม่เป็นไรเว้นแต่ว่าคุณจะเปลี่ยนไดเรกทอรีในสคริปต์และคาดว่าจะใช้เส้นทางที่คุณได้รับจากdirname $0ราวกับว่ามันสมบูรณ์ เพื่อให้ได้เส้นทางที่แน่นอน: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 ( แต่ก็มีวิธีที่ดีกว่าที่จะขยายเส้นทางที่?)
TJ Crowder

9
@TJ Crowder ฉันไม่แน่ใจว่าdirname $0เป็นปัญหาหากคุณกำหนดให้กับตัวแปรแล้วใช้มันเพื่อเปิดสคริปต์เช่น$dir/script.sh; ฉันคิดว่านี่เป็นกรณีใช้งานของสิ่งนี้ 90% ของเวลา ./script.shจะทำงานได้ดี
matt b

515

dirnameคำสั่งเป็นพื้นฐานที่สุดเพียงแค่การแยกเส้นทางถึงชื่อไฟล์ออกจาก$0ตัวแปร (ชื่อสคริปต์):

dirname "$0"

แต่ดังที่ด้าน bชี้ให้เห็นเส้นทางที่ส่งกลับจะแตกต่างกันไปตามวิธีการเรียกใช้สคริปต์ pwdไม่ทำงานเนื่องจากจะบอกคุณว่าไดเรกทอรีปัจจุบันคืออะไรไม่ใช่ไดเรกทอรีที่สคริปต์ตั้งอยู่นอกจากนี้หากดำเนินการลิงก์สัญลักษณ์ไปยังสคริปต์คุณจะได้รับเส้นทาง (อาจสัมพันธ์กัน) ไป ตำแหน่งที่ลิงก์อยู่ไม่ใช่สคริปต์จริง

บางคนพูดถึงreadlinkคำสั่ง แต่ที่ง่ายที่สุดคุณสามารถใช้:

dirname "$(readlink -f "$0")"

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

นี่เป็นสคริปต์ที่แสดงให้เห็นถึงสิ่งเหล่านี้whatdir.sh:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"

ใช้สคริปต์นี้ในบ้าน dir ของฉันโดยใช้เส้นทางญาติ:

>>>$ ./whatdir.sh 
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

อีกครั้ง แต่ใช้เส้นทางแบบเต็มไปยังสคริปต์:

>>>$ /Users/phatblat/whatdir.sh 
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

ตอนนี้เปลี่ยนไดเรกทอรี:

>>>$ cd /tmp
>>>$ ~/whatdir.sh 
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

และสุดท้ายใช้ลิงก์สัญลักษณ์เพื่อรันสคริปต์:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh 
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

13
readlinkจะไม่มีประโยชน์ในบางแพลตฟอร์มในการติดตั้งเริ่มต้น พยายามหลีกเลี่ยงการใช้งานถ้าคุณสามารถ
TL

43
ระวังอ้างทุกอย่างเพื่อหลีกเลี่ยงปัญหาช่องว่าง:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul

13
ใน OSX 10.10.1 โยเซมิตีไม่ได้รับการยอมรับเป็นตัวเลือกให้กับ-f readlinkใช้stat -fแทนทำงาน ขอบคุณ
cucu8

11
ใน OSX นั้นมีgreadlinkซึ่งโดยทั่วไปแล้วreadlinkเราทุกคนต่างก็คุ้นเคย นี่คือแพลตฟอร์มรุ่นอิสระdir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
โรเบิร์ต

6
โทรดี @robert FYI greadlinkสามารถติดตั้งผ่าน homebrew ได้อย่างง่ายดาย:brew install coreutils
phatblat

184
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

ใช้งานได้กับทุกรุ่นรวมถึง

  • เมื่อเรียกใช้ผ่านลิงก์นุ่มลึก
  • เมื่อไฟล์มัน
  • เมื่อสคริปต์ถูกเรียกโดยโอเปอเรเตอร์" source" aka .(dot)
  • เมื่อ ARG $0ถูกแก้ไขจากผู้โทร
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

อีกทางหนึ่งถ้าสคริปต์ bash นั้นเป็นsymlink แบบสัมพัทธ์คุณต้องการติดตามและส่งคืนพา ธ เต็มของสคริปต์ที่เชื่อมโยงไปยัง:

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

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

ความคิดเห็นและรหัส Copyleft สิทธิ์การใช้งานที่เลือกได้ภายใต้ GPL2.0 หรือใหม่กว่าหรือ CC-SA 3.0 (CreativeCommons Share Alike) หรือใหม่กว่า (c) 2008 สงวนลิขสิทธิ์ ไม่มีการรับประกันใด ๆ คุณได้รับการเตือน
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656


4
ดี! อาจทำให้การแทนที่สั้นลง "pushd [... ] popd / dev / null" โดย SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-ภิรมย์

5
และแทนที่จะใช้ pushd ... ; จะดีกว่าไหมถ้าใช้ $ (cd dirname "${SCRIPT_PATH}"&& pwd) แต่อย่างไรก็ตามสคริปต์ที่ยอดเยี่ยม!
ovanes

6
เป็นอันตรายสำหรับสคริปต์ที่จะcdออกจากไดเรกทอรีปัจจุบันโดยหวังว่าจะcdกลับมาอีกครั้งในภายหลัง: สคริปต์อาจไม่ได้รับอนุญาตให้เปลี่ยนไดเรกทอรีกลับไปเป็นไดเรกทอรีที่เป็นปัจจุบันเมื่อมีการเรียกใช้ (เหมือนกันสำหรับ pushd / popd)
Adrian Pronk

7
readlink -fเฉพาะ GNU BSD readlinkไม่มีตัวเลือกนั้น
Kara Brightwell

3
มีอะไรที่ไม่จำเป็นกับ subshells ทั้งหมด? ([ ... ])มีประสิทธิภาพน้อยกว่า[ ... ]และไม่มีข้อได้เปรียบใด ๆ จากการแยกที่เสนอเพื่อผลการดำเนินงานที่เกิดขึ้นที่นี่
ชาร์ลส์ดัฟฟี่

110

คำตอบสั้น ๆ :

`dirname $0`

หรือ ( เด่นกว่า ):

$(dirname "$0")

17
มันจะไม่ทำงานถ้าคุณมาสคริปต์ "source my / script.sh"
Rajkumar

ฉันใช้สิ่งนี้ตลอดเวลาในสคริปต์ทุบตีของฉันที่ทำสิ่งต่าง ๆ โดยอัตโนมัติและมักจะเรียกใช้สคริปต์อื่น ๆ ใน dir เดียวกัน ฉันไม่เคยใช้sourceสิ่งเหล่านี้และcd $(dirname $0)จดจำได้ง่าย
kqw

16
@ vidstige: ${BASH_SOURCE[0]}แทนที่จะ$0ทำงานด้วยsource my/script.sh
ทิโมธีโจนส์

@ TimothyJones ที่จะล้มเหลว 100% ของเวลาถ้าแหล่งที่มาจากเปลือกอื่น ๆ กว่าทุบตี ${BASH_SOURCE[0]}ไม่พอใจเลย ${BASH_SOURCE:-0}ดีกว่ามาก
Mathieu CAROFF

106

คุณสามารถใช้$BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

โปรดทราบว่าคุณต้องใช้#!/bin/bashและไม่ใช่#!/bin/shเพราะเป็นส่วนขยายของ Bash


14
เมื่อผมทำ./foo/scriptแล้วคือ$(dirname $BASH_SOURCE) ./foo
จนถึง

1
@ จนถึงในกรณีนี้เราสามารถใช้realpathคำสั่งเพื่อรับพา ธ แบบเต็มของ. /foo/script ดังนั้นdirname $(realpath ./foo/script) จะให้เส้นทางของสคริปต์
purushothaman poovai

73

สิ่งนี้ควรทำ:

DIR="$(dirname "$(readlink -f "$0")")"

สิ่งนี้ใช้ได้กับ symlink และช่องว่างในพา ธ

ดูหน้าคนสำหรับและdirnamereadlink

ดูเหมือนว่าจะไม่สามารถทำงานร่วมกับ Mac OS ได้ ฉันไม่รู้ว่าทำไม ข้อเสนอแนะใด ๆ


6
ด้วยโซลูชันของคุณให้เรียกใช้สคริปต์เช่นการ./script.shแสดง.แทนเส้นทางไดเรกทอรีแบบเต็ม
Bruno Negrão Zica

5
ไม่มีตัวเลือก -f สำหรับ readlink บน MacOS ใช้statแทน แต่ก็ยังแสดง.ว่าคุณอยู่ใน dir นี้
Denis The Menace

2
คุณต้องติดตั้งcoreutilsจาก Homebrew และใช้greadlinkเพื่อรับ-fตัวเลือกใน MacOS เนื่องจากเป็น * BSD ภายใต้หน้าปกและไม่ใช่ Linux
dragon788

คุณควรเพิ่มเครื่องหมายคำพูดคู่ล้อมรอบด้านขวาทั้งหมด:DIR="$(dirname "$(readlink -f "$0")")"
hagello

60

pwdสามารถใช้เพื่อค้นหาไดเร็กทอรีการทำงานปัจจุบันและdirnameเพื่อค้นหาไดเร็กทอรีของไฟล์เฉพาะ (คำสั่งที่ถูกรันคือ$0ดังนั้นdirname $0ควรให้ไดเร็กทอรีของสคริปต์ปัจจุบัน)

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

ฉันขอแนะนำดังต่อไปนี้:

#!/bin/bash

reldir=`dirname $0`
cd $reldir
directory=`pwd`

echo "Directory is $directory"

ด้วยวิธีนี้คุณจะได้รับไดเรกทอรีแน่นอน

เนื่องจากสคริปต์จะทำงานในอินสแตนซ์ bash แยกต่างหากจึงไม่จำเป็นต้องกู้คืนไดเร็กทอรีการทำงานหลังจากนั้น แต่ถ้าคุณต้องการเปลี่ยนกลับในสคริปต์ด้วยเหตุผลบางประการคุณสามารถกำหนดค่าpwdให้กับตัวแปรก่อนคุณได้อย่างง่ายดายเปลี่ยนไดเรกทอรีสำหรับใช้ในอนาคต

แม้เพียงแค่

cd `dirname $0`

แก้สถานการณ์เฉพาะในคำถามฉันพบว่ามีเส้นทางที่แน่นอนที่มีประโยชน์มากกว่าโดยทั่วไป


9
คุณสามารถทำได้ทั้งหมดในหนึ่งบรรทัดเช่นนี้: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane

วิธีนี้ใช้ไม่ได้หากสคริปต์ส่งสคริปต์อื่นและคุณต้องการทราบชื่อของสคริปต์
reinierpost

52

ฉันเบื่อที่จะมาที่หน้านี้ซ้ำแล้วซ้ำอีกเพื่อคัดลอกวางซับในคำตอบที่ยอมรับ ปัญหาที่เกิดขึ้นคือมันไม่ง่ายที่จะเข้าใจและจดจำ

นี่เป็นสคริปต์ที่ง่ายต่อการจดจำ:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be

2
หรือยิ่งคลุมเครือในบรรทัดเดียว: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc

ทำไมนี่ไม่ใช่คำตอบที่ยอมรับ? มีความแตกต่างrealpathจากการแก้ไข "ด้วยตนเอง" กับวนของreadlinkหรือไม่? แม้แต่readlinkหน้า man ก็บอกว่าNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123

1
และโดยวิธีที่เราไม่ควรใช้มาrealpathก่อนdirnameไม่ใช่หลังจาก? หากไฟล์สคริปต์ที่ตัวเองเป็น symlink ... DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"มันจะให้สิ่งที่ต้องการ จริง ๆ แล้วใกล้กับคำตอบที่เสนอโดย Simon
User9123 Mar25

@ User9123 ฉันคิดว่าการยอมรับคือพยายามเข้ากันได้กับเชลล์ / distro ยอดนิยมทั้งหมด มากกว่านั้นขึ้นอยู่กับสิ่งที่คุณพยายามทำในกรณีส่วนใหญ่คนต้องการได้รับไดเรกทอรีที่ symlink อยู่แทนไดเรกทอรีของแหล่งที่มาจริง
วัง

37

ฉันไม่คิดว่ามันจะง่ายเหมือนที่คนอื่นทำไว้ pwdไม่ทำงานเนื่องจากไดเรกทอรีปัจจุบันไม่จำเป็นต้องเป็นไดเรกทอรีที่มีสคริปต์ $0ไม่ได้มีข้อมูลเสมอ พิจารณาสามวิธีต่อไปนี้เพื่อเรียกใช้สคริปต์:

./script

/usr/bin/script

script

ในวิธีที่หนึ่งและสาม$0ไม่มีข้อมูลเส้นทางแบบเต็ม ในครั้งที่สองและสามpwdไม่ทำงาน วิธีเดียวที่จะได้รับไดเรกทอรีในวิธีที่สามคือการเรียกใช้ผ่านเส้นทางและค้นหาไฟล์ที่มีการจับคู่ที่ถูกต้อง โดยทั่วไปรหัสจะต้องทำซ้ำสิ่งที่ระบบปฏิบัติการทำ

วิธีหนึ่งในการทำสิ่งที่คุณขอคือเพียงฮาร์ดโค้ดข้อมูลใน/usr/shareไดเรกทอรีและอ้างอิงโดยเส้นทางแบบเต็ม ข้อมูลไม่ได้อยู่ใน/usr/binสารบบดังนั้นนี่น่าจะเป็นสิ่งที่ควรทำ


9
หากคุณตั้งใจจะพิสูจน์ความคิดเห็นของเขาให้พิสูจน์ว่าสคริปต์สามารถเข้าถึงที่เก็บไว้ด้วยตัวอย่างโค้ด
Richard Duerr

34
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )

นี่เป็นวิธีที่สั้นกว่าคำตอบที่เลือก และดูเหมือนจะใช้งานได้ดีเช่นกัน สมควรได้รับ 1,000 คะแนนเพื่อให้ผู้คนไม่มองข้าม
Patrick

2
เป็นจำนวนมากของคำตอบก่อนอธิบายในรายละเอียดค่า$0มิได้pwdมีการรับประกันที่จะมีข้อมูลที่ถูกต้องขึ้นอยู่กับว่าสคริปต์ที่ถูกเรียก
IMSoP

34
$(dirname "$(readlink -f "$BASH_SOURCE")")

ฉันชอบ$BASH_SOURCEมากกว่า$0เพราะมันชัดเจนสำหรับผู้อ่านที่ไม่มีความชำนาญในการทุบตี $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster

32

สิ่งนี้จะได้รับไดเรกทอรีการทำงานปัจจุบันบน Mac OS X 10.6.6:

DIR=$(cd "$(dirname "$0")"; pwd)

27

นี่เป็น Linux โดยเฉพาะ แต่คุณสามารถใช้:

SELF=$(readlink /proc/$$/fd/255)

1
นอกจากนี้ยังมีการทุบตีที่เฉพาะเจาะจง แต่บางทีพฤติกรรมของทุบตีมีการเปลี่ยนแปลง? /proc/fd/$$/255ดูเหมือนว่าจะชี้ไปที่ tty ไม่ใช่ไดเร็กทอรี ยกตัวอย่างเช่นในเปลือกเข้าสู่ระบบปัจจุบันของฉัน, ไฟล์อธิบาย 0, 1, 2 และ 255 /dev/pts/4ทุกอ้างถึง ไม่ว่าในกรณีใดคู่มือทุบตีไม่ได้กล่าวถึง fd 255 ดังนั้นจึงอาจไม่ฉลาดที่จะขึ้นอยู่กับพฤติกรรมนี้ \
Keith Thompson

2
เชลล์แบบโต้ตอบ! = สคริปต์ อย่างไรก็ตามrealpath ${BASH_SOURCE[0]};ดูเหมือนจะเป็นวิธีที่ดีที่สุดที่จะไป
Steve Baker

23

นี่คือซับในที่รองรับ POSIX:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test
echo $SCRIPT_PATH

4
ฉันประสบความสำเร็จเมื่อใช้สคริปต์ด้วยตัวเองหรือโดยใช้ sudo แต่ไม่ใช่เมื่อเรียกซอร์ส. /script.sh
Michael R

และจะล้มเหลวเมื่อcdกำหนดค่าให้พิมพ์ชื่อพา ธ ใหม่
Aaron Digulla

18

ฉันลองทั้งหมดเหล่านี้และไม่มีใครทำงาน มีใครเข้ามาใกล้มาก แต่มีข้อผิดพลาดเล็ก ๆ ที่ทำลายมันไม่ดี พวกเขาลืมห่อเส้นทางในเครื่องหมายคำพูด

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

ลองไดเรกทอรีนี้สำหรับขนาด:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

สิ่งนี้ทำให้ถูกต้องโดยไม่คำนึงถึงวิธีการหรือที่คุณเรียกใช้:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

ดังนั้นเพื่อให้มีประโยชน์จริงนี่คือวิธีการเปลี่ยนไดเรกทอรีของสคริปต์ที่ทำงานอยู่:

cd "`dirname "$0"`"

4
ไม่ทำงานหากสคริปต์นั้นมาจากสคริปต์อื่น
reinierpost

สิ่งนี้จะไม่ทำงานหากส่วนสุดท้ายของ $ 0 เป็นลิงก์สัญลักษณ์ที่ชี้ไปยังรายการของไดเรกทอรีอื่น ( ln -s ../bin64/foo /usr/bin/foo)
hagello

17

นี่คือวิธีง่ายๆที่ถูกต้อง:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

คำอธิบาย:

  • ${BASH_SOURCE[0]}- เส้นทางแบบเต็มไปยังสคริปต์ ค่านี้จะถูกต้องแม้เมื่อสคริปต์จะถูกมาเช่นsource <(echo 'echo $0')พิมพ์ทุบตีขณะแทนที่ด้วย${BASH_SOURCE[0]}จะพิมพ์เส้นทางแบบเต็มของสคริปต์ (ของหลักสูตรนี้จะถือว่าคุณตกลงกำลังการพึ่งพาทุบตี.)

  • readlink -f- แก้ไข symlinks ซ้ำ ๆ ในเส้นทางที่ระบุ นี่คือส่วนขยาย GNU และไม่สามารถใช้ได้กับ (เช่น) ระบบ BSD หากคุณกำลังใช้ Mac คุณสามารถใช้ในการติดตั้ง Homebrew GNU และแย่งนี้กับcoreutilsgreadlink -f

  • และแน่นอนdirnameได้รับไดเรกทอรีหลักของเส้นทาง


1
greadlink -fน่าเสียดายที่มันไม่ทำงานอย่างมีประสิทธิภาพเมื่อsourceใช้สคริปต์บน Mac :(
Gabe Kopley

17

วิธีที่สั้นและหรูหราที่สุดในการทำเช่นนี้คือ:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

สิ่งนี้จะใช้ได้กับทุกแพลตฟอร์มและสะอาดมาก

รายละเอียดเพิ่มเติมสามารถดูได้ใน " ไดเรกทอรีใดที่ทุบตีสคริปต์ใน "


วิธีแก้ไขปัญหาที่ดีมาก แต่จะไม่ทำงานหากไฟล์นั้นเชื่อมโยงกัน
ruuter

16

ฉันจะใช้สิ่งนี้:

# retrieve the full pathname of the called script
scriptPath=$(which $0)

# check whether the path is a link or not
if [ -L $scriptPath ]; then

    # it is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))

else

    # otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)

fi

นี่คือของจริง! ใช้งานได้ง่ายshเช่นกัน! ปัญหาเกี่ยวกับdirname "$0"การแก้ปัญหาพื้นฐานอย่างง่าย: หากสคริปต์อยู่ใน$PATHและถูกเรียกใช้โดยไม่มีพา ธ พวกเขาจะให้ผลลัพธ์ที่ผิด
Notinlist

@Notinlist ไม่เป็นเช่นนั้น ถ้าสคริปต์ที่พบผ่านPATH, $0จะมีชื่อไฟล์ที่แน่นอน ถ้าสคริปต์ถูกเรียกด้วยชื่อไฟล์ที่ญาติหรือแน่นอนมี/, $0จะมีที่
นีลเมย์ฮิว

จะไม่ทำงานกับสคริปต์ที่มา
Amit Naidu

16

นี่คือการแก้ไขเล็กน้อยสำหรับโซลูชัน e-Satis และ 3bcdnlklvc04a ชี้ให้เห็นในคำตอบของพวกเขา :

SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    SCRIPT_DIR="$PWD"
    popd > /dev/null
}    

กรณีนี้ควรใช้งานได้ในทุกกรณีที่ระบุไว้

สิ่งนี้จะป้องกัน popdความล้มเหลวหลังจากpushdขอบคุณ konsolebox


วิธีนี้ทำงานได้อย่างสมบูรณ์เพื่อให้ได้ชื่อ "ของจริง" แทนที่จะเป็นชื่อของ symlink ขอบคุณ!
Jay Taylor

1
ดีกว่าSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox

@ Konsolebox คุณพยายามจะป้องกันอะไร โดยทั่วไปฉันเป็นแฟนของ inlining เงื่อนไขตรรกะ แต่สิ่งที่เป็นข้อผิดพลาดเฉพาะที่คุณได้เห็นใน pushd หรือไม่ ฉันควรจับคู่แทนที่จะหาวิธีจัดการกับมันโดยตรงแทนที่จะส่ง SCRIPT_DIR ที่ว่างกลับมา
Fuwjax

@Fuwjax ปฏิบัติตามธรรมชาติเพื่อหลีกเลี่ยงการทำpopdในกรณี (แม้ในกรณีที่หายาก) ที่pushdล้มเหลว และในกรณีที่pushdล้มเหลวในการทำในสิ่งที่คุณคิดว่าควรจะมีค่าของSCRIPT_DIR? การกระทำอาจแตกต่างกันไปขึ้นอยู่กับสิ่งที่อาจดูสมเหตุสมผลหรือสิ่งที่ผู้ใช้คนหนึ่งอาจชอบ แต่แน่นอนว่าการทำpopdผิด
konsolebox

pushd popdอันตรายทั้งหมดเหล่านั้นสามารถหลีกเลี่ยงได้โดยการวางและใช้cd+ pwdล้อมรอบในการทดแทนคำสั่งแทน SCRIPT_DIR=$(...)
Amit Naidu

16

สำหรับระบบที่มี GNU coreutils readlink (เช่น linux):

$(readlink -f "$(dirname "$0")")

ไม่จำเป็นต้องใช้BASH_SOURCEเมื่อ$0มีชื่อไฟล์สคริปต์


2
นอกเสียจากว่าสคริปต์จะมาด้วย หรือ 'แหล่งที่มา' ซึ่งในกรณีนี้มันจะยังคงเป็นสิ่งที่สคริปต์มาหรือถ้าจากบรรทัดคำสั่ง '-bash' (ล็อกอิน tty) หรือ 'bash' (เรียกผ่าน 'bash -l') หรือ '/ bin / bash '(เรียกใช้เป็นเชลล์ที่ไม่ใช่การเข้าสู่ระบบแบบโต้ตอบ)
osirisgothra

ฉันเพิ่มคู่ของคำพูดรอบการdirnameโทร จำเป็นถ้าเส้นทางไดเรกทอรีประกอบด้วยช่องว่าง
user1338062

14
#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`

ฉันไม่ได้ทดสอบกับระบบอื่น แต่วิธีนี้เป็นทางออกที่ทำงานได้อย่างน้อยใน Ubuntu สำหรับฉัน!
Natus Drew

$0จะไม่ทำงานกับสคริปต์ที่มา
Amit Naidu

13

$_$0เป็นมูลค่าการกล่าวขวัญเป็นทางเลือกให้กับ หากคุณกำลังเรียกใช้สคริปต์จาก Bash คำตอบที่ยอมรับสามารถย่อให้เป็น:

DIR="$( dirname "$_" )"

โปรดทราบว่านี่จะต้องเป็นคำสั่งแรกในสคริปต์ของคุณ


4
มันจะแตกถ้าคุณsourceหรือ.สคริปต์ ในสถานการณ์เหล่านั้นจะมีพารามิเตอร์สุดท้ายของคำสั่งสุดท้ายที่คุณวิ่งก่อน$_ ทำงานทุกครั้ง .$BASH_SOURCE
clacke

11

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

  • เส้นทางแบบสัมบูรณ์หรือเส้นทางแบบสัมพัทธ์
  • ลิงก์ซอฟต์ไฟล์และไดเร็กทอรี
  • การภาวนาเป็นscript, bash script, bash -c script, source scriptหรือ. script
  • ช่องว่างแท็บบรรทัดใหม่ Unicode ฯลฯ ในไดเรกทอรีและ / หรือชื่อไฟล์
  • ชื่อไฟล์เริ่มต้นด้วยเครื่องหมายขีดคั่น

หากคุณกำลังเรียกใช้จาก Linux ดูเหมือนว่าการใช้จุดprocจับเป็นทางออกที่ดีที่สุดในการค้นหาแหล่งที่ได้รับการแก้ไขอย่างสมบูรณ์ของสคริปต์ที่กำลังทำงานอยู่ (ในเซสชันโต้ตอบจุดเชื่อมโยงจะชี้ไปที่ลำดับที่เกี่ยวข้อง/dev/pts/X):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

มันมีความอัปลักษณ์เล็กน้อย แต่การแก้ไขนั้นกะทัดรัดและเข้าใจง่าย เราไม่ได้ใช้งานพื้นฐานของ bash เท่านั้น แต่ฉันก็โอเคกับสิ่งนั้นเพราะreadlinkทำให้งานง่ายขึ้นมาก echo Xเพิ่มXไปยังจุดสิ้นสุดของตัวแปรสตริงเพื่อให้ช่องว่างใด ๆ ต่อท้ายในชื่อไฟล์ไม่ได้รับการกินและการเปลี่ยนตัวผู้เล่นพารามิเตอร์ที่ท้ายบรรทัดที่ได้รับการกำจัดของ${VAR%X} Xเพราะเพื่อแสดงบรรทัดใหม่ (นี่เป็นวิธีที่คุณสามารถสร้างไดเรกทอรีและไฟล์ที่ชื่อ deviously)readlinkเพิ่มบรรทัดใหม่ของตัวเอง (ซึ่งโดยปกติจะกินในการทดแทนคำสั่งหากไม่ใช่สำหรับกลอุบายก่อนหน้าของเรา) เราจึงต้องกำจัดมัน สิ่งนี้สามารถทำได้อย่างง่ายดายที่สุดโดยใช้$''รูปแบบการอ้างอิงซึ่งช่วยให้เราใช้ลำดับการหลีกเลี่ยงเช่น\n

ข้างต้นควรครอบคลุมความต้องการของคุณสำหรับการค้นหาสคริปต์ที่กำลังใช้งานอยู่บน Linux แต่ถ้าคุณไม่มีprocระบบไฟล์ตามที่คุณต้องการหรือหากคุณพยายามที่จะค้นหาเส้นทางที่ได้รับการแก้ไขอย่างสมบูรณ์ของไฟล์อื่น ๆ ค้นหารหัสด้านล่างที่เป็นประโยชน์ มันเป็นการปรับเปลี่ยนเล็กน้อยจากหนึ่งซับด้านบน หากคุณกำลังเล่นกับไดเรกทอรี / ชื่อไฟล์แปลก ๆ ให้ตรวจสอบผลลัพธ์ที่มีทั้งสองอย่างlsและให้readlinkข้อมูลเช่นเดียวกับที่lsจะแสดงเส้นทางที่ "เข้าใจง่าย" แทน?สิ่งต่าง ๆ เช่นการขึ้นบรรทัดใหม่

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"

ฉัน/dev/pts/30ใช้ทุบตีบน Ubuntu 14.10 Desktop
Dan Dascalescu

@DanDascalescu ใช้หนึ่งซับ? หรือข้อมูลโค้ดแบบเต็มที่ด้านล่าง และคุณให้อาหารชื่อเส้นทางที่ยุ่งยากได้ไหม?
billyjmc

บรรทัดหนึ่งรวมกับวงเงินอีกครั้งเพื่อให้echo $resolvedผมบันทึกไว้เป็นd, ,chmod +x d ./d
Dan Dascalescu

@DanDascalescu บรรทัดแรกในสคริปต์ของคุณต้องเป็น#!/bin/bash
billyjmc

10

ลองใช้:

real=$(realpath $(dirname $0))

1
สิ่งที่ฉันอยากรู้ก็คือทำไมทำไมวิธีนี้ถึงไม่ดี? ดูเหมือนไม่เลวและถูกต้องสำหรับฉัน มีใครอธิบายได้ไหม
Shou Ya

7
realpath ไม่ใช่อรรถประโยชน์มาตรฐาน
Steve Bennett

2
บน Linux realpath เป็นยูทิลิตี้มาตรฐาน (ส่วนหนึ่งของแพ็คเกจ GNU coreutils) แต่มันไม่ใช่ bash ในตัว (เช่นฟังก์ชันที่จัดทำโดย bash เอง) หากคุณใช้ Linux วิธีนี้อาจใช้ได้แม้ว่าฉันจะแทนที่ด้วย$0เพื่อ${BASH_SOURCE[0]}ให้วิธีนี้ใช้ได้ทุกที่รวมถึงในฟังก์ชั่น
Doug Richardson

3
คำสั่งของการดำเนินการในคำตอบนี้ไม่ถูกต้อง คุณต้องแรกแก้ไข symlink ที่แล้วทำdirnameเพราะส่วนสุดท้ายของ$0อาจจะ symlink ที่ชี้ไปยังไฟล์ที่ไม่อยู่ในไดเรกทอรีเดียวกันเป็น symlink ตัวเอง วิธีการแก้ปัญหาที่อธิบายไว้ในคำตอบนี้จะได้รับเส้นทางของไดเรกทอรีที่จัดเก็บ symlink ไม่ใช่ไดเรกทอรีของเป้าหมาย นอกจากนี้การแก้ปัญหานี้จะหายไป quoting มันจะไม่ทำงานหากเส้นทางมีอักขระพิเศษ
hagello

3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Kostiantyn Ponomarenko

9

ลองใช้โซลูชันที่เข้ากันได้ต่อไปนี้:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

ตามคำสั่งเช่นrealpathหรือreadlinkอาจไม่พร้อมใช้งาน (ขึ้นอยู่กับระบบปฏิบัติการ)

หมายเหตุ: ใน Bash ขอแนะนำให้ใช้${BASH_SOURCE[0]}แทน$0มิฉะนั้นพา ธ อาจแตกเมื่อทำการจัดหาไฟล์ ( source/ .)

หรือคุณสามารถลองใช้ฟังก์ชันต่อไปนี้ใน bash:

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

ฟังก์ชั่นนี้ใช้เวลา 1 ข้อโต้แย้ง หากอาร์กิวเมนต์มีเส้นทางที่แน่นอนอยู่แล้วให้พิมพ์ตามที่เป็นอยู่มิฉะนั้นพิมพ์$PWDตัวแปร + อาร์กิวเมนต์ชื่อไฟล์ (โดยไม่มี./คำนำหน้า)

ที่เกี่ยวข้อง:


โปรดอธิบายเพิ่มเติมเกี่ยวกับฟังก์ชัน realpath
Chris

1
realpathฟังก์ชัน@Chris ใช้เวลา 1 อาร์กิวเมนต์ หากอาร์กิวเมนต์มีเส้นทางที่แน่นอนอยู่แล้วให้พิมพ์ตามที่เป็นอยู่มิฉะนั้นพิมพ์$PWD+ ชื่อไฟล์ (โดยไม่มี./คำนำหน้า)
kenorb

โซลูชันที่ทำงานร่วมกันไขว้กันของคุณไม่ทำงานเมื่อสคริปต์นั้นเชื่อมโยงกัน
Jakub Jirutka

9

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

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

8

นี่เป็นวิธีย่อในการรับข้อมูลสคริปต์:

โฟลเดอร์และไฟล์:

    Script: "/tmp/src dir/test.sh"
    Calling folder: "/tmp/src dir/other"

ใช้คำสั่งเหล่านี้:

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

และฉันได้ผลลัพธ์นี้:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

ดูเพิ่มเติมที่: https://pastebin.com/J8KjxrPF



ฉันคิดว่าคำตอบของฉันก็โอเคเพราะมันยากที่จะหารุ่นทำงานที่เรียบง่าย ที่นี่คุณสามารถใช้รหัสที่คุณชอบเช่น cd + pwd, dirname + realpath หรือ dirname + readlink ฉันไม่แน่ใจว่าทุกส่วนมีอยู่ก่อนและคำตอบส่วนใหญ่มีความซับซ้อนและมากเกินไป ที่นี่คุณสามารถเก็บรหัสที่คุณชอบใช้ได้ อย่างน้อยโปรดอย่าลบออกตามที่ฉันต้องการในอนาคต: D
User8461

8

ใช้งานได้ใน bash-3.2:

path="$( dirname "$( which "$0" )" )"

หากคุณมี~/binไดเรกทอรีอยู่ใน$PATHนั้นคุณมี Aอยู่ในไดเรกทอรีนี้ ~/bin/lib/Bโดยคัดสคริปต์ คุณทราบว่าสคริปต์ที่รวมนั้นสัมพันธ์กับสคริปต์ดั้งเดิมในlibไดเรกทอรีย่อย แต่ไม่ใช่ตำแหน่งที่สัมพันธ์กับไดเรกทอรีปัจจุบันของผู้ใช้

นี่คือการแก้ไขโดยต่อไปนี้ (ภายในA):

source "$( dirname "$( which "$0" )" )/lib/B"

ไม่สำคัญว่าผู้ใช้อยู่ที่ใดหรือเขาเรียกสคริปต์อย่างไรมันจะทำงานได้ตลอดเวลา


3
ประเด็นที่whichถกเถียงกันมากคือ type, hashและอื่น ๆ builtins ทำสิ่งเดียวกันที่ดีกว่าในการทุบตี whichมันพกพาสะดวกกว่าถึงแม้ว่ามันจะไม่เหมือนกันกับที่whichใช้ในเชลล์อื่น ๆ เช่น tcsh แต่มันมีรูปร่างเหมือน builtin
Reinstate Monica กรุณา

"เสมอ"? ไม่ใช่เลย. whichในฐานะที่เป็นเครื่องมือภายนอกคุณไม่มีเหตุผลที่จะเชื่อว่ามันทำงานเหมือนกับเปลือกแม่
ชาร์ลส์ดัฟฟี่

7

ทางออกที่กะทัดรัดที่สุดในมุมมองของฉันคือ:

"$( cd "$( echo "${BASH_SOURCE[0]%/*}" )"; pwd )"

ไม่มีการพึ่งพาอย่างอื่นนอกจาก Bash การใช้dirname, readlinkและbasenameในที่สุดก็จะนำไปสู่ปัญหาความเข้ากันดังนั้นพวกเขาจะหลีกเลี่ยงที่ดีที่สุดถ้าเป็นไปได้ทั้งหมด


2
"$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )"คุณอาจจะเพิ่มเฉือนที่: คุณจะมีปัญหากับไดเรกทอรีรากถ้าคุณไม่ ทำไมคุณถึงต้องใช้เสียงสะท้อน?
konsolebox

dirnameและbasenamePOSIX เป็นมาตรฐานดังนั้นทำไมหลีกเลี่ยงการใช้มัน? ลิงค์: dirname,basename
myrdd

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