Diğer komut dosyalarını en iyi nasıl ekleyebilirim?


353

Normalde komut dosyası ekleme şekliniz "kaynak"

Örneğin:

main.sh:

#!/bin/bash

source incl.sh

echo "The main script"

incl.sh:

echo "The included script"

"./Main.sh" yürütme çıktısı:

The included script
The main script

... Şimdi, o kabuk komut dosyasını başka bir konumdan çalıştırmayı denerseniz, yolunuzda olmadıkça içerme işlemini bulamaz.

Komut dosyanızın dahil edilen komut dosyasını bulabilmesini sağlamanın iyi bir yolu nedir, özellikle de komut dosyasının taşınabilir olması gerekiyorsa?



1
Sorunuz o kadar iyi ve bilgilendiricidir ki , sorunuzu bile sormadan önce soruma cevap verildi! İyi iş!
Gabriel Staples

Yanıtlar:


229

Senaryolarımı birbirine göreli olmaya meyilliyim. Bu şekilde dirname kullanabilirsiniz:

#!/bin/sh

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

"$my_dir/other_script.sh"

5
Komut dosyası $ PATH aracılığıyla yürütülürse bu çalışmaz. o which $0zaman yararlı olacaktır
Hugo

41
Bir kabuk betiğinin yerini belirlemenin güvenilir bir yolu yoktur, bkz. Mywiki.wooledge.org/BashFAQ/028
Philipp

12
@Philipp, Bu girişin yazarı doğru, karmaşık ve gotchas var. Ancak bazı önemli noktaları kaçırıyor, ilk önce yazar, bash komut dosyanızla ne yapacağınız hakkında birçok şey varsayar. Ben de bir python betiğinin bağımlılıkları olmadan çalışmasını beklemezdim. Bash, aksi takdirde zor olacak işleri hızlı bir şekilde yapmanıza izin veren bir tutkal dilidir. Çalışmak için sisteminize ihtiyaç duyduğunuzda pragmatizm (Ve senaryo hakkında bağımlılık bulamama konusunda hoş bir uyarı) kazanır.
Aaron H.

11
BASH_SOURCEDiziyi ve bu dizideki ilk öğenin her zaman geçerli kaynağı nasıl gösterdiğini öğrendim .
haridsv

6
Komut dosyaları farklı konumlardaysa bu çalışmaz. Örneğin /home/me/main.sh, /home/me/test/inc.sh adresini dirname olarak döndürür / home / me döndürür. BASH_SOURCE kullanarak sacii cevap daha iyi bir çözüm stackoverflow.com/a/12694189/1000011
optik

187

Partiye geç kaldığımı biliyorum, ancak senaryoyu nasıl başlatırsanız ve yerleşikleri yalnızca nasıl kullanırsanız kullanın, bu işe yaramalıdır:

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

.(nokta) komutu için bir diğer adıdır source, $PWDÇalışma Directory için Yoludur, BASH_SOURCEüyeleri kaynak dosyalarının isimleri bir dizi değişkeni ise, ${string%substring}$ dize arkasından alt dize $ en kısa maçı şeritler


7
Bu benim için sürekli iş parçacığı tek cevap
Justin

3
@sacii Hattın ne zaman if [[ ! -d "$DIR" ]]; then DIR="$PWD"; figerekli olduğunu öğrenebilir miyim ? Komutları çalıştırmak için bir bash istemine yapıştırılıyorsa buna ihtiyacım var. Ancak, bir komut dosyası bağlamında çalışıyorsanız, buna ihtiyacım olduğunu göremiyorum ...
Johnny Wong

2
Ayrıca, birden fazla sources'de beklendiği gibi çalıştığını da belirtmek gerekir (yani, başka bir dizinde başka sourcebir komut dosyası vb. Varsa source, yine de çalışır).
Ciro Costa

4
Bu ${BASH_SOURCE[0]}sadece en son aranan gibi değil mi? Ayrıca, kullanarak DIR=$(dirname ${BASH_SOURCE[0]})if-koşulundan kurtulmanızı sağlayacaktır
kshenoy

1
Bu snippet'i, CC0 gibi nitelik gerektirmeyen bir lisans altında kullanılabilir hale getirmek mi yoksa yalnızca kamu malı olarak yayınlamak mı istiyorsunuz? Bu kelimeyi kullanmak istiyorum, ancak atıfları her komut dosyasının en üstüne koymak can sıkıcı!
BeeOnRope

52

Bir alternatif:

scriptPath=$(dirname $0)

dır-dir:

scriptPath=${0%/*}

.. avantajı, yerleşik bir komut olmayan (ve her zaman emülatörlerde bulunmayan) dirname bağımlılığı olmamasıdır.


2
basePath=$(dirname $0)içeren komut dosyası kaynaklandığında bana boş değer verdi.
prayagupd

41

Aynı dizinde ise şunları kullanabilirsiniz dirname $0:

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"

2
İki tuzak: 1) $0dir ./t.shve dirname döndürür .; 2) cd biniade sonra .doğru değil. $BASH_SOURCEdaha iyi değil.
18446744073709551615

başka bir tuzak: bunu dizin adındaki boşluklarla deneyin.
Hubert Grzeskowiak

source "$(dirname $0)/incl.sh"bu davalar için çalışıyor
dsm

27

Bunu yapmanın en iyi yolunun Chris Boran'ın yolunu kullanmak olduğunu düşünüyorum, ancak MY_DIR'ı şu şekilde hesaplamalısınız:

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

Readlink için kılavuz sayfalarını alıntılamak için:

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_DIRDoğru hesaplanmayan bir kullanım durumuyla hiç karşılaşmadım . Senaryonuza bir symlink üzerinden erişirseniz $PATHçalışır.


Güzel ve basit bir çözüm ve aklıma gelen birçok senaryo çağırma varyasyonunda benim için ünlü çalışıyor. Teşekkürler.
Brian Cline

Eksik tırnaklarla ilgili sorunların yanı sıra, $0doğrudan kullanmak yerine sembolik bağlantıları çözmek istediğiniz herhangi bir gerçek kullanım durumu var mı?
l0b0

1
@ l0b0: Senaryonuzu düşünün /home/you/script.shSen sen-ebilmek cd /homeçalıştırmak ve senaryonuzu oradan çalıştırmak ./you/script.shBu durumda dirname $0dönecek ./youve diğer script dahil başarısız olacaktır
dr.scre

1
Harika bir öneri, ancak, değişkenlerimi okuyabilmesi için aşağıdakileri yapmam gerekiyordu MY_DIR=$(dirname $(readlink -f $0)); source $MY_DIR/incl.sh
Frederick Ollinger

21

Bu soruya verilen cevapların bir kombinasyonu en sağlam çözümü sunar.

Bizim için bağımlılıklara ve dizin yapısına büyük destek veren üretim sınıfı senaryolarda çalıştı:

#! / Bin / bash

# Geçerli komut dosyasının tam yolu
THIS = `readlink -f" $ {BASH_SOURCE [0]} "2> / dev / null || echo $ 0`

# Geçerli komut dosyasının bulunduğu dizin
DIR = `dirname" $ ​​{THIS} "`

# 'Dot', 'kaynak', yani 'içerir' anlamına gelir:
. "$ DIR / compile.sh"

Yöntem bunların tümünü destekler:

  • Spaces yolunda
  • Bağlantılar (yoluyla readlink)
  • ${BASH_SOURCE[0]} daha sağlam $0

1
BU = readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0 readlink'iniz BusyBox v1.01 ise
Alexx Roche

@AlexxRoche teşekkür ederim! Bu tüm Linux'larda çalışır mı?
Brian Haak

1
Ben beklerdim. Debian Sid 3.16 ve QNAP'ın armv5tel 3.4.6 Linux üzerinde çalışıyor gibi görünüyor.
Alexx Roche

2
Bunu bir sıraya koydum: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
"$ 0" dirname aynı şeyi başarması gerektiğinde "cd ..." için oy kullandığınızdan şüpheleniyorum ...
Aaron H.

7
Bu kod, komut dosyası geçerli dizinden yürütüldüğünde bile mutlak yol döndürür. $ (dirname "$ 0") yalnızca "."
Max

1
Ve ./incl.shaynı yolu giderir cd+ pwd. Peki dizini değiştirmenin avantajı nedir?
l0b0

Bazen komut dosyasının mutlak yoluna ihtiyacınız olabilir, örneğin dizinleri ileri geri değiştirmeniz gerektiğinde.
Laryx Decidua

15

Bu komut dosyası kaynaklansa bile çalışır:

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

"[0]" ın amacını açıklayabilir misiniz?
Ray

1
@Ray BASH_SOURCE, bir çağrı yığınına karşılık gelen bir yol dizisidir. İlk öğe, şu anda yürütülen komut dosyası olan yığındaki en son komut dosyasına karşılık gelir. Aslında, $BASH_SOURCEdeğişken olarak adlandırılan varsayılan olarak ilk öğesine genişler, bu yüzden [0]burada gerekli değildir. Ayrıntılar için bu bağlantıya bakın.
Jonathan H

10

1. Neatest

Neredeyse her öneriyi araştırdım ve işte benim için çalışan en güzel öneri:

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

Komut dosyası bir $PATHdizine bağlandığında bile çalışır .

Burada iş başında görün: https://github.com/pendashteh/hcagent/blob/master/bin/hcagent

2. havalı

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

Bu aslında bu sayfadaki başka bir cevaptan, ama ben de cevabımı ekliyorum!

2. en güvenilir

Alternatif olarak, bunların işe yaramadığı nadir durumlarda, işte kurşun geçirmez yaklaşım:

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

Eylem halinde taskrunnerkaynağında görebilirsiniz: https://github.com/pendashteh/taskrunner/blob/master/bin/taskrunner

Umarım bu kişiye yardım eder :)

Ayrıca, sizin için işe yaramadıysa ve bir yorum olarak bırakın ve işletim sisteminizden ve öykünücünüzden bahsedin. Teşekkürler!


7

Diğer komut dosyalarının konumunu belirtmeniz gerekir, etrafında başka bir yol yoktur. Komut dosyanızın üst kısmında yapılandırılabilir bir değişken öneririm:

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

. $installpath/incl.sh

echo "The main script"

Alternatif olarak, kullanıcının PROG_HOME veya bunun gibi bir program değişkeninin bulunduğu yerde olduğunu belirten bir ortam değişkenini korumasında ısrar edebilirsiniz. Bu, /etc/profile.d/ dosyasında bir kullanıcı her oturum açtığında kaynaklanacak bir komut dosyası oluşturarak kullanıcı için otomatik olarak sağlanabilir.


1
Özgüllük arzusunu takdir ediyorum, ancak içerilen komut dosyaları başka bir paketin parçası olmadıkça neden tam yolun gerekli olması gerektiğini göremiyorum. Belirli bir göreli yoldan belirli bir göreli yoldan (yani komut dosyasının yürütüldüğü aynı dizinden) yüklenen bir güvenlik farkı görmüyorum. Neden bunun bir yolu olmadığını söylüyorsun?
Aaron H.

4
Çünkü betiğinizin yürüttüğü dizin betiğinize dahil etmek istediğiniz betiğin bulunduğu yer değildir. Komut dosyalarını yüklendikleri yere yüklemek istiyorsunuz ve çalışma zamanında nerede olduğunu söylemenin güvenilir bir yolu yok. Sabit bir konum kullanmamak da yanlış (yani bilgisayar korsanı tarafından sağlanan) komut dosyasını dahil etmenin ve çalıştırmanın iyi bir yoludur.
Steve Baker

6

Tek amacı sisteminizdeki çeşitli bileşenler için konumlar sağlamak olan bir setenv betiği oluşturmanızı öneririm.

Diğer tüm komut dosyaları bu komut dosyasını kaynak olarak ayarlayacaktır, böylece tüm konumlar setenv komut dosyasını kullanan tüm komut dosyalarında ortaktır.

Bu cronjobs çalıştırırken çok kullanışlıdır. Cron çalıştırırken minimal bir ortam elde edersiniz, ancak tüm cron komut dosyalarını ilk olarak setenv komut dosyasını içerirseniz, cronjobs'un yürütmesini istediğiniz ortamı kontrol edebilir ve senkronize edebilirsiniz.

Yaklaşık 2.000 kSLOC'luk bir projede sürekli entegrasyon için kullanılan yapı maymunumuzda böyle bir teknik kullandık.


3

Steve'in yanıtı kesinlikle doğru tekniktir ancak installpath değişkeninizin tüm bu bildirimlerin yapıldığı ayrı bir ortam betiğinde olması için yeniden düzenlenmesi gerekir.

Sonra tüm komut dosyaları bu komut dosyasını kaynak ve yükleme yolu değişikliği gerekir, sadece bir konumda değiştirmeniz gerekir. İşleri daha da geliştirir, geleceğe dayanıklıdır. Tanrım bu kelimeden nefret ediyorum! (-:

BTW Örneğinizde gösterilen şekilde kullanırken $ {installpath} kullanan değişkene gerçekten başvurmalısınız:

. ${installpath}/incl.sh

Diş telleri dışarıda bırakılırsa, bazı mermiler "installpath / incl.sh" değişkenini dener ve genişletir!


3

Shell Script Loader bunun için benim çözümüm.

Tek bir komut dosyasına başvurmak için birçok komut dosyasında birçok kez çağrılabilen, ancak komut dosyasını yalnızca bir kez yükleyecek, include () adında bir işlev sağlar. İşlev, tam yolları veya kısmi yolları kabul edebilir (komut dosyası bir arama yolunda aranır). Scriptleri koşulsuz olarak yükleyecek load () adında benzer bir işlev de sağlanmıştır.

Şunun için çalışır bash , ksh , pd Ksh ve zsh ile bunların her biri için optimize edilmiş komut; ve kül , çizgi , yadigarı sh , vb. gibi orijinal sh ile genel olarak uyumlu diğer kabuklar , kabuğun sağlayabildiği özelliklere bağlı olarak işlevlerini otomatik olarak optimize eden evrensel bir komut dosyası aracılığıyla.

[Örnek alınan]

start.sh

Bu isteğe bağlı bir başlangıç ​​betiğidir. Başlatma yöntemlerini buraya yerleştirmek sadece bir kolaylıktır ve bunun yerine ana komut dosyasına yerleştirilebilir. Komut dosyaları derlenecekse bu komut dosyasına da gerek yoktur.

#!/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

# ...

kül

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

çıktı:

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

En iyisi, buna dayalı betikler de mevcut derleyici ile tek bir betik oluşturmak için derlenebilir.

İşte onu kullanan bir proje: http://sourceforge.net/p/playshell/code/ci/master/tree/ . Komut dosyalarını derleyerek veya derlemeden taşınabilir bir şekilde çalışabilir. Tek bir komut dosyası oluşturmak için derleme de olabilir ve yükleme sırasında yardımcı olur.

Ayrıca, bir uygulama komut dosyasının nasıl çalıştığı hakkında kısa bir fikir sahibi olmak isteyen herhangi bir muhafazakar parti için daha basit bir prototip oluşturdum: https://sourceforge.net/p/loader/code/ci/base/tree/loader-include-prototype .bash . Küçüktür ve kodunun Bash 4.0 veya daha yeni bir sürümle çalıştırılması isteniyorsa herkes kodunu ana komut dosyasına ekleyebilir ve kullanmaz eval.


3
evalBağımlılıkları yüklemek için 100 satırdan fazla ed kodu içeren 12 kilobayt Bash betiği . Ah
l0b0

1
Tam evalolarak dibe yakın üç bloktan biri her zaman çalıştırılır. Bu yüzden gerekli olsun ya da olmasın, kesinlikle kullanıyor eval.
l0b0

3
Bu eval çağrısı güvenlidir ve Bash 4.0+ kullanıyorsanız kullanılmaz. Görüyorum ki, evalsaf kötülük olduğunu düşünen ve bunun yerine nasıl iyi bir şekilde kullanılacağını bilmeyen eski zaman senaryolarından birisiniz .
konsolebox

1
"Kullanılmayan" ile ne demek istediğini bilmiyorum, ama çalışıyor. Ve işimin bir parçası olarak birkaç yıl boyunca kabuk komut dosyası yazdıktan sonra, evet, bu her zamankinden daha ikna oldum eval.
l0b0

1
Dosyaları işaretlemenin iki taşınabilir, basit yolu anında akla gelir: Ya onlara inode numaralarından bahsederek ya da NUL ile ayrılmış yolları bir dosyaya koyarak.
l0b0

2

Kişisel olarak tüm kütüphaneleri bir libklasöre koyun ve importyüklemek için bir fonksiyon kullanın.

klasör yapısı

resim açıklamasını buraya girin

script.sh içindekiler

# 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
}

Bu içe aktarma işlevinin komut dosyanızın başında olması gerektiğini ve daha sonra aşağıdaki gibi kitaplıklarınızı kolayca içe aktarabileceğinizi unutmayın:

import "utils"
import "requirements"

Her kitaplığın üstüne tek bir satır ekleyin (yani utils.sh):

IMPORTED="$BASH_SOURCE"

Şimdi içeride işlevlerine erişebilir utils.shve requirements.shgelenscript.sh

YAPILACAKLAR: Tek bir shdosya oluşturmak için bir bağlayıcı yazın


Bu aynı zamanda betiği içinde bulunduğu dizin dışında yürütme problemini de çözüyor mu?
Aaron H.

@AaronH. Hayır. Büyük projelere bağımlılıkları dahil etmenin yapısal bir yoludur.
Xaqron

1

Source veya $ 0 kullanmak, komut dosyanızın gerçek yolunu vermez. Gerçek yolunu almak için komut dosyasının işlem kimliğini kullanabilirsiniz.

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

Bu komut dosyasını kullanıyorum ve her zaman bana iyi hizmet etti :)


1

Tüm başlangıç ​​komut dosyalarımı bir .bashrc.d dizinine koydum. Bu, /etc/profile.d vb. Yerlerde yaygın bir tekniktir.

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

Globbing kullanan çözümle ilgili sorun ...

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

... "çok uzun" bir dosya listeniz olabilir. Gibi bir yaklaşım ...

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

... çalışır ancak ortamı istendiği gibi değiştirmez.


1

Tabii ki, her biri için, ama bence aşağıdaki blok oldukça sağlam. Bu bir dizin bulmak için "en iyi" yolu ve başka bir bash betiği çağırmak için "en iyi" yolu içerir inanıyorum:

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

echo "The main script"

Bu, diğer komut dosyalarını dahil etmenin "en iyi" yolu olabilir. Bu, bir bash betiğinin nerede depolandığını söyleyen başka bir "en iyi" yanıtı temel alır


1

Bu güvenilir bir şekilde çalışmalıdır:

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

source_relative incl.sh

0

incl.sh ve main.sh dosyalarımızın bulunduğu klasörü bulmamız yeterlidir; main.sh dosyasını bununla değiştirin:

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"

Yanlış alıntı, gereksiz kullanım echove yanlış kullanımı sedbireyin gseçenek. -1.
l0b0

Her neyse, bu senaryo çalışması için: SCRIPT_DIR=$(echo "$0" | sed "s/${SCRIPT_NAME}//")ve sonrasource "${SCRIPT_DIR}incl.sh"
Krzysiek

0

Göre man hierkomut dosyası içerir için uygun bir yer/usr/local/lib/

/ Usr / local / lib

Yerel olarak yüklenmiş programlarla ilişkili dosyalar.

Şahsen ben /usr/local/lib/bash/includesdahil etmek için tercih ederim . Orada bash-yardımcı bu şekilde kütüphanelerini dahil olmak üzere 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 durum çıktısını içerir


-5

Ayrıca kullanabilirsiniz:

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

9
Komut dosyalarının bulunduğu dizinde olduğunuzu varsayarsınız. Başka bir yerde iseniz işe yaramaz.
Luc M
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.