web-dev-qa-db-fra.com

Bash: Comment inclure au mieux d'autres scripts?

La manière dont vous incluez normalement un script est avec "source"

par exemple:

main.sh:

#!/bin/bash

source incl.sh

echo "The main script"

incl.sh:

echo "The included script"

Le résultat de l'exécution de "./main.sh" est:

The included script
The main script

... Maintenant, si vous essayez d'exécuter ce script Shell depuis un autre emplacement, l'inclusion ne sera trouvée que si elle se trouve dans votre chemin.

Quel est un bon moyen de s'assurer que votre script peut trouver le script include, en particulier si, par exemple, le script doit être portable?

306
Aaron H.

J'ai tendance à faire en sorte que mes scripts soient tous relatifs les uns aux autres. De cette façon, je peux utiliser dirname:

#!/bin/sh

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

"$my_dir/other_script.sh"
201
Chris Boran

Je sais que je suis en retard à la fête, mais cela devrait fonctionner quelle que soit la manière dont vous commencez le script et utilisez exclusivement les commandes intégrées:

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

La commande . (point) est un alias vers la source, $PWD est le chemin du répertoire de travail, BASH_SOURCE est une variable de tableau dont les membres sont les noms de fichiers source, ${string%substring} enlève la correspondance la plus courte entre $ sous-chaîne et $ string

156
sacii

Une alternative à:

scriptPath=$(dirname $0)

est:

scriptPath=${0%/*}

.. l'avantage étant de ne pas dépendre de dirname, ce qui n'est pas une commande intégrée (et pas toujours disponible dans les émulateurs)

49
tardate

S'il se trouve dans le même répertoire, vous pouvez utiliser dirname $0:

#!/bin/bash

source $(dirname $0)/incl.sh

echo "The main script"
36
dsm

Je pense que la meilleure façon de faire est d'utiliser la méthode de Chris Boran, MAIS vous devriez calculer MY_DIR de cette façon:

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

Pour citer les pages de manuel de readlink:

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

Je n'ai jamais rencontré de cas d'utilisation dans lequel MY_DIR n'est pas correctement calculé. Si vous accédez à votre script via un lien symbolique dans votre $PATH, cela fonctionnera.

25
Mat131
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"
18
Max

Une combinaison des réponses à cette question fournit la solution la plus robuste.

Cela a fonctionné pour nous dans les scripts de production avec un support important des dépendances et de la structure de répertoires:

#!/bin/bash 

 # Chemin complet du script actuel 
 THIS = `readlink -f" $ {BASH_SOURCE [0]} "2>/dev/null || echo $ 0`. .
 # Le répertoire dans lequel se trouve le script actuel 
 DIR = `dirname" $ ​​{THIS} "` 

 # 'Point' signifie 'source', c'est-à-dire 'include': 
. " $ DIR/compile.sh "

La méthode prend en charge tous ces éléments:

  • Espaces dans le chemin
  • Liens (via readlink)
  • ${BASH_SOURCE[0]} est plus robuste que $0
18
Brian Haak

Cela fonctionne même si le script provient de:

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

Vous devez spécifier l'emplacement des autres scripts, il n'y a pas d'autre moyen de le contourner. Je recommanderais une variable configurable en haut de votre script:

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

. $installpath/incl.sh

echo "The main script"

Alternativement, vous pouvez insister pour que l'utilisateur maintienne une variable d'environnement indiquant où se trouve votre programme personnel, comme PROG_HOME ou quelque chose de ce genre. Cela peut être fourni automatiquement à l'utilisateur en créant un script avec ces informations dans /etc/profile.d/, qui seront générées à chaque fois qu'un utilisateur se connecte.

7
Steve Baker

Je vous suggère de créer un script setenv dont le seul but est de fournir des emplacements pour divers composants de votre système.

Tous les autres scripts utiliseraient ensuite ce script pour que tous les emplacements soient communs à tous les scripts utilisant le script setenv.

Ceci est très utile lorsque vous exécutez des tâches cron. Vous obtenez un environnement minimal lorsque vous exécutez cron, mais si tous les scripts cron incluent d’abord le script setenv, vous pouvez alors contrôler et synchroniser l’environnement dans lequel vous souhaitez que les tâches cron soient exécutées.

Nous avons utilisé une telle technique sur notre build singe, qui a été utilisée pour l'intégration continue sur un projet d'environ 2 000 kSLOC.

5
Rob Wells

La réponse de Steve est certainement la technique correcte, mais il convient de la refactoriser de sorte que votre variable installpath se trouve dans un script d’environnement distinct dans lequel toutes ces déclarations sont faites.

Ensuite, tous les scripts source ce script et devrait installpath changer, vous devez le changer dans un seul emplacement. Rend les choses plus, euh, futures. Dieu que je déteste cette parole! (-:

BTW Vous devriez vraiment faire référence à la variable en utilisant $ {installpath} lorsque vous l’utilisez de la manière montrée dans votre exemple:

. ${installpath}/incl.sh

Si les accolades sont laissées de côté, certains shells vont essayer de développer la variable "installpath/incl.sh"!

3
Rob Wells

Shell Script Loader est ma solution pour cela.

Il fournit une fonction nommée include () qui peut être appelée plusieurs fois dans de nombreux scripts pour désigner un seul script, mais ne le charge qu'une fois. La fonction peut accepter des chemins complets ou des chemins partiels (le script est recherché dans un chemin de recherche). Une fonction similaire nommée load () est également fournie et chargera les scripts de manière inconditionnelle.

Cela fonctionne pour bash, ksh, pd ksh et zsh avec des scripts optimisés pour chacun d’entre eux; et d'autres shells génériquement compatibles avec le sh d'origine, tels que ash, dash, heirloom sh, etc., via un script universel qui optimise automatiquement ses fonctions en fonction des fonctionnalités que Shell peut fournir.

[Exemple simplifié]

start.sh

Ceci est un script de démarrage facultatif. Placer les méthodes de démarrage ici est simplement une commodité et peut être placé dans le script principal à la place. Ce script n'est également pas nécessaire si les scripts doivent être compilés.

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

# ...

cendre

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

sortie:

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

Le mieux est que les scripts qui en dépendent puissent également être compilés pour former un seul script avec le compilateur disponible.

Voici un projet qui l'utilise: http://sourceforge.net/p/playshell/code/ci/master/tree/ . Il peut fonctionner de manière portable avec ou sans compilation des scripts. La compilation pour produire un seul script peut également se produire et est utile lors de l'installation.

J'ai également créé un prototype plus simple pour tout parti conservateur souhaitant avoir une idée du fonctionnement d'un script d'implémentation: https://sourceforge.net/p/loader/code/ci/base/tree/loader-include -prototype.bash . C'est petit et n'importe qui peut simplement inclure le code dans son script principal s'il le souhaite si son code est destiné à être exécuté avec Bash 4.0 ou plus récent, et n'utilise pas non plus eval.

1
konsolebox

Utiliser source ou $ 0 ne vous donnera pas le vrai chemin de votre script. Vous pouvez utiliser l'ID de processus du script pour récupérer son chemin réel.

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

J'utilise ce script et il m'a toujours bien servi :)

1
francoisrv

Je mets tous mes scripts de démarrage dans un répertoire .bashrc.d ..__ Ceci est une technique courante dans des endroits tels que /etc/profile.d, etc.

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

Le problème avec la solution en utilisant globbing ...

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

... Est-ce que vous pourriez avoir une liste de fichiers qui est "trop ​​longue" . Une approche comme ... 

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

... fonctionne mais ne modifie pas l'environnement comme vous le souhaitez.

1
phreed

Bien sûr, à chacun son tour, mais je pense que le bloc ci-dessous est assez solide. Je pense que cela implique le "meilleur" moyen de trouver un répertoire et le "meilleur" moyen d'appeler un autre script bash:

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

echo "The main script"

Donc, cela peut être le "meilleur" moyen d'inclure d'autres scripts. Ceci est basé sur une autre "meilleure" réponse que indique à un script bash où il est stocké

1
modulitos

Mettez personnellement toutes les bibliothèques dans un dossier lib et utilisez une fonction import pour les charger.

structure de dossier

 enter image description here

script.sh contenu

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

Notez que cette fonction d'importation doit être au début de votre script et vous pouvez ensuite importer facilement vos bibliothèques de la manière suivante:

import "utils"
import "requirements"

Ajoutez une seule ligne en haut de chaque bibliothèque (par exemple, utils.sh):

IMPORTED="$BASH_SOURCE"

Vous avez maintenant accès aux fonctions dans utils.sh et requirements.sh à partir de script.sh

TODO: Écrivez un éditeur de liens pour créer un seul fichier sh

1
Xaqron

Cela devrait fonctionner de manière fiable:

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

source_relative incl.sh
1
PSkocik

nous devons juste trouver le dossier où nos incl.sh et main.sh sont stockés; changez simplement votre main.sh avec ceci:

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"
0
fastrizwaan

Selon man hier l'emplacement approprié pour l'inclusion de script est _/usr/local/lib/_

/ usr/local/lib

Fichiers associés aux programmes installés localement.

Personnellement, je préfère _/usr/local/lib/bash/includes_ pour includes. Il y a bash-helper lib pour inclure les bibliothèques de cette façon:

_#!/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 includes status output

0