Usar el archivo de configuración para mi script de shell

Necesito crear un archivo de configuración para mi propio script:
Aquí hay un ejemplo:

script:

#!/bin/bash source /home/myuser/test/config echo "Name=$nam" >&2 echo "Surname=$sur" >&2 

Contenido de /home/myuser/test/config:

nam="Mark" sur="Brown" 

¡Eso funciona!

Mi pregunta: ¿es esta la forma correcta de ¿Hacer esto o hay otras formas?

Comentarios

  • Las variables deben estar en la parte superior. I ‘ m sorprendido de que funcione. De todos modos, ¿por qué necesita un archivo de configuración? ¿Está planeando usar estas variables en otro lugar?
  • Faheem, necesito las variables porque mi script tiene muchas opciones: usar un config se amplificará la secuencia de comandos. Gracias
  • IMHO, está bien. Lo haría de esta manera.
  • abcde también lo hace de esta manera y ese es un programa bastante grande (para un script de shell). Puedes echarle un vistazo aquí .

Answer

source no es seguro ya que ejecutará código arbitrario. Esto puede no ser una preocupación para usted, pero si los permisos de archivo son incorrectos, es posible que un atacante con acceso al sistema de archivos ejecute código como un usuario con privilegios inyectando código en un archivo de configuración cargado por un script seguro como un init script.

Hasta ahora, la mejor solución que he podido identificar es la torpe solución reinventando la rueda:

myscript.conf

password=bar echo rm -rf / PROMPT_COMMAND="echo "Sending your last command $(history 1) to my email"" hostname=localhost; echo rm -rf / 

Usando source, esto ejecutaría echo rm -rf / dos veces, además de cambiar el usuario en ejecución «s $PROMPT_COMMAND. En su lugar, haga esto:

myscript.sh (Bash 4)

#!/bin/bash typeset -A config # init array config=( # set default values in config array [username]="root" [password]="" [hostname]="localhost" ) while read line do if echo $line | grep -F = &>/dev/null then varname=$(echo "$line" | cut -d "=" -f 1) config[$varname]=$(echo "$line" | cut -d "=" -f 2-) fi done < myscript.conf echo ${config[username]} # should be loaded from defaults echo ${config[password]} # should be loaded from config file echo ${config[hostname]} # includes the "injected" code, but it"s fine here echo ${config[PROMPT_COMMAND]} # also respects variables that you may not have # been looking for, but they"re sandboxed inside the $config array 

myscript.sh (compatible con Mac / Bash 3)

#!/bin/bash config() { val=$(grep -E "^$1=" myscript.conf 2>/dev/null || echo "$1=__DEFAULT__" | head -n 1 | cut -d "=" -f 2-) if [[ $val == __DEFAULT__ ]] then case $1 in username) echo -n "root" ;; password) echo -n "" ;; hostname) echo -n "localhost" ;; esac else echo -n $val fi } echo $(config username) # should be loaded from defaults echo $(config password) # should be loaded from config file echo $(config hostname) # includes the "injected" code, but it"s fine here echo $(config PROMPT_COMMAND) # also respects variables that you may not have # been looking for, but they"re sandboxed inside the $config array 

Responda si encuentra un exploit de seguridad en mi código.

Comentarios

  • Para su información, esta es una solución de Bash versión 4.0 que, lamentablemente, está sujeta a locos problemas de licencia impuestos por Apple y no está disponible de forma predeterminada en Mac
  • @Sukima Buen punto. ‘ he agregado una versión que es compatible con Bash 3. Su debilidad es que no manejará * en las entradas correctamente, pero luego ¿Qué en Bash maneja bien ese carácter?
  • El primer script falla si la contraseña contiene una barra invertida.
  • @Kusalananda ¿Qué pasa si la barra invertida se escapa? my\\password
  • Esta versión tarda varios segundos en procesar mi archivo de configuración, encontré que la solución de gw0 es mucho más rápida.

Respuesta

Analice el archivo de configuración, no lo ejecute.

Actualmente estoy escribiendo una aplicación en el trabajo que utiliza una configuración XML extremadamente simple:

<config> <username>username-or-email</username> <password>the-password</password> </config> 

En el script de shell (la «aplicación»), esto es lo que hago para obtener el nombre de usuario (más o menos, lo he puesto en una función de shell):

username="$( xml sel -t -v "/config/username" "$config_file" )" 

El comando xml es XMLStarlet , que está disponible para la mayoría de Unices.

Estoy usando XML ya que otras partes de la aplicación también tratan con datos codificados en archivos XML, así que fue más fácil.

Si prefiere JSON, «s jq que es un analizador JSON de shell fácil de usar.

Mi archivo de configuración se vería así en JS ACTIVADO:

{ "username": "username-or-email", "password": "the-password" } 

Y luego «obtendría el nombre de usuario en el script:

username="$( jq -r ".username" "$config_file" )" 

Comentarios

  • La ejecución del script tiene una serie de ventajas y desventajas. Las principales desventajas son la seguridad, si alguien puede alterar el archivo de configuración, entonces puede ejecutar código, y es más difícil hacerlo a prueba de idiotas. Las ventajas son la velocidad, en una prueba simple es más de 10,000 veces más rápido obtener un archivo de configuración que ejecutar pq, y flexibilidad, cualquiera a quien le guste el parche de mono en Python lo apreciará.
  • @icarus Qué tan grande archivos de configuración que encuentra habitualmente y con qué frecuencia necesita analizarlos en una sesión? Tenga en cuenta también que se pueden obtener varios valores de XML o JSON de una vez.
  • Por lo general, solo unos pocos (1 a 3) valores. Si está utilizando eval para establecer varios valores, entonces está ejecutando partes seleccionadas del archivo de configuración :-).
  • @icarus Estaba pensando en matrices … No es necesario eval nada. El impacto en el rendimiento de usar un formato estándar con un analizador existente (aunque ‘ es una utilidad externa) es insignificante en comparación con la solidez, la cantidad de código y la facilidad de uso. y mantenibilidad.
  • +1 para » analizar el archivo de configuración, no ‘ t ejecutarlo »

Respuesta

Aquí hay una versión limpia y portátil que es compatible con Bash 3 y up, tanto en Mac como en Linux.

Especifica todos los valores predeterminados en un archivo separado, para evitar la necesidad de una función de configuración de «valores predeterminados» enorme, desordenada y duplicada en todos sus scripts de shell. Y te permite elegir entre leer con o sin alternativas predeterminadas:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (esta es una biblioteca, entonces no hay shebang-line):

config_read_file() { (grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d "=" -f 2-; } config_get() { val="$(config_read_file config.cfg "${1}")"; if [ "${val}" = "__UNDEFINED__" ]; then val="$(config_read_file config.cfg.defaults "${1}")"; fi printf -- "%s" "${val}"; } 

test.sh (o cualquier script donde desee leer valores de configuración) :

#!/usr/bin/env bash source config.shlib; # load the config library functions echo "$(config_get myvar)"; # will be found in user-cfg printf -- "%s\n" "$(config_get myvar)"; # safer way of echoing! myvar="$(config_get myvar)"; # how to just read a value without echoing echo "$(config_get othervar)"; # will fall back to defaults echo "$(config_get bleh)"; # "__UNDEFINED__" since it isn"t set anywhere 

Explicación del script de prueba:

  • Tenga en cuenta que todos usos de config_get en test.sh están entre comillas dobles. Al envolver cada config_get entre comillas dobles, nos aseguramos de que el texto del valor de la variable nunca sea malinterpretado como banderas. Y asegura que preservamos los espacios en blanco correctamente, como múltiples espacios en una fila en el valor de configuración.
  • ¿Y qué es esa línea printf? Bueno, «Es algo que debe tener en cuenta: echo es un comando incorrecto para imprimir texto sobre el que no tiene control. Incluso si usa comillas dobles, interpretará banderas. Intente configurar myvar (en config.cfg) en -e y verá una línea vacía, porque echo pensará que «es una bandera. Pero printf no tiene ese problema. El printf -- dice «imprime esto y no» interpretes nada como banderas «, y el "%s\n" dice» formatea la salida como una cadena con una nueva línea al final, y por último, el parámetro final es el valor para que printf formatee.
  • Si no va a hacer eco de valores en la pantalla, entonces simplemente los asignaría normalmente, como myvar="$(config_get myvar)";. Si va a imprimirlos en la pantalla, sugiero usar printf para estar totalmente seguro contra cualquier cadena incompatible con el eco que pueda estar en la configuración del usuario. Pero echo está bien si la variable proporcionada por el usuario no está el primer carácter de la cadena que está repitiendo, ya que esa es la única situación en la que se pueden interpretar las «banderas», por lo que algo como echo "foo: $(config_get myvar)"; es seguro, ya que » foo «no» comienza con un guión y, por lo tanto, le dice a echo que el resto de la cadena tampoco está indicada. 🙂

Comentarios

  • @ user2993656 Gracias por notar que mi código original todavía tenía mi nombre de archivo de configuración privado (environment.cfg) en lugar del correcto. En cuanto a » echo -n » edición que hiciste, eso depende del shell usado. En Mac / Linux Bash, » echo -n » significa » eco sin salto de línea final «, lo cual hice para evitar el seguimiento de nuevas líneas. Pero parece funcionar exactamente igual sin él, así que gracias por las ediciones.
  • En realidad, simplemente lo revisé y lo reescribí para usar printf en lugar de echo, lo que asegura que ‘ eliminará el riesgo de que el eco malinterprete » flags » en los valores de configuración.
  • Me gusta mucho esta versión. Dejé caer las config.cfg.defaults en lugar de definirlas en el momento de llamar a $(config_get var_name "default_value"). tritarget.org/static/…
  • Asimismo, esto es genial.

Respuesta

La forma más común, eficiente y correcta es usar source o . como forma abreviada. Por ejemplo:

source /home/myuser/test/config 

o

. /home/myuser/test/config 

Sin embargo, algo a considerar es el problemas de seguridad que pueden plantear el uso de un archivo de configuración de origen externo adicional, dado que se puede insertar código adicional. Para obtener más información, incluso sobre cómo detectar y resolver este problema, recomendaría echar un vistazo a la sección «Asegúrelo» de http://wiki.bash-hackers.org/howto/conffile#secure_it

Comentarios

  • Tenía grandes esperanzas en ese artículo (también apareció en mis resultados de búsqueda), pero el autor ‘ s sugerencia de intentar usar regex para filtrar código malicioso es un ejercicio inútil.
  • El procedimiento con punto, ¿requiere una ruta absoluta?Con el relativo no ‘ no funciona

Responder

Yo uso esto en mis scripts:

sed_escape() { sed -e "s/[]\/$*.^[]/\\&/g" } cfg_write() { # path, key, value cfg_delete "$1" "$2" echo "$2=$3" >> "$1" } cfg_read() { # path, key -> value test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" | sed "s/^$(echo "$2" | sed_escape)=//" | tail -1 } cfg_delete() { # path, key test -f "$1" && sed -i "/^$(echo $2 | sed_escape).*$/d" "$1" } cfg_haskey() { # path, key test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" > /dev/null } 

Debería admitir todas las combinaciones de caracteres, excepto que las teclas «no pueden tener = en ellos, ya que ese es el separador. Cualquier otra cosa funciona.

% cfg_write test.conf mykey myvalue % cfg_read test.conf mykey myvalue % cfg_delete test.conf mykey % cfg_haskey test.conf mykey || echo "It"s not here anymore" It"s not here anymore 

Además, esto es completamente seguro ya que no «usa source o eval.

Comentarios

  • No es ‘ t esto supuesto para crear primero el archivo de configuración si no ‘ t existe? No ‘ t, pero touch -a "${path}" por supuesto se asegurará de que exista, también sin actualizar frívolamente su mtime.

Respuesta

Esto es sucinto y seguro:

# Read common vars from common.vars # the incantation here ensures (by env) that only key=value pairs are present # then declare-ing the result puts those vars in our environment declare $(env -i `cat common.vars`) 

El -i garantiza que solo obtenga las variables de common.vars

Actualización: Una ilustración de la seguridad es que

env -i "touch evil1 foo=omg boo=$(touch evil2)" 

No producirá ningún archivo tocado. Probado en mac con bash, es decir, usando bsd env.

Comentarios

  • Solo mira cómo se crean los archivos evil1 y evil2 si pones esto en comm on.vars « `touch evil1 foo = omg boo = $ (touch evil2)` «
  • @pihentagy Para mí, lo siguiente no produce archivos tocados env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Se ejecuta en mac.
  • de hecho, pero no puede acceder a foo. ‘ he intentado env -i ... myscript.sh y dentro de ese script foo no está definido. Sin embargo, si elimina » basura «, funcionará. Así que gracias por explicarme. : +1:

Respuesta

La mayoría de los usuarios, aunque no muchos contenedores, ya tienen el git binario. Entonces, ¿por qué no utilizar git config para la gestión de la configuración de la aplicación utilizando un archivo de configuración dedicado no conflictivo como en los ejemplos siguientes?

# Set $ git config -f ~/.myapp core.mykey myval # Get $ git config -f ~/.myapp core.mykey myval # Get invalid $ git config -f ~/.myapp core.mykey $ echo $? 1 # List git config -f ~/.myapp -l core.mykey=myval # View $ cat ~/.myapp [core] mykey = myval 

Para obtener más comandos, consulte su man página. Es aconsejable asegurarse primero de que exista el archivo de configuración:

touch -a ~/.myapp 

Respuesta

Este parece seguro y corto. Siéntase libre de romper esto sin piedad. Me gustaría conocer una forma mejor.

TL; DR;

while read LINE; do declare "$LINE"; done < evil.conf 

Estoy usando bash 4.3.48.

También es compatible con bash --posix. Ver la parte inferior para la prueba.

Pero sh no lo admite debido a declare.


Prueba básica para aquellos que quieren una prueba

Cree el archivo evil.conf

 echo > evil.conf " A=1 B=2 C=$(echo hello) # Could produce side-effect D=`touch evil` C=$((1+2)) E=$(ping 8.8.8.8 -n 3) echo hello # Could produce visible side-effect touch evil2 ping 8.8.8.8 -n 3 F=ok"  

Cargue la configuración con el fragmento

 while read LINE; do declare "$LINE"; done < evil.conf  

Salida (ver desinfectante en acción)

bash: declare: `": not a valid identifier bash: declare: `": not a valid identifier bash: declare: `# Could produce side-effect": not a valid identifier bash: declare: `echo hello": not a valid identifier bash: declare: `": not a valid identifier bash: declare: `# Could produce visible side-effect": not a valid identifier bash: declare: `touch evil2": not a valid identifier bash: declare: `ping 8.8.8.8 -n 3": not a valid identifier 

Comprobemos los valores ahora

 for V in A B C D E F; do declare -p $V; done  
declare -- A="1" declare -- B="2" declare -- C="\$((1+2))" declare -- D="\`touch evil\`" declare -- E="\$(ping 8.8.8.8 -n 3)" declare -- F="ok" 

Compruebe los efectos secundarios (sin efectos secundarios):

 ls evil evil2  
ls: cannot access "evil": No such file or directory ls: cannot access "evil2": No such file or directory 

Apéndice. Prueba bash --posix

bash -c "while read LINE; do declare "$LINE"; done < evil.conf; for V in A B C D E F; do declare -p $V; done" --posix 

Respuesta

Para mi escenario, source o . w tan bien, pero quería admitir las variables de entorno local (es decir, FOO=bar myscript.sh) teniendo prioridad sobre las variables configuradas. También quería que el archivo de configuración fuera editable por el usuario y cómodo para alguien acostumbrado a los archivos de configuración de origen, y que lo mantuviera lo más pequeño / simple posible, para no distraer la atención de la idea principal de mi muy pequeño script.

Esto es lo que se me ocurrió:

CONF=${XDG_CONFIG_HOME:-~/config}/myscript.sh if [ ! -f $CONF ]; then cat > $CONF << CONF VAR1="default value" CONF fi . <(sed "s/^\([^=]\+\) *= *\(.*\)$/\1=${\1:-\2}/" < $CONF) 

Esencialmente, comprueba las definiciones de variables (sin ser muy flexible con los espacios en blanco) y reescribe esas líneas para que el El valor se convierte a un valor predeterminado para esa variable, y la variable no se modifica si se encuentra, como la variable XDG_CONFIG_HOME anterior. Obtiene esta versión alterada del archivo de configuración y continúa.

El trabajo futuro podría hacer que el script sed sea más robusto, filtrar las líneas que parecen raras o no » t definiciones, etc., no se rompen en los comentarios al final de la línea, pero esto es lo suficientemente bueno para mí por ahora.

Respuesta

Puede hacerlo:

#!/bin/bash name="mohsen" age=35 cat > /home/myuser/test/config << EOF Name=$name Age=$age EOF 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *