簡體   English   中英

Unix shell 腳本 找出腳本文件所在的目錄?

[英]Unix shell script find out which directory the script file resides?

基本上我需要使用與 shell 腳本文件位置相關的路徑運行腳本,如何將當前目錄更改為與腳本文件所在的目錄相同的目錄?

在 Bash 中,你應該得到你需要的東西:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

原始帖子包含解決方案(忽略響應,它們沒有添加任何有用的東西)。 有趣的工作是由上面提到的帶有選項-f unix 命令readlink完成的。 當腳本被絕對路徑和相對路徑調用時有效。

對於 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

之前對某個答案的評論說過,但在所有其他答案中很容易錯過。

使用 bash 時:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Bash 參考手冊,5.2 Bash 變量

假設您正在使用 bash

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname "$0")

echo $current_dir
echo $script_dir

此腳本應打印您所在的目錄,然后是該腳本所在的目錄。例如,當從/使用/home/mez/的腳本調用它時,它會輸出

/
/home/mez

請記住,從命令的輸出中分配變量時,請將命令包裝在$() - 否則您將無法獲得所需的輸出。

如果您使用的是 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}"

這個問題的最佳答案在這里回答:
從內部獲取 Bash 腳本的源目錄

它是:

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

正如馬可建議的那樣:

BASEDIR=$(dirname $0)
echo $BASEDIR

除非您從腳本所在的同一目錄中執行腳本,否則此方法有效,在這種情況下,您將獲得值 '.'。

要解決該問題,請使用:

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

您現在可以在整個腳本中使用變量 current_dir 來引用腳本目錄。 但是,這可能仍然存在符號鏈接問題。

如果您想獲取實際的腳本目錄(無論您是使用符號鏈接還是直接調用腳本),請嘗試:

BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"

這適用於 linux 和 macOS。 我看不到這里有人提到realpath 不確定這種方法是否有任何缺點。

在 macOS 上,您需要安裝coreutils才能使用realpath 例如: brew install coreutils

cd $(dirname $(readlink -f $0))
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"

讓我們把它變成一個 POSIX oneliner:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)

在許多與 Bourne 兼容的 shell 上進行了測試,包括 BSD 的 shell。

據我所知,我是作者,並將其放入公共領域。 有關更多信息,請參閱: https : //www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/

介紹

這個答案糾正了這個線程(由 TheMarko 編寫)非常破碎但令人震驚的最高投票答案:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

為什么自己使用目錄名“$0”不起作用?

dirname $0僅在用戶以非常特定的方式啟動腳本時才有效。 我能夠找到幾種情況,其中此答案失敗並使腳本崩潰。

首先,讓我們了解這個答案是如何工作的。 他通過執行獲取腳本目錄

dirname "$0"

$0代表調用腳本的命令的第一部分(它基本上是沒有參數的輸入命令:

/some/path/./script argument1 argument2

$0="/some/path/./script"

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

假設您與腳本位於同一目錄中,並使用此命令啟動它

./script   

在這種情況下 $0 將是 ./script 並且 dirname $0 將給出:

. #or BASEDIR=".", again this will crash your script

使用:

sh script

不輸入完整路徑也會給出 BASEDIR="。"

使用相對目錄:

 ../some/path/./script

給出以下目錄名 $0:

 ../some/path/.

如果您在 /some 目錄中並以這種方式調用腳本(注意開頭沒有 /,同樣是相對路徑):

 path/./script.sh

您將獲得 dirname $0 的此值:

 path/. 

和 ./path/./script (相對路徑的另一種形式)給出:

 ./path/.

basedir $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

這個單行說明了 shell 腳本的位置,無論您是運行它還是獲取它都無關緊要 此外,它會解析所涉及的任何符號鏈接,如果是這樣的話:

dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))

順便說一句,我想您正在使用/bin/bash

如此多的答案,全都似是而非,每個答案都有優點和缺點,目標略有不同(可能應該為每個答案說明)。 這是另一個解決方案,它滿足了在所有系統上清晰並在所有 bash 上工作(不假設 bash 版本,或readlinkpwd選項)的主要目標,並且合理地執行您期望發生的事情(例如,解析符號鏈接是一個有趣的問題,但通常不是您真正想要的),處理路徑中的空格等邊緣情況,忽略任何錯誤並在出現任何問題時使用合理的默認值。

每個組件都存儲在一個單獨的變量中,您可以單獨使用:

# 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

使用 tcsh,您可以使用:h變量修飾符來檢索路徑。

一個警告是,如果腳本作為tcsh myscript.csh執行,那么您將只能獲得腳本名稱。 解決方法是驗證路徑,如下所示。

#!/bin/tcsh

set SCRIPT_PATH = $0:h
if ( $SCRIPT_PATH == $0 ) then
        set SCRIPT_PATH = "."
endif

$SCRIPT_PATH/compile.csh > $SCRIPT_PATH/results.txt

有關變量修飾符的更多信息,訪問https://learnxinyminutes.com/docs/tcsh/

基礎版:

dir=$(dirname $0)

如果腳本可能通過$PATH調用,則:

dir=$(dirname $(which $0))

如果腳本可以這樣調用: bash script.sh ,則:

dir=$(dirname $(which $0 2>/dev/null || realpath ./$0))

如果你覺得非常不安全,那么:

dir="$(dirname -- "$(which -- "$0" 2>/dev/null || realpath -- "./$0")")"

我發現的最強大的方法之一是

#!/bin/sh
relative_dir=`perl -e 'use Cwd "realpath";$pwd = realpath(shift); $pwd =~ s/\/[^\/]*$//; print $pwd' $0`
cd $relative_dir

使用符號鏈接並為我的許多同事工作,無論他們選擇 shell 類型

這應該夠了吧:

echo `pwd`/`dirname $0`

它可能看起來很難看,具體取決於它的調用方式和 cwd,但應該能讓你到達你需要去的地方(或者,如果你關心它的外觀,你可以調整字符串)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM