Script Bash: lidando com espaços ao executar indiretamente comandos

3

Eu tenho lutado por um tempo com os seguintes problemas que escrevi scripts nos quais qualquer comando executado é primeiro colocado em uma string, e então executado. Dessa forma, o comando é escrito apenas uma vez e pode ser analisado, exibido, usado várias vezes. Ele funciona bem na maioria das vezes, mas não nos seguintes casos detalhados abaixo. Então, se algum de vocês puder ajudar, eu ficaria mais que feliz;) Eu devo admitir que eu sempre luto para entender o modo como o script funciona ao lidar com variáveis contendo citações e espaços, então se você souber de um bom 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
    
por user104688 09.11.2011 / 12:11

2 respostas

2

Resposta curta: veja BashFAQ # 050 .

Resposta longa: geralmente, a melhor maneira de fazer isso é colocando comandos em arrays em vez de variáveis simples (eval is não recomendado - ele tende a criar bugs novos e perigosos). Aqui está o caso 1 do estilo 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

Observe que na entrada de log eu usei ${cmdToRun[*]} em vez de ${cmdToRun[@]} , então ele separará as entradas da matriz com espaços em vez de tratá-las como argumentos separados para funcLog. Isso significa que o log é ambíguo (você não pode informar espaços dentro de nomes de arquivos a partir de espaços entre nomes de arquivos); Se isso for um problema, use $(printf " %q" "${cmdToRun[@]}") , e ele adicionará aspas / escapes / etc para espaços dentro de nomes de arquivos.

    
por 09.11.2011 / 17:12
1

O comando eval é seu amigo:

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
    
por 09.11.2011 / 13:20

Tags