web-dev-qa-db-ja.com

Unixシェルスクリプトは、スクリプトファイルが存在するディレクトリを見つけますか?

基本的に、シェルスクリプトファイルの場所に関連するパスを使用してスクリプトを実行する必要があります。現在のディレクトリを、スクリプトファイルが存在する場所と同じディレクトリに変更する方法を教えてください。

414
William Yeung

Bashでは、このように必要なものを入手する必要があります。

#!/usr/bin/env bash

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

元の投稿には解決策が含まれています(応答を無視します。有用なものは追加されません)。興味深い作業は、オプション-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 も参照してください。

348
al.

ある回答についての以前のコメントはそれを言っていましたが、他のすべての回答の中で見逃しやすいです。

Bashを使うとき:

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

Bashリファレンスマニュアル、5.2 Bash変数

46
Daniel

Bashを使っているとしましょう

#!/bin/bash

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

echo $current_dir
echo $script_dir

このスクリプトは、自分がいるディレクトリを表示し、次にそのスクリプトがあるディレクトリを表示します。たとえば、/のスクリプトを使用して/home/mez/から呼び出すと、

/
/home/mez

コマンドの出力から変数を代入するときは、コマンドを$()で囲むと、思いどおりの出力が得られません。

38
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}"
31
docwhat

TheMarkoが示唆するように:

BASEDIR=$(dirname $0)
echo $BASEDIR

スクリプトが存在するのと同じディレクトリからスクリプトを実行しない限り、これは機能します。

この問題を回避するには

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

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

スクリプトディレクトリ全体を参照するために、スクリプト全体で変数current_dirを使用できるようになりました。しかしこれにはまだシンボリックリンクの問題があるかもしれません。

16
ranamalo

この質問に対する最良の答えはここで答えられました:
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'"
15
cd $(dirname $(readlink -f $0))
13
blueyed

それをPOSIX onelinerにしましょう:

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

BSDを含む多くのBourne互換シェルでテスト済み。

私の知る限りでは、私は作者であり、それをパブリックドメインにしています。詳細については、以下を参照してください。 https://www.jasan.tk/posts/2017-05-11-posix_Shell_dirname_replacement/

7
Ján Sáreník
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"
7
searchrome

非常に多くの答えがありますが、それぞれ長所と短所があり、目的が多少異なります(おそらくそれぞれについて述べる必要があります)。これは、すべてのシステムにわたって、すべてのbashで明確であり、機能するという(bashバージョン、またはreadlinkまたはpwdオプションに関する仮定なしの)基本的な目的を満たし、シンボリックリンクの解決などを合理的に実現します。これは興味深い問題ですが、通常は実際には望んでいることではありません。パス内のスペースなどのEdgeのケースを処理し、エラーを無視し、問題がある場合は適切なデフォルトを使用します。

各コンポーネントは個別に使用できる個別の変数に格納されています。

# 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)"
2
michael

前書き

この回答このスレッドの非常に壊れているが衝撃的にトップ投票された回答を修正します(TheMarkoによって書かれた):

#!/usr/bin/env bash

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

なぜdirname "$ 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 が機能する唯一の2つの状況は、ユーザーが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
2
thebunnyrules

青い答え /からヒントを得た

read < <(readlink -f $0 | xargs dirname)
cd $REPLY
2
Steven Penny

このワンライナーはシェルスクリプトがどこにあるのかを伝えます、はあなたがそれを実行したかどうかは関係ありません。また、関連するシンボリックリンクがあればそれも解決します。

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

ところで、私はあなたが/ bin/bashを使っていると思います。

2
Richard Gomes

実際のスクリプトディレクトリを取得したい場合は(シンボリックリンクを使用してスクリプトを起動しているか直接起動しているかに関係なく)、

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

これはLinuxとmacOSの両方で動作します。ここで誰もがrealpathについて言及しているのを見ることができませんでした。このアプローチに欠点があるかどうかわからない。

macOSでは、coreutilsを使うためにはrealpathをインストールする必要があります。例:brew install coreutils

1
Rohith