Problema com espaços em nomes de arquivos

8

Eu quero fazer algo repetidamente em uma lista de arquivos. Os arquivos em questões têm espaços em seus nomes:

david@david: ls -l
total 32
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha
-rw-rw-r-- 1 david david  0 Mai  8 11:55 haha~
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (3rd copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (4th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (5th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (6th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (7th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (another copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (copy)

Agora quero definir cada um desses arquivos:

david@david: echo '
for file in $(ls)
do
stat $file
done' | bash

(eu uso echo e um pipe para escrever comandos de várias linhas.)

Quando faço isso, ele funciona corretamente nos arquivos que não possuem espaços em seus nomes. Mas os outros ...

stat: cannot stat ‘(another’: No such file or directory
stat: cannot stat ‘copy)’: No such file or directory

A alteração de $(ls) para "$(ls)" ou $file para "$file" não funciona. O que posso fazer?

Editar:

echo '
for files in *
do
stat "$files"
done' | bash

faz o truque! Como sou novo no bash, quero manter as coisas o mais simples possível - então nada com a tentativa de escapar de espaços, ou usando xargs ou a solução com read -r , embora eles resolvam o problema.

Como alguns perguntaram: Sim, usar isso em vez de stat * é estranho. Mas eu só queria encontrar uma maneira geral de aplicar o mesmo comando em um monte de nomes de arquivos no bash, usando um loop for. Então, stat poderia representar gzip , gpg ou rm .

    
por user258532 08.05.2015 / 12:35

7 respostas

12

As várias citações do echo ' estão complicando a coisa.

Você pode usar apenas:

for f in *; do stat -- "$f"; done

Mas também

stat -- * 

... e se você quiser coletar os arquivos e então aplicar o comando (por que?) você pode ir com (mas tenha cuidado com o arquivo contendo novas linhas ... (1))

for f in *; do echo "$f"; done | xargs stat --

... e se você quiser também arquivos ocultos, use apenas * .* como padrão, mas lembre-se que . e .. estarão no conjunto .

Como um aparte, você não deve analisar ls output .

(1) mas se você tem nomes de arquivos com novas linhas, você de alguma forma merece ...; -)

    
por Rmano 08.05.2015 / 13:04
6

Em uma nota lateral: você pode dividir comandos longos / complicados em várias linhas adicionando um espaço seguido por uma barra invertida e pressionando Enter toda vez que quiser começar a escrever em uma nova linha, em vez de bifurcá-la vários processos usando echo [...] | bash ; você também deve colocar $file entre aspas duplas, para evitar que stat seja quebrado no caso de nomes de arquivos contendo espaços:

for file in $(ls); \
do \
stat "$file"; \
done

O problema é que $(ls) se expande para uma lista de nomes de arquivos contendo espaços, e o mesmo acontecerá também com "$(ls)" .

Mesmo resolvendo este problema, este método ainda irá quebrar em nomes de arquivos contendo barras invertidas e em nomes de arquivos contendo novas linhas (como apontado por terdon).

Uma solução para ambos os problemas seria canalizar a saída de find para um while loop executando read -r , de modo que, a cada iteração, read -r armazenará uma linha da saída de find em $file :

find . -maxdepth 1 -type f | while read -r file; do \
    stat "$file"; \
done
    
por kos 08.05.2015 / 12:42
3

Use o bom e velho find , funciona com arquivos ocultos, novas linhas e espaços.

find . -print0 | xargs -I {} -0 stat {}

ou qualquer outro em vez de stat

find . -print0 | xargs -I {} -0 file {}
find . -print0 | xargs -I {} -0 cat {}
    
por A.B. 08.05.2015 / 13:05
1

Como R, já encontrei uma solução alternativa em R:

filenames <- dir(); # reads file names into an array.
                    # It works also recursively
                    # with dir(recursive=TRUE)
for (i in 1:length(filenames)) {
system(     # calls a system function
 paste(     # join stat and the file name
  "stat",
  filenames[i]
 )
)
}

Eu sei, é uma loucura. Desejo que a saída de ls seja mais fácil de analisar ... R pode lidar com espaços, porque dir () retorna um valor de caractere entre aspas. Qualquer coisa entre aspas é, então, um nome de arquivo válido com espaços.

    
por user258532 08.05.2015 / 13:17
1

Eu me deparei com outras instâncias de problemas de espaços em branco em loops for, então o seguinte comando (imo mais robusto) é o que geralmente uso. Ele também se encaixa muito bem em tubos.

$ ls | while read line; do stat "$line"; done;

Você pode combinar isso com grep ou usar find :

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;
    
por Tyzoid 08.05.2015 / 17:01
-1

Essa resposta resolverá o problema de analisar ls e cuidar dos backspaces e novas linhas

Tente isso, ele resolverá seu problema usando o IFS do Separador de campo interno.

IFS="\n" for f in $(ls); do   stat "$f"; done

Mas também você pode resolvê-lo sem necessidade de analisar a saída ls usando

for f in *; do   stat "$f"; done
    
por Maythux 08.05.2015 / 12:45
-1

Em vez disso, você pode renomear seus arquivos substituindo o espaço por algum outro caractere, como sublinhado, para se livrar desse problema:

Para isso, execute facilmente o comando:

for file in * ; do mv "$f" "${f// /_}" ; done
    
por Maythux 15.06.2015 / 17:12