2009-04-27 20 views
15

Estoy escribiendo un script bash muy simple que ataca un directorio determinado, cifra el resultado de eso y luego divide el archivo resultante en varios archivos más pequeños ya que los medios de copia de seguridad no admiten archivos enormes.Variables como comandos en scripts bash

No tengo mucha experiencia con bash scripting. Creo que tengo problemas para citar mis variables correctamente para permitir espacios en los parámetros. El guión sigue:

#! /bin/bash 

# This script tars the given directory, encrypts it, and transfers 
# it to the given directory (likely a USB key). 

if [ $# -ne 2 ] 
then 
    echo "Usage: `basename $0` DIRECTORY BACKUP_DIRECTORY" 
    exit 1 
fi 

DIRECTORY=$1 
BACKUP_DIRECTORY=$2 
BACKUP_FILE="$BACKUP_DIRECTORY/`date +%Y-%m-%dT%H-%M-%S.backup`" 

TAR_CMD="tar cv $DIRECTORY" 
SPLIT_CMD="split -b 1024m - \"$BACKUP_FILE\"" 

ENCRYPT_CMD='openssl des3 -salt' 

echo "$TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD" 

$TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

say "Done backing up" 

ejecución de este comando falla con:

dividida: "foo/2009-04-27T14-32-04.backup" AA: No existe el fichero o directorio

Puedo solucionarlo quitando las comillas en $BACKUP_FILE donde establezco $SPLIT_CMD. Pero, si tengo un espacio en el nombre de mi directorio de respaldo, no funciona. Además, si copio y pego la salida del comando "echo" directamente en el terminal, funciona bien. Claramente, hay algo que no entiendo acerca de cómo Bash está escapando cosas.

+0

¿por qué se incrusta $ BACKUP_FILE en SPLIT_CMD cuando simplemente podría ponerlo tras $ SPLIT_CMD en t él tubería? –

+0

Bueno, podría hacer eso, pero en ese momento no tiene mucho sentido tener variables para contener mis comandos y también puedo expandirlo todo como en la respuesta de Juliano a continuación. – wxs

+0

http://mywiki.wooledge.org/BashFAQ/050 – tripleee

Respuesta

35

Simplemente no coloque comandos enteros en las variables. Te meterás en muchos problemas tratando de recuperar los argumentos citados.

también:

  1. Evitar el uso de todas las letras en mayúscula nombres de variables en los scripts. Una manera fácil de dispararte en el pie.
  2. No use comillas invertidas, use $ (...) en cambio, anida mejor.

#! /bin/bash 

if [ $# -ne 2 ] 
then 
    echo "Usage: $(basename $0) DIRECTORY BACKUP_DIRECTORY" 
    exit 1 
fi 

directory=$1 
backup_directory=$2 
current_date=$(date +%Y-%m-%dT%H-%M-%S) 
backup_file="${backup_directory}/${current_date}.backup" 

tar cv "$directory" | openssl des3 -salt | split -b 1024m - "$backup_file" 
+0

Sí, supongo que probablemente lo haga de esta manera. Me parece menos elegante que tener los comandos en variables, pero supongo que esa es la naturaleza de las secuencias de comandos bash. – wxs

+4

@wxs: no hay nada elegante en poner comandos en variables. No obtienes ningún tipo de flexibilidad; por el contrario, solo causas errores debido a la división de palabras. Lo que podrías haber intentado hacer es poner comandos en funciones. Usted ejecuta funciones. Nunca debe ejecutar contenido variable. nunca. – lhunath

+7

No entiendo 1. ¿me importa explicarlo? ¿Cómo/por qué es más fácil dispararse en el pie? – ata

5

No estoy seguro, pero podría valer la pena ejecutar una evaluación en los comandos primero.

Esto le permitirá fiesta de expandir las variables $ TAR_CMD y como en toda su amplitud (al igual que el comando echo hace a la consola, que se dice obras)

Bash entonces leerá la línea por segunda vez con el variables expandidas

eval $TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

Acabo de hacer una búsqueda en Google y esta página parece que podría hacer un trabajo decente al explicar por qué es necesaria. http://fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F

+0

Parece que también lo hace, pero creo que hace que todo sea un poco complicado. Oh, bueno, veo por qué las personas inventaron otros lenguajes de scripting. – wxs

+0

Este es un riesgo de seguridad importante. Vea BashFAQ # 50 para la alternativa de mejores prácticas: http://mywiki.wooledge.org/BashFAQ/050 - y BashFAQ # 48 para una descripción de por qué 'eval' conlleva riesgos: http://mywiki.wooledge.org/BashFAQ/048 –

+0

Buena captura @CharlesDuffy, si se usa en un sistema compartido donde a otros usuarios se les concede acceso a la secuencia de comandos para ejecutar con derechos elevados, es un riesgo. – Eddie

1

Citando espacios dentro de las variables de tal manera que la cáscara se volverá a interpretar correctamente las cosas es difícil . Es este tipo de cosas lo que me impulsa a buscar un lenguaje más fuerte. Si eso es perl o python o ruby ​​o lo que sea (elijo Perl, pero eso no siempre es para todos), es solo algo que le permitirá eludir el shell para cotizar.

No es que nunca he logrado hacerlo bien con dosis liberales de eval, pero solo eso eval me da los eebie-jeebies (se convierte en un nuevo dolor de cabeza cuando se quiere tomar la opinión del usuario y evaluarlo, aunque en este caso, tomarías cosas que escribiste y evaluarías eso en su lugar), y me dieron dolores de cabeza en la depuración.

con Perl, como mi ejemplo, me gustaría ser capaz de hacer algo como:

@tar_cmd = (qw(tar cv), $directory); 
@encrypt_cmd = (qw(openssl des3 -salt)); 
@split_cmd = (qw(split -b 1024m -), $backup_file); 

La parte difícil aquí es hacer las tuberías - pero un poco de IO::Pipe, tenedor, y la reapertura de stdout y stderr y no está mal Algunos dirían que es peor que citar el caparazón correctamente, y entiendo de dónde vienen, pero, para mí, es más fácil de leer, mantener y escribir. Diablos, alguien podría tomar el trabajo duro de esto y crear un módulo IO :: Pipeline y hacer que todo sea trivial ;-)

+0

Es solo un problema difícil si * lo * es un problema difícil usando 'eval'. No hay una buena razón para hacer eso. –

4

Hay un punto para poner solo comandos y opciones en las variables.

#! /bin/bash 

if [ $# -ne 2 ] 
then 
    echo "Usage: `basename $0` DIRECTORY BACKUP_DIRECTORY" 
    exit 1 
fi 

. standard_tools  

directory=$1 
backup_directory=$2 
current_date=$(date +%Y-%m-%dT%H-%M-%S) 
backup_file="${backup_directory}/${current_date}.backup" 

${tar_create} "${directory}" | ${openssl} | ${split_1024} "$backup_file" 

Puede reubicar a los comandos a otro archivo que la fuente, por lo que puede volver a utilizar los mismos comandos y opciones a través de muchos guiones. Esto es muy útil cuando tienes muchas secuencias de comandos y quieres controlar cómo usan todas las herramientas. Así standard_tools debería contener:

export tar_create="tar cv" 
export openssl="openssl des3 -salt" 
export split_1024="split -b 1024m -" 
+0

'tar_create' no se encuentra pero ayudó –

+1

Esto no resuelve el problema para argumentos complejos. Si su 'tar_create' era' tar cv --exclude = "* *" ', habría fallado de forma muy similar a la original. Y las 'exportaciones's no hacen nada útil aquí: estas variables se usan en el mismo shell, por lo que contaminar el espacio de entorno del subproceso es simplemente un desperdicio. –

5

eval no es una práctica aceptable si sus nombres de directorio pueden ser generados por fuentes no fiables. Ver BashFAQ #48 para más información sobre qué eval no debe utilizarse, y BashFAQ #50 para más información sobre la causa de este problema y sus soluciones adecuadas, algunas de las cuales se señala más adelante:

Si usted necesita para construir sus comandos a través del tiempo , el uso de matrices:

tar_cmd=(tar cv "$directory") 
split_cmd=(split -b 1024m - "$backup_file") 
encrypt_cmd=(openssl des3 -salt) 
"${tar_cmd[@]}" | "${encrypt_cmd[@]}" | "${split_cmd[@]}" 

Alternativamente, si esto es sólo acerca de la definición de sus comandos en un lugar central, las funciones de uso:

tar_cmd() { tar cv "$directory"; } 
split_cmd() { split -b 1024m - "$backup_file"; } 
encrypt_cmd() { openssl des3 -salt; } 
tar_cmd | split_cmd | encrypt_cmd 
Cuestiones relacionadas