他のスクリプトを含めるにはどうすればよいですか?


353

通常スクリプトを含める方法は「ソース」を使用する方法です

例えば:

main.sh:

#!/bin/bash

source incl.sh

echo "The main script"

incl.sh:

echo "The included script"

「./main.sh」を実行すると、次のように出力されます。

The included script
The main script

...さて、別の場所からそのシェルスクリプトを実行しようとすると、パスにない限り、インクルードを見つけることができません。

スクリプトがインクルードスクリプトを確実に検出できるようにするための良い方法は何ですか(特に、たとえば、スクリプトを移植可能にする必要がある場合)。



1
あなたの質問はとても良く、有益なので、あなたがあなたの質問をする前に私の質問は答えられました!良くやった!
ガブリエルステープルズ

回答:


229

私は自分のスクリプトをすべて相互に関連させる傾向があります。そうすれば、dirnameを使用できます。

#!/bin/sh

my_dir="$(dirname "$0")"

"$my_dir/other_script.sh"

5
これは、スクリプトが$ PATHを介して実行される場合は機能しません。その後、which $0有用であろう
ヒューゴ

41
シェルスクリプトの場所を特定する信頼できる方法はありません。mywiki.wooledge.org/ BashFAQ / 028
Philipp

12
@フィリップ、そのエントリの作者は正しいです、それは複雑で、落とし穴があります。しかし、いくつかの重要な点が欠けています。最初に、作成者はあなたがbashスクリプトで何をしようとしているのかについて多くのことを想定しています。Pythonスクリプトが依存関係なしで実行されることも期待していません。Bashは、他の方法では困難であったことをすばやく実行できるグルー言語です。ビルドシステムを機能させる必要がある場合は、実用主義(およびスクリプトが依存関係を見つけられないという警告)が役に立ちます。
アーロンH.

11
BASH_SOURCE配列について、そしてこの配列の最初の要素が常に現在のソースを指す方法について学習しました。
haridsv 14

6
スクリプトが別の場所にある場合、これは機能しません。たとえば、dirnameは/ home / meを返すため、/ home / me / main.shは/home/me/test/inc.shを呼び出します。BASH_SOURCEを使用してのSacII答えは、良好な溶液であるstackoverflow.com/a/12694189/1000011
opticyclic

187

パーティーに遅れるのはわかっていますが、スクリプトをどのように開始し、ビルトインを排他的に使用しても、これは機能するはずです。

DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "$DIR/incl.sh"
. "$DIR/main.sh"

.(ドット)コマンドはのエイリアスでありsource$PWDは作業ディレクトリのパスで、BASH_SOURCE配列変数で、そのメンバーはソースファイル名であり、${string%substring}$ substringの最短一致を$ stringの後ろから削除します


7
これは一貫して私のために働いたスレッドでの唯一の答えです
Justin

3
@saciiいつラインがif [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi必要になるかわかりますか?コマンドを実行するためにbashプロンプトに貼り付けている場合は、その必要性を見つけることができます。ただし、スクリプトファイルコンテキスト内で実行している場合、その必要性はわかりません...
Johnny Wong

2
また、複数ので期待どおりに機能することにも注意sourceする必要がsourceあります(つまりsource、別のディレクトリに別のスクリプトが存在する場合などでも機能します)。
Ciro Costa

4
これ${BASH_SOURCE[0]}は最新のものだけを呼び出したいのではないですか?また、を使用DIR=$(dirname ${BASH_SOURCE[0]})すると、if条件を取り除くことができます
kshenoy

1
このスニペットをCC0などの属性が不要なライセンスの下で利用できるようにするか、それともパブリックドメインにリリースするだけですか?この逐語的表現を使用したいのですが、すべてのスクリプトの先頭に属性を置くのは面倒です。
BeeOnRope

52

の代替:

scriptPath=$(dirname $0)

です:

scriptPath=${0%/*}

..組み込みコマンドではないdirnameに依存しないことの利点(そして常にエミュレーターで利用できるわけではありません)


2
basePath=$(dirname $0)含まれているスクリプトファイルが読み込まれると、空白の値が表示されました。
prayagupd 2014年

41

同じディレクトリにある場合は、次を使用できますdirname $0

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"

2
2つの落とし穴:1)$0is ./t.shとdirnameは.; 2)cd bin返品後は.正しくありません。$BASH_SOURCE良くないです
18446744073709551615

別の落とし穴:ディレクトリ名にスペースを入れてこれを試してください。
Hubert Grzeskowiak 2017年

source "$(dirname $0)/incl.sh"それらの場合に
有効

27

これを行う最良の方法は、Chris Boranの方法を使用することですが、MY_DIRを次のように計算する必要があります。

#!/bin/sh
MY_DIR=$(dirname $(readlink -f $0))
$MY_DIR/other_script.sh

readlinkのmanページを引用するには:

readlink - display value of a symbolic link

...

  -f, --canonicalize
        canonicalize  by following every symlink in every component of the given 
        name recursively; all but the last component must exist

MY_DIRが正しく計算されないユースケースに遭遇したことはありません。のシンボリックリンクを介してスクリプトにアクセスすると、スクリプトが機能します$PATH


素晴らしくシンプルなソリューションであり、スクリプト呼び出しのさまざまなバリエーションで私が考えられるほど有名です。ありがとう。
ブライアンクライン2013年

引用符の欠落に関する問題以外に、$0直接使用するのではなく、シンボリックリンクを解決する必要がある実際の使用例はありますか?
l0b0

1
@ l0b0:スクリプトが次のようになり、そこからスクリプトを実行/home/you/script.shできるcd /homeと想像してください./you/script.sh。この場合、dirname $0戻り./you、他のスクリプトを含めると失敗します
dr.scre

1
グレート提案は、しかし、私はそれが`に私の変数を読み込むようにするために以下のことを行うために必要な MY_DIR=$(dirname $(readlink -f $0)); source $MY_DIR/incl.sh
フレデリックOllinger

21

この質問への回答の組み合わせは、最も堅牢なソリューションを提供します。

依存関係とディレクトリ構造を強力にサポートする本番環境レベルのスクリプトで機能しました。

#!/ bin / bash

#現在のスクリプトの完全パス
THIS = `readlink -f" $ {BASH_SOURCE [0]} "2> / dev / null || echo $ 0`

#現在のスクリプトが存在するディレクトリ
DIR = `dirname" $ {THIS} "`

#「ドット」は「ソース」、つまり「インクルード」を意味します:
。「$ DIR / compile.sh」

このメソッドは次のすべてをサポートします。

  • パス内のスペース
  • リンク(を介してreadlink
  • ${BASH_SOURCE[0]} より堅牢です $0

1
THIS = readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0 リードリンクがBusyBox v1.01の場合
Alexx Roche

@AlexxRocheありがとうございます!これはすべてのLinuxで機能しますか?
ブライアン・ハーク2017

1
そうなると思います。Debian Sid 3.16とQNAPのarmv5tel 3.4.6 Linuxで動作するようです。
Alexx Roche 2017

2
私はそれをこの1行に入れましたDIR=$(dirname $(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0)) # https://stackoverflow.com/a/34208365/
。– Acumenus

20
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"

1
私は...のdirname「$ 0」は同じことを達成する必要があるときに「... CD」あなたがダウンに投票してしまったことを疑う
アーロン・H.

7
このコードは、スクリプトが現在のディレクトリから実行された場合でも絶対パスを返します。$(dirname "$ 0")だけでは "。"のみが返されます。
最大

1
そして、./incl.sh同じパスに解決さcd+ pwd。では、ディレクトリを変更する利点は何ですか?
l0b0

スクリプトの絶対パスが必要な場合があります。たとえば、ディレクトリを前後に変更する必要がある場合などです。
Laryx Decidua

15

これは、スクリプトがソースであっても機能します。

source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"

「[0]」の目的は何ですか?
レイ

1
@Ray BASH_SOURCEは、コールスタックに対応するパスの配列です。最初の要素は、現在実行中のスクリプトであるスタック内の最新のスクリプトに対応しています。実際に$BASH_SOURCEは、変数として呼び出され、デフォルトで最初の要素に展開されるため、ここ[0]では必要ありません。詳細については、このリンクを参照してください。
Jonathan H

10

1.ニーテスト

私はほとんどすべての提案を調査しました、そしてここに私のために働いた最も最近のものがあります:

script_root=$(dirname $(readlink -f $0))

スクリプトが$PATHディレクトリにシンボリックリンクされている場合でも機能します。

ここで実際にそれを見てください:https//github.com/pendashteh/hcagent/blob/master/bin/hcagent

2.かっこいい

# Copyright https://stackoverflow.com/a/13222994/257479
script_root=$(ls -l /proc/$$/fd | grep "255 ->" | sed -e 's/^.\+-> //')

これは実際にはこのページの別の回答からのものですが、私の回答にも追加しています!

2.最も信頼できる

または、これらが機能しなかったまれなケースで、防弾アプローチは次のとおりです。

# Copyright http://stackoverflow.com/a/7400673/257479
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

script_root=$(dirname $(whereis_realpath "$0"))

あなたはtaskrunnerソースでそれを実際に見ることができます:https : //github.com/pendashteh/taskrunner/blob/master/bin/taskrunner

これが誰かを助けることを願っています:)

また、うまくいかなかった場合はコメントとして残し、オペレーティングシステムとエミュレータについて言及してください。ありがとう!


7

他のスクリプトの場所を指定する必要があります。他の方法はありません。スクリプトの上部に構成可能な変数をお勧めします。

#!/bin/bash
installpath=/where/your/scripts/are

. $installpath/incl.sh

echo "The main script"

または、PROG_HOMEなどのプログラムのホームがどこにあるかを示す環境変数をユーザーが維持するように要求することもできます。これは、/ etc / profile.d /にその情報を含むスクリプトを作成することにより、ユーザーに自動的に提供できます。これは、ユーザーがログインするたびにソースされます。


1
特定性への欲求に感謝しますが、インクルードスクリプトが別のパッケージの一部でない限り、フルパスが必要な理由がわかりません。特定の相対パス(つまり、スクリプトが実行されているのと同じディレクトリ)と特定のフルパスからロードされるセキュリティの違いがわかりません。どうしてそれを回避する方法がないと言うのですか?
Aaron H.

4
スクリプトが実行されているディレクトリは、スクリプトに含めるスクリプトが配置されている場所とは限らないためです。スクリプトがインストールされている場所にスクリプトをロードしたいのですが、それが実行時にどこにあるかを確認する信頼できる方法がありません。固定の場所を使用しないことも、間違った(つまり、ハッカーが提供した)スクリプトを含めて実行するための良い方法です。
スティーブベイカー

6

システム全体のさまざまなコンポーネントの場所を提供することを唯一の目的とするsetenvスクリプトを作成することをお勧めします。

次に、他のすべてのスクリプトがこのスクリプトをソースにするため、setenvスクリプトを使用するすべてのスクリプトですべての場所が共通になります。

これは、cronjobsを実行するときに非常に役立ちます。cronを実行すると最小限の環境が得られますが、すべてのcronスクリプトに最初にsetenvスクリプトを含めるようにすると、cronjobsを実行する環境を制御および同期できます。

私たちは、約2,000 kSLOCのプロジェクト全体で継続的に統合するために使用されたビルドモンキーでこのような手法を使用しました。


3

Steveの返答は間違いなく正しい手法ですが、installpath変数がそのようなすべての宣言が行われる別の環境スクリプトにあるようにリファクタリングする必要があります。

次に、すべてのスクリプトがそのスクリプトをソースとし、installpathを変更する必要があります。変更する必要があるのは、1つの場所だけです。物事をより確実にし、将来を保証します。神、私はその言葉を憎む!(-:

ところで、例に示されている方法で変数を使用する場合、$ {installpath}を使用して変数を実際に参照する必要があります。

. ${installpath}/incl.sh

中括弧が省略されている場合、一部のシェルは変数「installpath / incl.sh」を展開しようとします。


3

シェルスクリプトローダーは、これに対する私のソリューションです。

これは、単一のスクリプトを参照するために多くのスクリプトで何度も呼び出すことができるinclude()という名前の関数を提供しますが、スクリプトは1回しかロードしません。関数は、完全なパスまたは部分的なパスを受け入れることができます(スクリプトは検索パスで検索されます)。無条件にスクリプトをロードするload()という名前の同様の関数も提供されています。

それはのために働くのbashkshのPDのkshzshのとそれらのそれぞれのために最適化されたスクリプト。シェルが提供できる機能に応じて機能を自動的に最適化するユニバーサルスクリプトを通じて、ashdashheirloom shなどの元のshと一般的に互換性のある他のシェル。

【ごちそう例】

start.sh

これはオプションのスタータースクリプトです。ここに起動メソッドを配置するのは便利であり、代わりにメインスクリプトに配置できます。スクリプトをコンパイルする場合も、このスクリプトは必要ありません。

#!/bin/sh

# load loader.sh
. loader.sh

# include directories to search path
loader_addpath /usr/lib/sh deps source

# load main script
load main.sh

main.sh

include a.sh
include b.sh

echo '---- main.sh ----'

# remove loader from shellspace since
# we no longer need it
loader_finish

# main procedures go from here

# ...

include main.sh
include a.sh
include b.sh

echo '---- a.sh ----'

b.sh

include main.sh
include a.sh
include b.sh

echo '---- b.sh ----'

出力:

---- b.sh ----
---- a.sh ----
---- main.sh ----

最良のものは、それに基づいたスクリプトをコンパイルして、使用可能なコンパイラで単一のスクリプトを形成することもできます。

これを使用するプロジェクトは次のとおりです:http : //sourceforge.net/p/playshell/code/ci/master/tree/。スクリプトをコンパイルして、またはコンパイルせずに、移植可能に実行できます。単一のスクリプトを生成するためのコンパイルも発生する可能性があり、インストール時に役立ちます。

また、実装スクリプトがどのように機能するかを簡単に理解したいと考えている保守的な党のために、より単純なプロトタイプを作成しました。https//sourceforge.net/p/loader/code/ci/base/tree/loader-include-prototype .bash。それは小さく、コードがBash 4.0以降で実行することを意図していて、それを使用しない場合は、メインスクリプトにコードを含めることができますeval


3
eval依存関係をロードするための100行を超えるedコードを含む12キロバイトのBashスクリプト。Ouch
l0b0 2014年

1
eval下部にある3つのブロックのうちの1つが常に実行されます。したがって、必要かどうかにかかわらず、確実にを使用していevalます。
l0b0

3
そのeval呼び出しは安全であり、Bash 4.0以降を使用している場合は使用されません。確かに、あなたevalは純粋な悪だと考え、代わりにそれをうまく活用する方法を知らない昔のスクリプト作成者の1人です。
konsolebox 2014年

1
「未使用」とはどういう意味かわかりませんが、走っています。そして、私の仕事の一部としてのシェルスクリプトの数年後、はい、それevalは悪いことだと今まで以上に確信しています。
l0b0

1
ファイルにフラグを付ける2つのポータブルで簡単な方法がすぐに思い浮かびます。それらをiノード番号で参照するか、NULで区切られたパスをファイルに入れることによって参照します。
l0b0 2014年

2

個人的にすべてのライブラリーをlibフォルダーに入れ、import関数を使用してそれらをロードします。

フォルダ構造

ここに画像の説明を入力してください

script.sh 内容物

# Imports '.sh' files from 'lib' directory
function import()
{
  local file="./lib/$1.sh"
  local error="\e[31mError: \e[0mCannot find \e[1m$1\e[0m library at: \e[2m$file\e[0m"
  if [ -f "$file" ]; then
     source "$file"
    if [ -z $IMPORTED ]; then
      echo -e $error
      exit 1
    fi
  else
    echo -e $error
    exit 1
  fi
}

このインポート関数はスクリプトの先頭にある必要があり、次のようにライブラリを簡単にインポートできます。

import "utils"
import "requirements"

各ライブラリ(utils.sh)の上部に1行追加します。

IMPORTED="$BASH_SOURCE"

今、あなたは内部の機能へのアクセスを持っているutils.shrequirements.shから、script.sh

TODO:単一のshファイルを構築するためのリンカーを作成する


これは、スクリプトが存在するディレクトリの外でスクリプトを実行する問題も解決しますか?
アーロンH.

@AaronH。いいえ。大規模なプロジェクトに依存関係を含めるための構造化された方法です。
Xaqron

1

ソースまたは$ 0を使用しても、スクリプトの実際のパスはわかりません。スクリプトのプロセスIDを使用して実際のパスを取得できます

ls -l       /proc/$$/fd           | 
grep        "255 ->"            |
sed -e      's/^.\+-> //'

私はこのスクリプトを使用していて、いつもうまくいきました:)


1

すべての起動スクリプトを.bashrc.dディレクトリに配置しました。これは、/ etc / profile.dなどの場所で一般的な手法です。

while read file; do source "${file}"; done <<HERE
$(find ${HOME}/.bashrc.d -type f)
HERE

グロビングを使用したソリューションの問題...

for file in ${HOME}/.bashrc.d/*.sh; do source ${file};done

...「長すぎる」ファイルリストがある可能性がありますか?のようなアプローチ...

find ${HOME}/.bashrc.d -type f | while read file; do source ${file}; done

...実行されますが、必要に応じて環境は変更されません。


1

もちろん、それぞれ独自のものですが、以下のブロックはかなりしっかりしていると思います。これには、ディレクトリを見つける「最善の」方法と、別のbashスクリプトを呼び出す「最善の」方法が関係していると思います。

scriptdir=`dirname "$BASH_SOURCE"`
source $scriptdir/incl.sh

echo "The main script"

したがって、これが他のスクリプトを含める「最善の」方法かもしれません。これは別の「最良の」答えに基づいています、bashスクリプトに保存場所を通知ます


1

これは確実に機能するはずです:

source_relative() {
 local dir="${BASH_SOURCE%/*}"
 [[ -z "$dir" ]] && dir="$PWD"
 source "$dir/$1"
}

source_relative incl.sh

0

incl.shとmain.shが保存されているフォルダーを見つけるだけです。main.shを次のように変更するだけです。

main.sh

#!/bin/bash

SCRIPT_NAME=$(basename $0)
SCRIPT_DIR="$(echo $0| sed "s/$SCRIPT_NAME//g")"
source $SCRIPT_DIR/incl.sh

echo "The main script"

誤った引用、不要なの使用echo、およびsedgオプションの誤った使用。-1。
l0b0

とにかく、このスクリプトの作業を行う取得しますSCRIPT_DIR=$(echo "$0" | sed "s/${SCRIPT_NAME}//")と、その後source "${SCRIPT_DIR}incl.sh"
Krzysiek

0

man hierスクリプトに適した場所は次のとおりです/usr/local/lib/

/ usr / local / lib

ローカルにインストールされたプログラムに関連するファイル。

個人的に/usr/local/lib/bash/includesはインクルードを好む。その方法でlibを含めるためのbash-helper lib があります。

#!/bin/bash

. /usr/local/lib/bash/includes/bash-helpers.sh

include api-client || exit 1                   # include shared functions
include mysql-status/query-builder || exit 1   # include script functions

# include script functions with status message
include mysql-status/process-checker; status 'process-checker' $? || exit 1
include mysql-status/nonexists; status 'nonexists' $? || exit 1

bash-helpersにはステータス出力が含まれます


-5

次のものも使用できます。

PWD=$(pwd)
source "$PWD/inc.sh"

9
スクリプトが置かれているのと同じディレクトリにいると想定します。他の場所にいる場合は機能しません。
Luc M
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.