2009-04-27 14 views
15

Sto scrivendo uno script bash molto semplice che filtra una determinata directory, ne codifica l'output e quindi divide il file risultante in più file più piccoli poiché i supporti di backup non supportano file enormi.Variabili come comandi negli script di bash

Non ho molta esperienza con bash scripting. Credo di avere problemi nel quotare correttamente le mie variabili per consentire spazi nei parametri. Lo script segue:

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

esecuzione di questo comando non riesce con:

divisa: "foo/2009-04-27T14-32-04.backup" AA: No such file or directory

Posso risolvere il problema rimuovendo le virgolette intorno a $BACKUP_FILE dove ho impostato $SPLIT_CMD. Ma, se ho uno spazio nel nome della mia directory di backup, non funziona. Inoltre, se copio e incollo l'output dal comando "echo" direttamente nel terminale funziona correttamente. Chiaramente c'è qualcosa che non capisco su come Bash stia sfuggendo alle cose.

+0

Perché si incorporare $ BACKUP_FILE in SPLIT_CMD quando si poteva appena messo dopo $ SPLIT_CMD in t lui oleodotto? –

+0

Beh, potrei farlo, ma a quel punto non c'è davvero molto senso nell'avere variabili per contenere i miei comandi e potrei anche espanderlo come nella risposta di Juliano in basso. – wxs

+0

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

risposta

35

Semplicemente non inserire interi comandi nelle variabili. Avrai molti problemi nel tentativo di recuperare gli argomenti citati.

anche:

  1. Evitare di utilizzare tutte le capitali-nomi delle variabili negli script. Un modo semplice per spararti sul piede.
  2. Non utilizzare backquote, utilizzare $ (...) invece, nidifica meglio.

#! /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ì, credo che probabilmente lo farò in questo modo. Sembra meno elegante che avere i comandi nelle variabili, ma credo che sia la natura dello scripting di bash. – wxs

+4

@wxs: Non c'è niente di elegante nel mettere i comandi nelle variabili. Non ottieni alcun tipo di flessibilità; al contrario, causi solo bug dovuti alla divisione delle parole. Quello che potresti aver intenzione di fare è mettere i comandi nelle funzioni. Esegui le funzioni. Non dovresti mai eseguire contenuti variabili. mai. – lhunath

+7

Non capisco 1. cura di spiegare? Come/perché rende più facile spararsi nel piede? – ata

5

Non sono sicuro, ma potrebbe valere la pena eseguire una valutazione sui comandi prima.

Questo vi permetterà di bash espande le variabili $ TAR_CMD e tale da loro piena ampiezza (proprio come il comando echo fa alla console, che si dice opere)

Bash poi leggere la riga una seconda volta con il variabili espanse.

eval $TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

Ho appena fatto una ricerca su Google e la pagina sembra che potrebbe fare un lavoro decente a spiegare perché ciò che è necessario. http://fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F

+0

Anche questo sembra farlo, ma sento che rende tutto un po 'troppo complicato. Oh bene, vedo perché la gente ha inventato altri linguaggi di scripting. – wxs

+0

Questo è un grande rischio per la sicurezza. Vedi BashFAQ # 50 per l'alternativa best-practice: http://mywiki.wooledge.org/BashFAQ/050 - e BashFAQ # 48 per una descrizione del perché "eval" comporta dei rischi: http://mywiki.wooledge.org/BashFAQ/048 –

+0

Buona cattura @CharlesDuffy, se questo viene utilizzato su un sistema condiviso in cui agli altri utenti è concesso l'accesso allo script per l'esecuzione con diritti elevati, è un rischio. – Eddie

1

Citando spazi interni variabili in modo tale che il guscio si ri-interpretare le cose è propriamente difficile. È questo tipo di cose che mi spinge a raggiungere un linguaggio più forte. Che sia perl o pitone o rubino o altro (scelgo perl, ma non è sempre per tutti), è solo qualcosa che è che ti consentirà di ignorare la shell per la citazione.

Non è che non sono mai riuscito a farlo bene con abbondanti dosi di eval, ma solo che eval mi dà i eebie-jeebies (diventa un intero mal di testa di nuovo quando si vuole prendere l'input dell'utente e eval, anche se in questo caso useresti roba che hai scritto e la valuta, invece, e che ho avuto mal di testa nel debugging.

con il Perl, come il mio esempio, io sarei in grado di fare qualcosa di simile:

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

La parte difficile qui sta facendo i tubi - ma un po 'di IO::Pipe, forchetta, e la riapertura stdout e stderr e non è male Alcuni direbbero che è peggio che citare correttamente la shell, e capisco da dove vengono, ma, per me, questo è più facile da leggere, mantenere e scrivere. Diamine, qualcuno potrebbe prendere il duro lavoro da questo e creare un modulo IO :: Pipeline e rendere l'intera cosa banale ;-)

+0

È solo un problema difficile se si * fa * un problema difficile usando 'eval'. Non c'è una buona ragione per farlo. –

4

C'è un punto per mettere solo i comandi e le opzioni nelle variabili.

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

È possibile spostare i comandi a un altro file di origine, in modo da poter riutilizzare gli stessi comandi e le opzioni in molti script. Questo è molto utile quando hai molti script e vuoi controllare come tutti usano gli strumenti. Così standard_tools sarebbero contengono:

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

'tar_create' manca ma ha comunque aiutato –

+1

Questo non risolve il problema per argomenti complessi. Se il tuo 'tar_create' era' tar cv --exclude = "* *" ', avrebbe fallito più o meno allo stesso modo dell'originale. E il 'export's non fa nulla di utile qui - queste variabili sono usate nella stessa shell, quindi lo spazio ambientale del sottoprocesso inquinante è semplicemente uno spreco. –

5

eval non è una pratica accettabile se i vostri nomi delle directory può essere generato da fonti non attendibili. Vedere BashFAQ #48 per ulteriori informazioni sul motivo per cui non deve essere utilizzato eval e BashFAQ #50 per più sulla causa principale di questo problema e le sue soluzioni adeguate, alcuni dei quali sono toccati in seguito:

Se avete bisogno di costruire i comandi nel tempo , utilizzare le matrici:

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

in alternativa, se si tratta solo di definire i comandi in un unico posto centrale, utilizzare le funzioni di:

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