web-dev-qa-db-fra.com

Variables en tant que commandes dans les scripts bash

J'écris un script bash très simple qui tarne un répertoire donné, crypte la sortie de celui-ci, puis divise le fichier résultant en plusieurs fichiers plus petits car le support de sauvegarde ne prend pas en charge les fichiers volumineux.

Je n'ai pas beaucoup d'expérience avec les scripts bash. Je crois que j'ai des problèmes avec la citation correcte de mes variables pour autoriser les espaces dans les paramètres. Le script suit:

#! /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"

L'exécution de cette commande échoue avec:

split: "foo/2009-04-27T14-32-04.backup" aa: Aucun fichier ou répertoire de ce type

Je peux le corriger en supprimant les guillemets autour de $BACKUP_FILE où je mets $SPLIT_CMD. Mais, si j'ai un espace au nom de mon répertoire de sauvegarde, cela ne fonctionne pas. De plus, si je copie et colle la sortie de la commande "echo" directement dans le terminal, cela fonctionne très bien. Il est clair que je ne comprends pas comment Bash échappe aux choses.

21
wxs

Ne mettez tout simplement pas des commandes entières dans des variables. Vous aurez beaucoup de mal à essayer de récupérer les arguments cités.

Aussi:

  1. Évitez d'utiliser des noms de variables en majuscules dans les scripts. Un moyen facile de vous tirer sur le pied.
  2. N'utilisez pas de guillemets, utilisez plutôt $ (...), il s'emboîtera mieux.

#! /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"
44
Juliano

Je ne suis pas sûr, mais cela pourrait valoir la peine d'exécuter un eval sur les commandes en premier.

Cela permettra à bash d'étendre les variables $ TAR_CMD et autres à leur pleine largeur (tout comme la commande echo le fait pour la console, ce qui, selon vous, fonctionne)

Bash lira alors la ligne une deuxième fois avec les variables développées.

eval $TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

Je viens de faire une recherche Google et cette page semble pouvoir expliquer convenablement pourquoi cela est nécessaire. http://fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F

6
Eddie

eval n'est pas une pratique acceptable si les noms de votre répertoire peuvent être générés par des sources non fiables. Voir BashFAQ # 48 pour en savoir plus sur les raisons pour lesquelles eval ne doit pas être utilisé, et BashFAQ # 5 pour en savoir plus sur la cause première de ce problème et ses solutions appropriées , dont certains sont abordés ci-dessous:

Si vous avez besoin de construire vos commandes au fil du temps, utilisez des tableaux:

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

Alternativement, s'il s'agit simplement de définir vos commandes en un seul endroit central, utilisez les fonctions:

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

Il est utile de ne mettre que des commandes et des options dans des 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"

Vous pouvez déplacer les commandes vers un autre fichier que vous sourcez, afin de pouvoir réutiliser les mêmes commandes et options dans de nombreux scripts. C'est très pratique lorsque vous avez beaucoup de scripts et que vous souhaitez contrôler la façon dont ils utilisent tous les outils. Donc standard_tools contiendrait:

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

Il est difficile de citer des espaces à l'intérieur de variables de sorte que le shell réinterprète correctement les choses (== --- ==) . C'est ce genre de chose qui m'incite à chercher une langue plus forte. Que ce soit Perl ou python ou Ruby ou autre chose (je choisis Perl, mais ce n'est pas toujours pour tout le monde), c'est juste quelque chose qui vous permettra de contourner le Shell pour la citation.

Ce n'est pas que je n'ai jamais réussi à bien faire les choses avec des doses libérales d'eval, mais juste que l'eval me donne les eebie-jeebies (devient un tout nouveau casse-tête lorsque vous voulez prendre les commentaires des utilisateurs et les évaluer, bien que dans ce cas, vous je prendrais des choses que vous avez écrites et évalueriez à la place), et que j'ai eu des maux de tête lors du débogage.

Avec Perl, comme mon exemple, je serais capable de faire quelque chose comme:

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

La partie difficile ici est de faire les tuyaux - mais un peu de IO :: Pipe , fork, et de rouvrir stdout et stderr, et ce n'est pas mal. Certains diraient que c'est pire que de citer correctement le Shell, et je comprends d'où ils viennent, mais, pour moi, c'est plus facile à lire, à maintenir et à écrire. Heck, quelqu'un pourrait prendre le dur travail de cela et créer un module IO :: Pipeline et rendre le tout trivial ;-)

1
Tanktalus