Về cơ bản tôi cần chạy tập lệnh với các đường dẫn liên quan đến vị trí tệp tập lệnh shell, làm thế nào tôi có thể thay đổi thư mục hiện tại thành cùng thư mục với nơi tập tin tập lệnh nằm?
Về cơ bản tôi cần chạy tập lệnh với các đường dẫn liên quan đến vị trí tệp tập lệnh shell, làm thế nào tôi có thể thay đổi thư mục hiện tại thành cùng thư mục với nơi tập tin tập lệnh nằm?
Câu trả lời:
Trong Bash, bạn sẽ nhận được những gì bạn cần như thế này:
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
echo "$BASEDIR"
readlink
(xem câu trả lời của al bên dưới)
$BASH_SOURCE
thay thế sẽ an toàn hơn $0
, bởi vì $0
không phải lúc nào cũng chứa đường dẫn của tập lệnh được gọi, chẳng hạn như khi 'tìm nguồn cung ứng' một tập lệnh.
$BASH_SOURCE
là đặc thù của Bash, câu hỏi là về kịch bản shell nói chung.
CUR_PATH=$(pwd)
hoặc pwd
trả về thư mục hiện tại (không phải là thư mục cha mẹ)!
$BASH_SOURCE
và nó trả về những gì tôi cần. Kịch bản của tôi đang được gọi từ một kịch bản khác và $0
trả về .
trong khi $BASH_SOURCE
trả về thư mục con bên phải (trong trường hợp của tôi scripts
).
Bài đăng gốc chứa giải pháp (bỏ qua các phản hồi, họ không thêm bất cứ điều gì hữu ích). Công việc thú vị được thực hiện bằng lệnh unix đã đề cập readlink
với tùy chọn -f
. Hoạt động khi tập lệnh được gọi bởi một đường dẫn tuyệt đối cũng như tương đối.
Đối với 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
Đối với 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
Xem thêm: https://stackoverflow.com/a/246128/59087
readlink
. Đó là lý do tại sao tôi khuyên bạn nên sử dụng Pushd / popd (tích hợp cho bash).
-f
tùy chọn để readlink
làm điều gì đó khác nhau trên OS X (Lion) và có thể BSD. stackoverflow.com/questions/1055671/
-f
không được hỗ trợ trên OS X (kể từ Lion); ở đó, bạn có thể bỏ qua -f
để giải quyết tối đa một mức độ gián tiếp, ví dụ pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"
, hoặc bạn có thể cuộn đoạn mã theo dõi liên kết đệ quy của riêng mình như được trình bày trong bài được liên kết.
sh /some/other/directory/script.sh)
trong trường hợp này .
sẽ là pwd của bạn, không phải/some/other/directory
Một nhận xét trước đó về một câu trả lời đã nói điều đó, nhưng rất dễ bỏ lỡ trong số tất cả các câu trả lời khác.
Khi sử dụng bash:
echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"
dirname "$BASH_SOURCE"
Thay vào đó, bạn nên sử dụng để xử lý khoảng trắng trong $ BASH_SOURCE.
"$(dirname "${BASH_SOURCE[0]}")"
Giả sử bạn đang sử dụng bash
#!/bin/bash
current_dir=$(pwd)
script_dir=$(dirname $0)
echo $current_dir
echo $script_dir
Tập lệnh này sẽ in thư mục mà bạn đang ở, và sau đó là thư mục mà tập lệnh nằm trong. Ví dụ: khi gọi nó từ /
với tập lệnh trong /home/mez/
, nó xuất ra
/
/home/mez
Hãy nhớ rằng, khi gán các biến từ đầu ra của lệnh, hãy bọc lệnh vào $(
và )
- hoặc bạn sẽ không nhận được đầu ra mong muốn.
Nếu bạn đang sử dụng bash ....
#!/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
bằng cd $(dirname "${0}")
và cd -
để làm cho nó hoạt động trên các shell khác, nếu chúng có pwd -L
.
Như theMarko gợi ý:
BASEDIR=$(dirname $0)
echo $BASEDIR
Điều này hoạt động trừ khi bạn thực thi tập lệnh từ cùng thư mục chứa tập lệnh, trong trường hợp đó bạn nhận được giá trị '.'
Để giải quyết vấn đề đó, hãy sử dụng:
current_dir=$(pwd)
script_dir=$(dirname $0)
if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi
Bây giờ bạn có thể sử dụng biến current_dir trong toàn bộ tập lệnh của mình để tham chiếu đến thư mục tập lệnh. Tuy nhiên điều này vẫn có thể có vấn đề liên kết tượng trưng.
Câu trả lời tốt nhất cho câu hỏi này đã được trả lời ở đây:
Lấy thư mục nguồn của tập lệnh Bash từ bên trong
Và đó là:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
One-liner sẽ cung cấp cho bạn tên thư mục đầy đủ của tập lệnh bất kể nó được gọi từ đâu.
Để hiểu cách thức hoạt động, bạn có thể thực thi đoạn mã sau:
#!/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'"
Hãy biến nó thành một POSIX oneliner:
a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)
Đã thử nghiệm trên nhiều hệ vỏ tương thích Bourne, bao gồm cả các vỏ BSD.
Theo như tôi biết tôi là tác giả và tôi đưa nó vào phạm vi công cộng. Để biết thêm thông tin, hãy xem: https://www.jasan.tk/posts/2017-05-11-poseix_shell_dirname_Vplocation/
cd: too many arguments
nếu khoảng trắng trong đường dẫn và trả về $PWD
. (một sửa chữa rõ ràng, nhưng chỉ cho thấy có bao nhiêu trường hợp thực sự có)
Nếu bạn muốn lấy thư mục tập lệnh thực tế (bất kể bạn đang gọi tập lệnh bằng liên kết tượng trưng hay trực tiếp), hãy thử:
BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"
Điều này hoạt động trên cả linux và macOS. Tôi không thể thấy ai ở đây đề cập đếnrealpath
. Không chắc chắn liệu có bất kỳ nhược điểm trong phương pháp này.
trên macOS, bạn cần cài đặt coreutils
để sử dụng realpath
. Vd : brew install coreutils
.
GIỚI THIỆU
Câu trả lời này sửa câu trả lời được bình chọn rất hàng đầu nhưng gây sốc của chủ đề này (được viết bởi TheMarko):
#!/usr/bin/env bash
BASEDIR=$(dirname "$0")
echo "$BASEDIR"
TẠI SAO SỬ DỤNG dirname "$ 0" TRÊN NÓ KHÔNG LÀM VIỆC?
tên thư $ 0 sẽ chỉ hoạt động nếu người dùng khởi chạy tập lệnh theo một cách rất cụ thể. Tôi đã có thể tìm thấy một số tình huống trong đó câu trả lời này không thành công và làm hỏng kịch bản.
Trước hết, hãy hiểu câu trả lời này hoạt động như thế nào. Anh ấy nhận được thư mục kịch bản bằng cách làm
dirname "$0"
$ 0 đại diện cho phần đầu tiên của lệnh gọi tập lệnh (về cơ bản đó là lệnh được nhập mà không có đối số:
/some/path/./script argument1 argument2
$ 0 = "/ some / path /./ script"
Về cơ bản, dirname tìm thấy cuối cùng / trong một chuỗi và cắt nó ở đó. Vì vậy, nếu bạn làm:
dirname /usr/bin/sha256sum
bạn sẽ nhận được: / usr / bin
Ví dụ này hoạt động tốt vì / usr / bin / sha256sum là một đường dẫn được định dạng đúng nhưng
dirname "/some/path/./script"
sẽ không hoạt động tốt và sẽ cung cấp cho bạn:
BASENAME="/some/path/." #which would crash your script if you try to use it as a path
Giả sử bạn đang ở cùng thư mục với tập lệnh của mình và bạn khởi chạy nó bằng lệnh này
./script
$ 0 trong tình huống này sẽ là ./script và dirname $ 0 sẽ cung cấp:
. #or BASEDIR=".", again this will crash your script
Sử dụng:
sh script
Không nhập vào đường dẫn đầy đủ cũng sẽ cho BASEDIR = "."
Sử dụng các thư mục tương đối:
../some/path/./script
Cung cấp một dirname $ 0 trong số:
../some/path/.
Nếu bạn đang ở trong thư mục / some và bạn gọi script theo cách này (lưu ý sự vắng mặt của / ở đầu, lại là một đường dẫn tương đối):
path/./script.sh
Bạn sẽ nhận được giá trị này cho dirname $ 0:
path/.
và ./path/./script (một dạng khác của đường dẫn tương đối) đưa ra:
./path/.
Hai tình huống duy nhất dựa trên $ 0 sẽ hoạt động là nếu người dùng sử dụng sh hoặc chạm để khởi chạy tập lệnh vì cả hai sẽ dẫn đến $ 0:
$0=/some/path/script
Nó sẽ cung cấp cho bạn một đường dẫn bạn có thể sử dụng với dirname.
GIẢI PHÁP
Bạn sẽ có tài khoản và phát hiện mọi tình huống được đề cập ở trên và áp dụng cách khắc phục nếu xảy ra:
#!/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
Lớp lót này cho biết tập lệnh shell nằm ở đâu, không quan trọng nếu bạn chạy nó hoặc nếu bạn có nguồn gốc nó . Ngoài ra, nó giải quyết bất kỳ liên kết tượng trưng liên quan, nếu đó là trường hợp:
dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))
Nhân tiện, tôi cho rằng bạn đang sử dụng / bin / bash .
Rất nhiều câu trả lời, tất cả đều hợp lý, mỗi câu hỏi đều có mục tiêu ủng hộ và khác biệt (có lẽ nên được nêu rõ cho từng mục tiêu). Đây là một giải pháp khác đáp ứng mục tiêu chính là cả rõ ràng và hoạt động trên tất cả các hệ thống, trên tất cả các bash (không có giả định về các phiên bản bash, hoặc readlink
hoặcpwd
tùy chọn) và hợp lý là những gì bạn mong muốn xảy ra (ví dụ: giải quyết các liên kết tượng trưng là một vấn đề thú vị, nhưng thường không phải là những gì bạn thực sự muốn), xử lý các trường hợp cạnh như khoảng trắng trong đường dẫn, v.v., bỏ qua mọi lỗi và sử dụng mặc định lành mạnh nếu có bất kỳ vấn đề nào.
Mỗi thành phần được lưu trữ trong một biến riêng biệt mà bạn có thể sử dụng riêng lẻ:
# 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)"
Lấy cảm hứng từ câu trả lời của blueyed
read < <(readlink -f $0 | xargs dirname)
cd $REPLY
Điều đó sẽ làm các trick:
echo `pwd`/`dirname $0`
Nó có thể trông xấu xí tùy thuộc vào cách nó được gọi và cwd nhưng sẽ đưa bạn đến nơi bạn cần đến (hoặc bạn có thể điều chỉnh chuỗi nếu bạn quan tâm nó trông như thế nào).
`pwd`/`dirname $0`
nhưng vẫn có thể thất bại trên các liên kết tượng trưng