programas - scripts bash ejemplos
Bash: ¿Cuál es la mejor manera de incluir otros scripts? (20)
1. más limpio
Exploré casi todas las sugerencias y aquí está la más ingeniosa que me funcionó:
script_root=$(dirname $(readlink -f $0))
Funciona incluso cuando el script está enlazado a un directorio $PATH
.
Véalo en acción aquí: https://github.com/pendashteh/hcagent/blob/master/bin/hcagent
2. El más fresco
# Copyright https://stackoverflow.com/a/13222994/257479
script_root=$(ls -l /proc/$$/fd | grep "255 ->" | sed -e ''s/^./+-> //'')
Esto es en realidad de otra respuesta en esta misma página, ¡pero también lo estoy agregando a mi respuesta!
2. El más confiable.
Alternativamente, en el raro caso de que no funcionen, aquí está el enfoque a prueba de balas:
# 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"))
Puede verlo en acción en la fuente taskrunner
: https://github.com/pendashteh/taskrunner/blob/master/bin/taskrunner
Espero que esto ayude a alguien por ahí :)
Además, déjelo como un comentario si uno no funcionó para usted y mencione su sistema operativo y emulador. ¡Gracias!
La forma en que normalmente incluirías un script es con "source"
p.ej:
main.sh:
#!/bin/bash
source incl.sh
echo "The main script"
incl.sh:
echo "The included script"
La salida de ejecutar "./main.sh" es:
The included script
The main script
... Ahora, si intenta ejecutar ese script de shell desde otra ubicación, no puede encontrar la inclusión a menos que esté en su ruta.
¿Cuál es una buena manera de asegurarse de que su script pueda encontrar el script de inclusión, especialmente si, por ejemplo, el script necesita ser portátil?
Creo que la mejor manera de hacer esto es usar el método de Chris Boran, PERO debes calcular MY_DIR de esta manera:
#!/bin/sh
MY_DIR=$(dirname $(readlink -f $0))
$MY_DIR/other_script.sh
Para citar las páginas man 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
Nunca he encontrado un caso de uso en el que MY_DIR
no esté correctamente calculado. Si accede a su script a través de un enlace simbólico en su $PATH
, funciona.
Debe especificar la ubicación de los otros scripts, no hay otra forma de evitarlos. Recomiendo una variable configurable en la parte superior de su script:
#!/bin/bash
installpath=/where/your/scripts/are
. $installpath/incl.sh
echo "The main script"
Alternativamente, puede insistir en que el usuario mantenga una variable de entorno que indique dónde se encuentra su programa principal, como PROG_HOME o algo así. Esto se puede proporcionar al usuario automáticamente al crear un script con esa información en /etc/profile.d/, que se obtendrá cada vez que un usuario inicie sesión.
El uso de source o $ 0 no le dará la ruta real de su script. Podría usar el ID de proceso del script para recuperar su ruta real
ls -l /proc/$$/fd |
grep "255 ->" |
sed -e ''s/^./+-> //''
Estoy usando este script y siempre me ha servido bien :)
Esto debería funcionar de manera confiable:
source_relative() {
local dir="${BASH_SOURCE%/*}"
[[ -z "$dir" ]] && dir="$PWD"
source "$dir/$1"
}
source_relative incl.sh
Esto funciona incluso si el script es de origen:
source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"
La respuesta de Steve es definitivamente la técnica correcta, pero se debe refaccionar para que su variable installpath esté en un script de entorno separado donde se realizan todas las declaraciones.
Luego, todos los scripts originan ese script y deberían instalar el cambio de ruta de acceso, solo necesita cambiarlo en una ubicación. Hace las cosas más, eh, a prueba de futuro. Dios odio esa palabra! (-:
Por cierto, deberías referirte a la variable usando $ {installpath} cuando la uses de la forma que se muestra en tu ejemplo:
. ${installpath}/incl.sh
Si las llaves se quedan fuera, algunos shells intentarán expandir la variable "installpath / incl.sh".
Le sugiero que cree un script setenv cuyo único propósito sea proporcionar ubicaciones para varios componentes en todo su sistema.
Todas las demás secuencias de comandos luego generarían esta secuencia de comandos de modo que todas las ubicaciones sean comunes en todas las secuencias de comandos utilizando la secuencia de comandos setenv.
Esto es muy útil cuando se ejecuta cronjobs. Obtiene un entorno mínimo cuando ejecuta cron, pero si hace que todos los scripts cron incluyan primero el script setenv, entonces podrá controlar y sincronizar el entorno en el que desea que se ejecuten los cronjobs.
Utilizamos una técnica de este tipo en nuestro mono de construcción que se usó para la integración continua en un proyecto de aproximadamente 2,000 kSLOC.
Ponga personalmente todas las bibliotecas en una carpeta lib
y use una función de import
para cargarlas.
estructura de carpetas
contenido de 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
}
Tenga en cuenta que esta función de importación debería estar al principio de su secuencia de comandos y luego puede importar fácilmente sus bibliotecas de esta manera:
import "utils"
import "requirements"
Agregue una sola línea en la parte superior de cada biblioteca (es decir, utils.sh):
IMPORTED="$BASH_SOURCE"
Ahora tiene acceso a las funciones dentro de utils.sh
y script.sh
desde script.sh
TODO: Escribir un enlazador para construir un solo archivo sh
Por supuesto, a cada uno lo suyo, pero creo que el bloque de abajo es bastante sólido. Creo que esto implica la "mejor" forma de encontrar un directorio y la "mejor" forma de llamar a otro script de bash:
scriptdir=`dirname "$BASH_SOURCE"`
source $scriptdir/incl.sh
echo "The main script"
Así que esta puede ser la "mejor" forma de incluir otros scripts. Esto se basa en otra "mejor" respuesta que le dice a un script de bash dónde está almacenado
Puse todos mis scripts de inicio en un directorio .bashrc.d. Esta es una técnica común en lugares como /etc/profile.d, etc.
while read file; do source "${file}"; done <<HERE
$(find ${HOME}/.bashrc.d -type f)
HERE
El problema con la solución usando globbing ...
for file in ${HOME}/.bashrc.d/*.sh; do source ${file};done
... es que puede tener una lista de archivos que es "demasiado larga". Un enfoque como ...
find ${HOME}/.bashrc.d -type f | while read file; do source ${file}; done
... corre pero no cambia el ambiente como se desea.
Sé que llego tarde a la fiesta, pero esto debería funcionar sin importar cómo inicie la secuencia de comandos y utilice los elementos integrados exclusivamente:
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "$DIR/incl.sh"
. "$DIR/main.sh"
.
(punto) el comando es un alias a la fuente, $PWD
es la ruta del Directorio de trabajo, BASH_SOURCE
es una variable de matriz cuyos miembros son los nombres de los archivos de origen, ${string%substring}
elimina la concordancia más corta de $ substring de la parte posterior de $ string
Si está en el mismo directorio, puede usar dirname $0
:
#!/bin/bash
source $(dirname $0)/incl.sh
echo "The main script"
También puedes usar:
PWD=$(pwd)
source "$PWD/inc.sh"
Tiendo a hacer que todos mis guiones sean relativos entre sí. De esa manera puedo usar dirname:
#!/bin/sh
my_dir="$(dirname "$0")"
"$my_dir/other_script.sh"
Una alternativa a:
scriptPath=$(dirname $0)
es:
scriptPath=${0%/*}
.. la ventaja es que no depende de dirname, que no es un comando incorporado (y no siempre está disponible en los emuladores)
solo necesitamos encontrar la carpeta donde se almacenan nuestros incl.sh y main.sh; simplemente cambia tu main.sh con esto:
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"
Shell Script Loader es mi solución para esto.
Proporciona una función llamada include () que puede llamarse muchas veces en muchos scripts para referirse a un solo script, pero solo cargará el script una vez. La función puede aceptar rutas completas o rutas parciales (el script se busca en una ruta de búsqueda). También se proporciona una función similar llamada load () que cargará los scripts incondicionalmente.
Funciona para bash , ksh , pd ksh y zsh con scripts optimizados para cada uno de ellos; y otros shells que son genéricamente compatibles con el sh original como ash , dash , heirloom sh , etc., a través de un script universal que optimiza automáticamente sus funciones dependiendo de las características que el shell pueda proporcionar.
[Ejemplo adelantado]
start.sh
Este es un script de inicio opcional. Colocar los métodos de inicio aquí es solo una conveniencia y puede colocarse en el script principal en su lugar. Este script tampoco es necesario para compilar los scripts.
#!/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
# ...
ceniza
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 ----''
salida:
---- b.sh ----
---- a.sh ----
---- main.sh ----
Lo mejor es que los scripts basados en él también se pueden compilar para formar un solo script con el compilador disponible.
Aquí hay un proyecto que lo usa: http://sourceforge.net/p/playshell/code/ci/master/tree/ . Se puede ejecutar de forma portátil con o sin compilar los scripts. La compilación para producir un solo script también puede suceder, y es útil durante la instalación.
También creé un prototipo más simple para cualquier parte conservadora que quiera tener una breve idea de cómo funciona un script de implementación: https://sourceforge.net/p/loader/code/ci/base/tree/loader-include-prototype.bash . Es pequeño y cualquiera puede incluir el código en su script principal si lo desea, si su código está diseñado para ejecutarse con Bash 4.0 o más reciente, y tampoco usa eval
.
Una combinación de las respuestas a esta pregunta proporciona la solución más robusta.
Trabajó para nosotros en scripts de grado de producción con un gran soporte de dependencias y estructura de directorios:
#!/bin/bash # Full path of the current script THIS=`readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0` # The directory where current script resides DIR=`dirname "${THIS}"` # ''Dot'' means ''source'', i.e. ''include'': . "$DIR/compile.sh"
El método soporta todos estos:
- Espacios en camino
- Enlaces (a través de
readlink
) -
${BASH_SOURCE[0]}
es más robusto que$0
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"