Problema com espaços nos nomes dos arquivos

8

Eu quero fazer algo repetidamente em uma lista de arquivos. Os arquivos nas perguntas 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 eu quero declarar 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

Alterar $(ls)para "$(ls)"ou $filepara "$file"não funciona. O que eu posso fazer?

Editar:

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

faz o truque! Como sou iniciante no bash, quero manter as coisas o mais simples possível - nada tentando escapar dos espaços, usando xargsou a solução 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 vários nomes de arquivos no bash, usando um loop for. Então, statpoderia significar gzip, gpgou rm.

user258532
fonte
1
o que há de errado stat *? (;-)
Rmano
Veja também mywiki.wooledge.org/ParsingLs
Rmano
Eu apenas uso stat como exemplo. :) Quero coletar nomes de arquivos com ls e usar os resultados de ls em um loop bash.
User258532 8/15

Respostas:

12

A citação múltipla do echo 'está complicando a coisa.

Você pode apenas usar:

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

Mas também

stat -- * 

... e se você deseja coletar os arquivos e aplicar o comando (por quê?), você pode prosseguir (mas tenha cuidado com o arquivo que contém novas linhas ... (1))

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

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

Como um aparte, você não deve analisar a lssaída .


(1) mas se você tiver nomes de arquivos com novas linhas, você merece um pouco ... ;-)

Rmano
fonte
"e aplique o comando (por quê?)" -> stat serve apenas como um comando arbitrário, pois estou tentando descobrir como fazer loops bash com nomes de arquivos. Pode ser gpg, gzip ou qualquer outra coisa.
User258532 8/15
@ user258532 qualquer que seja o comando, use sempre for f in *; do command "$f"; done. Nunca analise ls, certamente nunca faça isso em for loop e por que usar echo?
terdon
echo: Porque eu gosto de escrever os comandos em várias linhas ... :)
user258532
2
@ user258532 Hein? Por que você precisaria ecoar para isso? Basta pressionar enter e continuar em uma nova linha. Se você terminar uma linha com uma cotação aberto ou em um dos do, |, &&etc, você pode continuar na nova linha. Ou isso ou use heredocs. Não há razão para usar echoe também pode causar problemas.
terdon
"Basta pressionar Enter e continuar em uma nova linha." Ai. :-D Agora, meu QI está oficialmente abaixo de 0 - você sabe, eu sou muito novo no bash e coisas assim. Mas, na verdade, ganho meu dinheiro com o R (o conjunto estatístico e a linguagem de scripts).
User258532 8/15
6

Em uma observação lateral: você pode dividir comandos longos / complicados em várias linhas adicionando um espaço seguido por uma barra invertida e pressionando Entertoda vez que quiser começar a escrever em uma nova linha, em vez de bifurcar vários processos usando echo [...] | bash; Além disso, você deve colocar $fileaspas duplas, para impedir a statquebra no caso de nomes de arquivos que contenham espaços:

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

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

Mesmo resolvendo esse problema, esse método ainda será interrompido nos nomes de arquivos que contêm barras invertidas e nos nomes de arquivos que contêm novas linhas (conforme indicado por terdon).

Uma solução para os dois problemas seria canalizar a saída de findum whileloop read -rpara que, a cada iteração, read -rarmazene uma linha de findsaída em $file:

find . -maxdepth 1 -type f | while read -r file; do \
    stat "$file"; \
done
kos
fonte
1
e arquivos ocultos? :)
AB
5
Isso ainda falhará nos nomes de arquivos que contêm novas linhas. Apenas não analise ls. Sempre.
terdon 8/15
4
@ user258532 não, não. Sério, apenas não analisels . Existem maneiras melhores e mais robustas. Você também pode ler o seguinte: Por que * não * analisar `ls`? para mais detalhes.
terdon
1
O problema aqui não é ls, é for- foritera sobre (IFS separados) palavras dado depois que o inkeyword`
Glenn Jackman
1
@glennjackman bem, sim, é a combinação de fore ls. for f in *ficaria bem, por exemplo.
terdon
3

Use o bom e velho findtrabalho 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 {}
AB
fonte
1

Como R, eu 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, é loucura. Eu gostaria que a saída de lsfosse mais fácil de analisar ... R pode lidar com espaços, porque dir () retorna um valor de caractere citado. Qualquer coisa entre as aspas é um nome de arquivo válido com espaços.

user258532
fonte
3
Não se preocupe (mas +1 por esforço! :). Basta usar for f in *, pressionar enter e continuar em uma nova linha do:, pressione enter novamente stat "$f", digite novamente, donedigite. É um comando dividido bem em 4 linhas e não quebra em nenhum tipo de nome de arquivo.
terdon
1

Eu já encontrei outras instâncias de problemas de espaço em branco em loops, então o comando (imo mais robusto) a seguir é o que geralmente uso. Também se encaixa perfeitamente em tubos.

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

Você pode combinar isso grepou usar find:

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;
Tyzoid
fonte
Sua primeira resposta falha em nomes de arquivos que contêm barra invertida ou espaços à esquerda ou à direita. Sua segunda resposta falha totalmente, a menos que você inclua a -Eopção grep, sem a qual ela não será reconhecida +em uma expressão regular. Mesmo assim, é um fracasso e uma falha nesta questão, pois você grep remove nomes de arquivos que contêm espaços . Também remove nomes de arquivos contendo dígitos (numerais) e pontuação (por exemplo, parênteses), como fazem os exemplos da pergunta. E isso nem menciona nomes de arquivos que contêm nova linha ou começam com -(traço).
Scott
-1

Esta resposta resolverá o problema de análise lse cuidará dos backspaces e das novas linhas

Tente isso, ele resolverá o seu problema usando o Internal Field Separator IFS.

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

Mas você também pode resolvê-lo rapidamente sem precisar analisar a saída usando

for f in *; do   stat "$f"; done
Maythux
fonte
1
não funciona para arquivos ocultos.
AB
2
O OP não pediu arquivos ocultos
Maythux 8/15
1
Não vejo por que você precisa modificar IFSaqui: citar a variável deve ser suficiente para impedir a divisão de palavras, com certeza?
Steeldriver # 8/15
para análise ls .
Maythux
Para o que é o voto negativo !!!
Maythux
-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 fazer isso, execute o comando facilmente:

for file in * ; do mv "$f" "${f// /_}" ; done
Maythux
fonte