Criando o script BASH 'para' manipular nomes de arquivos com espaços (ou solução alternativa)

12

Embora eu tenha usado BASH por vários anos, minha experiência com scripts BASH é relativamente limitada.

Meu código é como abaixo. Ele deve pegar toda a estrutura de diretórios dentro do diretório atual e replicá-la em $OUTDIR .

for DIR in 'find . -type d -printf "\"%P\"0"'
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done

O problema é que aqui está uma amostra da minha estrutura de arquivos:

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K

Observe os espaços: -S E for recebe parâmetros palavra por palavra, portanto, a saída do meu script é algo assim:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot

Mas eu preciso dele para pegar nomes de arquivos inteiros (uma linha de cada vez) da saída de find . Eu também tentei fazer com que find colocasse aspas duplas em torno de cada nome de arquivo. Mas isso não ajuda.

for DIR in 'find . -type d -printf "\"%P\"0"'

E saída com essa linha alterada:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"

Agora, eu preciso de alguma maneira que eu possa iterar assim, porque eu também quero executar um comando mais complicado envolvendo gstreamer em cada arquivo em uma estrutura similar a seguir. Como eu deveria estar fazendo isso?

Edit: Eu preciso de uma estrutura de código que me permita executar várias linhas de código para cada diretório / arquivo / loop. Desculpe se eu não estava claro.

Solução: tentei inicialmente:

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done

Isso funcionou bem na maior parte. No entanto, descobri mais tarde que, como os resultados do pipe no loop while estão sendo executados em um subshell, as variáveis configuradas no loop ficaram indisponíveis posteriormente, o que dificultou bastante a implementação de um contador de erros. Minha solução final (de esta resposta em SO ):

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

Isso mais tarde me permitiu incrementar condicionalmente as variáveis dentro do loop, que permaneceriam disponíveis mais tarde no script.

    
por Samuel Jaeschke 31.12.2009 / 00:19

8 respostas

11

Você precisa enviar o find para um loop while .

find ... | while read -r dir
do
    something with "$dir"
done

Além disso, você não precisará usar -printf neste caso.

Você pode fazer essa prova contra arquivos com novas linhas em seus nomes, se desejar, usando um delimitador de bytes nulos (que é o único caractere que não pode aparecer em um caminho de arquivo * nix):

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done

Você também encontrará $() em vez de backticks para ser mais versátil e fácil. Eles podem ser aninhados com muito mais facilidade e as citações podem ser feitas com muito mais facilidade. Este exemplo artificial ilustrará esses pontos:

echo "$(echo "$(echo "hello")")"

Tente fazer isso com backticks.

    
por 31.12.2009 / 00:24
7

Veja esta resposta que escrevi há alguns dias para um exemplo de um script que lida com nomes de arquivos com espaços.

Existe uma maneira um pouco mais complicada (mas mais concisa) de alcançar o que você está tentando fazer:

find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}

-print0 diz ao find para separar os argumentos com um nulo; o -0 para xargs diz para esperar que os argumentos sejam separados por nulos. Isso significa que ele manipula os espaços bem.

-I {} diz ao xargs para substituir a string {} pelo nome do arquivo. Isso também implica que apenas um nome de arquivo deve ser usado por linha de comando (xargs normalmente irá encher o número que couber na linha)

O resto deve ser óbvio.

    
por 31.12.2009 / 01:09
5

O problema que você está enfrentando é que a instrução for está respondendo ao find como argumentos separados. O delimitador de espaço. Você precisa usar a variável IFS do bash para não dividir o espaço.

Aqui está uma link que explica como fazer isso.

The IFS internal variable

One way around this problem is to change Bash's internal IFS (Internal Field Separator) variable so that it splits fields by something other than the default whitespace (space, tab, newline), in this case, a comma.

#!/bin/bash
IFS=$';'

for I in 'find -type d -printf \"%P\"\;'
do
   echo "== $I =="
done

Defina seu achado para produzir o delimitador de campo após o% P e defina seu IFS apropriadamente. Eu escolhi o ponto-e-vírgula, pois é muito improvável que ele encontre seus nomes de arquivo.

A outra alternativa é chamar mkdir do achado diretamente via -exec , você pode pular o loop for por completo. Isso se você não precisar fazer nenhuma análise adicional.

    
por 31.12.2009 / 00:31
4

Se o corpo do seu loop for mais do que um único comando, é possível usar xargs para direcionar um script de shell:

export OUTPATH=/some/where/else/
find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; do
  printf "mkdir -p %q\n" "${OUTPATH}${DIR}"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done' -

Certifique-se de incluir o traço final (ou alguma outra "palavra") se o shell for da variedade Bourne / POSIX (é usado para definir $ 0 no shell script). Além disso, deve-se tomar cuidado com as citações, já que o shell script está sendo escrito dentro de uma string entre aspas, em vez de diretamente no prompt.

    
por 31.12.2009 / 06:18
1

na sua pergunta atualizada você tem

mkdir -p \"${OUTPATH}${DIR}\"

isso deve ser

mkdir -p "${OUTPATH}${DIR}"
    
por 02.01.2010 / 00:42
1
find . -type d -exec mkdir -p "{}0" ';' -exec echo "Created {}0" ';'
    
por 25.02.2014 / 16:09
0

ou para tornar a coisa toda muito menos complicada:

% rsync -av --include='*/' --exclude='*' SRC DST

isto replica a estrutura de diretórios do SRC para o DST.

    
por 31.12.2009 / 10:12
0

Se você tem o GNU Parallel http: // www.gnu.org/software/parallel/ instalado, você pode fazer isso:

find . -type d | parallel echo making {} ";" mkdir -p /tmp/outdir/{} ";" echo made {}

Assista ao vídeo de introdução do GNU Parallel para saber mais: link

    
por 22.08.2010 / 22:13