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?
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"
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
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)
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"
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.
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"
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:
readlink
)${BASH_SOURCE[0]}
est plus robuste que $0
Cela fonctionne même si le script provient de:
source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"
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.
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.
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"!
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
.
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 :)
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.
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é
Mettez personnellement toutes les bibliothèques dans un dossier lib
et utilisez une fonction import
pour les charger.
structure de dossier
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
Cela devrait fonctionner de manière fiable:
source_relative() {
local dir="${BASH_SOURCE%/*}"
[[ -z "$dir" ]] && dir="$PWD"
source "$dir/$1"
}
source_relative incl.sh
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"
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
_