Script di Bash: gestione degli spazi durante l'esecuzione di comandi indiretti


3

Ho sofferto per un po 'di tempo con i problemi successivi che ho scritto script in cui ogni comando eseguito viene prima messo in una stringa, e poi eseguito. In questo modo, il comando viene scritto una sola volta e può essere analizzato, visualizzato, utilizzato più volte. Funziona bene la maggior parte del tempo ma non nei seguenti casi dettagliati qui sotto. Quindi se qualcuno di voi può aiutare, sarei più che felice;) Devo ammettere che ho sempre difficoltà a capire come funziona lo script quando si tratta di variabili contenenti virgolette e spazi, quindi se si conosce un buon tutorial ...

Caso 1: comando mkdir

dir="/tmp/Mytests/DirWith2 spaces 2"
cmdToRun="mkdir -p ${dir}"
echo "Running [${cmdToRun}]"
${cmdToRun} # <= this does not work, OK it seems normal !

cmdToRun="mkdir -p """${dir}""""
${cmdToRun} # <= this does not work either !

cmdToRun="mkdir -p \"${dir}\""
${cmdToRun} # <= this does not work either !

Caso 2: comando rsync

rsync_cmd="rsync -avz --stats " 
rsync_destinationBaseDir="/tmp/ToTestRsync"
ListOfDirToSynchronize=( \
"/opt/dir1/subdir1" "/opt/dir1space 1/subdir1" "/opt/dir1space 1/subdir1space 1" )
rsync_host_from="root@SRV1:" 
A loop on the directories in my ListOfDirToSynchronize
{
  SourceDir="${ListOfDirToSynchronize[$i]}"  
  ( # here we start a subshell to run // rsyncsto speed-up the whole process
    cmdToRun="${rsync_cmd} ${rsync_host_from}'${SourceDir}/' ${rsync_destinationBaseDir}${SourceDir}"
    funcLog " | INFO | Directories synchronisation | SubprocessID [${subProcessID}] | running command [${cmdToRun}]"
    ${cmdToRun} >> ${LOG_FILE} 2>&1 #<== this does not work when spaces in the directories
  ) # end of subshell
} # end of the loop

Risposte:


2

Risposta breve: vedi BashFAQ # 050 .

Risposta lunga: in genere, il modo migliore per farlo consiste nel mettere i comandi negli array anziché nelle variabili semplici (eval is non consigliato - tende a creare nuovi e pericolosi bugs). Ecco il caso 1 fatto in stile array:

dir="/tmp/Mytests/DirWith2 spaces 2"
cmdToRun=(mkdir -p "${dir}")  # Note double-quotes to preserve spaces within $dir
"${cmdToRun[@]}"  # This is the standard idiom for expanding an array preserving both spaces and word breaks

Caso 2:

rsync_cmd=(rsync -avz --stats)
rsync_destinationBaseDir="/tmp/ToTestRsync"
ListOfDirToSynchronize=( \
  "/opt/dir1/subdir1" "/opt/dir1space 1/subdir1" "/opt/dir1space 1/subdir1space 1" )
rsync_host_from="root@SRV1:" 
# A loop on the directories in my ListOfDirToSynchronize
for SourceDir in "${ListOfDirToSynchronize[@]}"; do
  ( # here we start a subshell to run // rsyncsto speed-up the whole process
    cmdToRun=("${rsync_cmd[@]}" "${rsync_host_from}${SourceDir}/" "${rsync_destinationBaseDir}${SourceDir}")
    funcLog " | INFO | Directories synchronisation | SubprocessID [${subProcessID}] | running command [${cmdToRun[*]}]"
    "${cmdToRun[@]}" >> ${LOG_FILE} 2>&1 #<== this does not work when spaces in the directories
  ) # end of subshell
done # end of the loop

Si noti che nella voce di registro che ho usato ${cmdToRun[*]} invece di ${cmdToRun[@]}, quindi separerà le voci dell'array con spazi anziché trattarli come argomenti separati per funclog. Ciò significa che il registro è ambiguo (non è possibile indicare spazi all'interno dei nomi di file da spazi tra nomi di file); se questo è un problema, usa $(printf " %q" "${cmdToRun[@]}") invece, aggiungerà virgolette / escape / etc per gli spazi all'interno dei nomi di file.


Grazie Gordon, ho appena letto il tuo commento e ho già testato con successo la funzione eval (vedi il mio commento precedente). Testerò quel tomorow ma, per me, sarà una soluzione migliore in quanto ho qualche difficoltà a capire tutto sulla funzione eval, mentre con gli array, non c'è magia magica. Yann
user104688

1

Il eval il comando è tuo amico:

dir="/tmp/Mytests/DirWith2 spaces 2"

cmdToRun="mkdir -p \"${dir}\""
eval "${cmdToRun}"

E:

cmdToRun="${rsync_cmd} ${rsync_host_from}'${SourceDir}/' '${rsync_destinationBaseDir}${SourceDir}'"
funcLog ...
eval "${cmdToRun}" >> "${LOG_FILE}" 2>&1

Super! funziona bene Grazie, davvero.
user104688

Spero che l'altra parte del mio problema venga risolta in modo rapido ed efficiente ....
user104688

Ho aggiunto una soluzione per la seconda parte. L'idea è la stessa, ma non ho intenzione di configurare un'intera configurazione rsync per testarlo. ;-)
Peter Eisentraut

Non usare eval se possibile, causa strani bug. Ad esempio, prova quanto sopra su un nome file con una doppia citazione in esso. Fare NON provalo su un file chiamato foo$(rm -R ~)bar o sarai molto molto infelice.
Gordon Davisson

Ciao Peter, grazie per tutto. Dopo avermi detto la prima volta che ho parlato di eval per mkdir, ho pensato di provare la stessa soluzione su rsync. Ho trovato una soluzione che ho provato che utilizza l'opzione "\" # per sfuggire a tutti gli spazi bianchi (se presenti): "" diventa "\" ma non trattiamo qui con filepath che contiene due o più spazi consecutivi SourceDir = $ (echo "$ {SourceDir}" | sed -e "s: [\ t] [\ t] *: \\\: g") cmdToRun = "$ {rsync_cmd_base} $ {rsync_cmd_options} $ {rsync_host_from} '$ { SourceDir} / '$ {rsync_destinationBaseDir} $ {SourceDir} "eval" $ {cmdToRun} "& gt; & gt; "$ {LOG_FILE}" 2 & gt; & amp; 1
user104688
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.