โดยทั่วไปฉันจำเป็นต้องเรียกใช้สคริปต์ที่มีเส้นทางที่เกี่ยวข้องกับที่ตั้งไฟล์เชลล์สคริปต์ฉันจะเปลี่ยนไดเรกทอรีปัจจุบันเป็นไดเรกทอรีเดียวกันกับที่ไฟล์สคริปต์อยู่ได้อย่างไร
โดยทั่วไปฉันจำเป็นต้องเรียกใช้สคริปต์ที่มีเส้นทางที่เกี่ยวข้องกับที่ตั้งไฟล์เชลล์สคริปต์ฉันจะเปลี่ยนไดเรกทอรีปัจจุบันเป็นไดเรกทอรีเดียวกันกับที่ไฟล์สคริปต์อยู่ได้อย่างไร
คำตอบ:
ใน Bash คุณควรได้รับสิ่งที่คุณต้องการเช่นนี้:
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
echo "$BASEDIR"
readlink
เช่นกัน (ดูคำตอบของอัลด้านล่าง)
$BASH_SOURCE
แทน$0
เพราะ$0
ไม่ได้มีเส้นทางของสคริปต์ที่ถูกเรียกเช่นเคยเช่นเมื่อ 'sourcing' สคริปต์
$BASH_SOURCE
เฉพาะ Bash คำถามเกี่ยวกับเชลล์สคริปต์โดยทั่วไป
CUR_PATH=$(pwd)
หรือpwd
ส่งคืนไดเรกทอรีปัจจุบัน (ซึ่งไม่จำเป็นต้องเป็นสคริปต์หลัก dir)!
$BASH_SOURCE
และจะคืนสิ่งที่ฉันต้องการ สคริปต์ของฉันถูกเรียกใช้จากสคริปต์อื่นแล้ว$0
ส่งกลับ.
ขณะที่$BASH_SOURCE
ส่งคืนไดเรกทอรีย่อยที่ถูกต้อง (ในกรณีของฉันscripts
)
โพสต์ต้นฉบับมีการแก้ปัญหา (ไม่สนใจคำตอบพวกเขาไม่ได้เพิ่มอะไรที่เป็นประโยชน์ งานที่น่าสนใจทำโดยคำสั่ง unix ที่กล่าวถึงreadlink
พร้อมตัวเลือก-f
ที่มีตัวเลือกทำงานเมื่อสคริปต์ถูกเรียกโดยสัมบูรณ์เช่นเดียวกับโดยเส้นทางสัมพัทธ์
สำหรับ bash, sh, ksh:
#!/bin/bash
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH
สำหรับ tcsh, csh:
#!/bin/tcsh
# Absolute path to this script, e.g. /home/user/bin/foo.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH
ดูเพิ่มเติมที่: https://stackoverflow.com/a/246128/59087
readlink
ไม่ได้ทุกระบบมี นั่นเป็นเหตุผลที่ฉันแนะนำให้ใช้ pushd / popd (บิวด์อินสำหรับทุบตี)
-f
ตัวเลือกที่จะreadlink
ทำอะไรบางอย่างที่แตกต่างกันใน OS X (สิงโต) และอาจ BSD stackoverflow.com/questions/1055671/…
-f
ไม่รองรับ OS X เลย (เหมือนของ Lion); คุณสามารถปล่อยสิ่ง-f
ที่ต้องทำด้วยการแก้ไขได้ในระดับหนึ่งทางอ้อมเช่นpushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"
หรือคุณสามารถม้วนสคริปต์ตามลิงก์ symbian ซ้ำตามที่แสดงในโพสต์ที่เชื่อมโยง
sh /some/other/directory/script.sh)
ในกรณีนี้.
จะเป็น pwd ของคุณไม่ใช่/some/other/directory
ความคิดเห็นก่อนหน้านี้เกี่ยวกับคำตอบบอกว่ามันเป็นเรื่องง่ายที่จะพลาดในทุกคำตอบอื่น ๆ
เมื่อใช้ทุบตี:
echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"
dirname "$BASH_SOURCE"
แทนเพื่อจัดการช่องว่างใน $ BASH_SOURCE
"$(dirname "${BASH_SOURCE[0]}")"
สมมติว่าคุณกำลังใช้ bash
#!/bin/bash
current_dir=$(pwd)
script_dir=$(dirname $0)
echo $current_dir
echo $script_dir
สคริปต์นี้ควรพิมพ์ไดเรกทอรีที่คุณอยู่แล้วไดเรกทอรีที่สคริปต์นั้นอยู่ตัวอย่างเช่นเมื่อเรียกจาก/
ด้วยสคริปต์ใน/home/mez/
นั้นจะส่งออก
/
/home/mez
โปรดจำไว้ว่าเมื่อกำหนดตัวแปรจากผลลัพธ์ของคำสั่งให้ล้อมคำสั่ง$(
และ)
- หรือคุณจะไม่ได้ผลลัพธ์ที่ต้องการ
หากคุณใช้ทุบตี ....
#!/bin/bash
pushd $(dirname "${0}") > /dev/null
basedir=$(pwd -L)
# Use "pwd -P" for the path without links. man bash for more info.
popd > /dev/null
echo "${basedir}"
pushd
/ popd
ด้วยcd $(dirname "${0}")
และcd -
จะทำให้มันทำงานบนเปลือกหอยอื่น ๆ pwd -L
ถ้าพวกเขามี
ในฐานะที่เป็น theMarko แนะนำ:
BASEDIR=$(dirname $0)
echo $BASEDIR
วิธีนี้ใช้งานได้เว้นแต่คุณจะเรียกใช้งานสคริปต์จากไดเรกทอรีเดียวกันกับที่สคริปต์อยู่ซึ่งในกรณีนี้คุณจะได้รับ '' '
ในการหลีกเลี่ยงปัญหานั้นให้ใช้:
current_dir=$(pwd)
script_dir=$(dirname $0)
if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi
ตอนนี้คุณสามารถใช้ตัวแปร current_dir ตลอดทั้งสคริปต์เพื่ออ้างถึงไดเรกทอรีสคริปต์ อย่างไรก็ตามเรื่องนี้อาจยังมีปัญหา symlink
คำตอบที่ดีที่สุดสำหรับคำถามนี้ตอบที่นี่:
รับไดเรกทอรีแหล่งของสคริปต์ทุบตีจากภายใน
และมันคือ:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
หนึ่งซับซึ่งจะให้ชื่อไดเรกทอรีแบบเต็มของสคริปต์ไม่ว่าจะถูกเรียกจากที่ใด
เพื่อให้เข้าใจถึงวิธีการทำงานคุณสามารถรันสคริปต์ต่อไปนี้:
#!/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" )" && pwd )"
if [ "$DIR" != "$RDIR" ]; then
echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"
cd $(dirname $(readlink -f $0))
มาทำให้เป็น POSIX oneliner:
a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)
ทดสอบกับกระสุนที่เข้ากันได้กับ Bourne จำนวนมากรวมถึง BSD
เท่าที่ฉันรู้ว่าฉันเป็นผู้เขียนและฉันใส่ไว้ในโดเมนสาธารณะ สำหรับข้อมูลเพิ่มเติมโปรดดูที่: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/
cd: too many arguments
$PWD
(การแก้ไขที่ชัดเจน แต่ก็แสดงให้เห็นว่าหลายกรณีมีขอบเป็นจริง)
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"
หากคุณต้องการรับไดเรกทอรีสคริปต์จริง (ไม่ว่าคุณจะเรียกใช้สคริปต์โดยใช้ symlink หรือโดยตรง) ให้ลอง:
BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"
สิ่งนี้ใช้ได้กับทั้ง linux และ macOS realpath
ผมมองไม่เห็นคนที่นี่พูดถึงเกี่ยวกับ ไม่แน่ใจว่ามีข้อบกพร่องในวิธีการนี้หรือไม่
บน MacOS คุณจะต้องติดตั้งเพื่อใช้งานcoreutils
เช่น:realpath
brew install coreutils
บทนำ
คำตอบนี้แก้ไขคำตอบที่ได้รับการโหวตแตกหัก แต่ตกตะลึงมากที่สุดของกระทู้นี้ (เขียนโดย TheMarko):
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
echo "$BASEDIR"
ทำไมถึงใช้ dirname "$ 0" เพราะไม่ได้ผล?
dirname $ 0จะใช้งานได้ก็ต่อเมื่อผู้ใช้เปิดตัวสคริปต์ด้วยวิธีที่เฉพาะเจาะจงมาก ฉันสามารถค้นหาสถานการณ์ต่าง ๆ ที่คำตอบนี้ล้มเหลวและขัดข้องสคริปต์
ก่อนอื่นเรามาทำความเข้าใจว่าคำตอบนี้ใช้ได้อย่างไร เขาได้รับไดเรกทอรีสคริปต์โดยทำ
dirname "$0"
$ 0หมายถึงส่วนแรกของคำสั่งที่เรียกใช้สคริปต์ (โดยพื้นฐานแล้วมันคือคำสั่งอินพุตที่ไม่มีอาร์กิวเมนต์:
/some/path/./script argument1 argument2
$ 0 = "/ บาง / เส้นทาง /./ สคริปต์"
โดยทั่วไป dirname จะค้นหาตัวสุดท้าย / ในสตริงและตัดมันที่นั่น ดังนั้นถ้าคุณ:
dirname /usr/bin/sha256sum
คุณจะได้รับ: / usr / bin
ตัวอย่างนี้ทำงานได้ดีเพราะ / usr / bin / sha256sum เป็นเส้นทางที่จัดรูปแบบอย่างถูกต้อง แต่
dirname "/some/path/./script"
จะไม่ทำงานได้ดีและจะให้คุณ:
BASENAME="/some/path/." #which would crash your script if you try to use it as a path
สมมติว่าคุณอยู่ใน dir เดียวกับสคริปต์ของคุณและคุณเปิดใช้งานด้วยคำสั่งนี้
./script
$ 0 ในสถานการณ์นี้จะเป็น. /script และ dirname $ 0 จะให้:
. #or BASEDIR=".", again this will crash your script
โดยใช้:
sh script
โดยไม่ต้องป้อนเส้นทางแบบเต็มจะให้ BASEDIR = "."
ใช้ไดเรกทอรีญาติ:
../some/path/./script
ให้ dirname $ 0 จาก:
../some/path/.
หากคุณอยู่ในไดเรกทอรี / some และคุณเรียกสคริปต์ในลักษณะนี้ (สังเกตว่าไม่มี / ในตอนเริ่มต้นให้ใช้พา ธ สัมพัทธ์อีกครั้ง):
path/./script.sh
คุณจะได้รับค่านี้สำหรับ dirname $ 0:
path/.
และ ./path/./script (รูปแบบอื่นของเส้นทางแบบสัมพัทธ์) ให้:
./path/.
มีเพียงสองสถานการณ์เท่านั้นที่ฐานของ$ 0จะทำงานถ้าผู้ใช้ใช้ sh หรือ touch เพื่อเรียกใช้สคริปต์เนื่องจากทั้งคู่จะส่งผลให้มีค่า $ 0:
$0=/some/path/script
ซึ่งจะให้เส้นทางที่คุณสามารถใช้กับ dirname
การแก้ไขปัญหา
คุณมีบัญชีและตรวจสอบทุกสถานการณ์ดังกล่าวข้างต้นและใช้การแก้ไขหากเกิดขึ้น:
#!/bin/bash
#this script will only work in bash, make sure it's installed on your system.
#set to false to not see all the echos
debug=true
if [ "$debug" = true ]; then echo "\$0=$0";fi
#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)
#situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)
#situation3: different dir but relative path used to launch script
if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi
if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1
_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
if [ "$B_" != "/" ]; then #fix for situation3 #<- bash only
if [ "$B_2" = "./" ]; then
#covers ./relative_path/(./)script
if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
else
#covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
fi
fi
if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi
นี้หนึ่งซับบอกที่เชลล์สคริปต์คือไม่ว่าคุณจะวิ่งหรือถ้าคุณมามัน นอกจากนี้ยังแก้ไขลิงก์สัญลักษณ์ที่เกี่ยวข้องหากเป็นกรณีนี้:
dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))
โดยวิธีการที่ฉันคิดว่าคุณกำลังใช้/ bin / ทุบตี
มีคำตอบมากมายที่เป็นไปได้ทั้งหมดโดยมีวัตถุประสงค์ของโปรและคอนและวัตถุประสงค์ที่แตกต่างกันเล็กน้อย (ซึ่งควรระบุไว้ในแต่ละข้อ) นี่เป็นอีกวิธีการหนึ่งที่ตรงตามวัตถุประสงค์หลักของทั้งความชัดเจนและการทำงานในทุกระบบใน bash ทั้งหมด (ไม่มีข้อสันนิษฐานเกี่ยวกับ bash version หรือreadlink
หรือpwd
ตัวเลือก) และสิ่งที่คุณคาดหวังว่าจะเกิดขึ้น (เช่นการแก้ไข symlink) ปัญหาที่น่าสนใจ แต่ไม่ใช่สิ่งที่คุณต้องการจริง ๆ ) จัดการกับตัวพิมพ์ขอบเช่นช่องว่างในพา ธ ฯลฯ ละเว้นข้อผิดพลาดใด ๆ และใช้ค่าเริ่มต้นที่มีสติหากมีปัญหาใด ๆ
แต่ละองค์ประกอบถูกเก็บไว้ในตัวแปรแยกต่างหากที่คุณสามารถใช้ทีละรายการ:
# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]} # this script's name
PROG_NAME=${PROG_PATH##*/} # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
แรงบันดาลใจจากคำตอบของ Blueyed
read < <(readlink -f $0 | xargs dirname)
cd $REPLY
ที่ควรทำเคล็ดลับ:
echo `pwd`/`dirname $0`
มันอาจดูน่าเกลียดขึ้นอยู่กับว่ามันถูกเรียกใช้และ cwd แต่ควรให้คุณไปที่ที่คุณต้องการไป (หรือคุณสามารถปรับแต่งสตริงถ้าคุณสนใจว่ามันดู)
`pwd`/`dirname $0`
แต่อาจยังคงล้มเหลวใน symlinks