Variables como comandos en scripts bash

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.

No tengo mucha experiencia con bash scripting. Creo que tengo problemas para citar mis variables correctamente para permitir espacios en los parámetros. El guion 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" 

La ejecución de este comando falla con:

split: “foo / 2009-04-27T14-32-04.backup” aa: No existe tal archivo o directorio

Puedo solucionarlo quitando las comillas alrededor de $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.

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

También:

  1. Evitar el uso de nombres de variables con todos los capitales en las secuencias de comandos. Una manera fácil de dispararte en el pie.
  2. No use comillas invertidas, use $ (…) en su lugar, 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" 

eval no es una práctica aceptable si sus nombres de directorio pueden ser generados por fonts que no son de confianza. Consulte BashFAQ # 48 para obtener más información sobre por qué eval no se debe usar, y BashFAQ # 50 para obtener más información sobre la causa raíz de este problema y sus soluciones adecuadas, algunas de las cuales se abordan a continuación:

Si necesita construir sus comandos a lo largo del tiempo, use 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 solo se trata de definir sus comandos en un lugar central, use funciones:

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

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

Esto permitirá que bash expanda las variables $ TAR_CMD y todo su ancho (tal como lo hace el comando echo para la consola, que dices que funciona)

Bash leerá la línea una segunda vez con las 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

Hay un punto para poner solo comandos y opciones en 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 los comandos en otro archivo que haya creado, de modo que pueda reutilizar los mismos comandos y opciones en muchos scripts. Esto es muy útil cuando tienes muchas secuencias de comandos y quieres controlar cómo usan todas las herramientas. Así que standard_tools contendría:

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

Citar espacios dentro de variables tales que el caparazón volverá a interpretar las cosas correctamente 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á omitir el shell para cotizar.

No es que nunca haya 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ía cosas que usted escribió y evalúe eso en su lugar), y me dieron dolores de cabeza en la depuración.

Con Perl, como mi ejemplo, podría 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 volver a abrir 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 😉