Problème avec les espaces dans les noms de fichiers

8

Je veux faire quelque chose à plusieurs reprises sur une liste de fichiers. Les fichiers des questions ont des espaces dans leurs noms:

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)

Maintenant, je veux statuer chacun de ces fichiers:

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

(J'utilise echo et un pipe pour écrire des commandes multilignes.)

Lorsque je fais cela, cela fonctionne correctement sur les fichiers qui n'ont pas d'espace dans leurs noms. Mais les autres ...

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

Le passage $(ls)à "$(ls)"ou $fileà "$file"ne fonctionne pas. Que puis-je faire?

Éditer:

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

fait l'affaire! Comme je suis nouveau dans bash, je veux garder les choses aussi simples que possible - donc rien avec essayer d'échapper aux espaces, ou d'utiliser xargsou la solution avec read -r, bien qu'ils résolvent le problème.

Comme certains l'ont demandé: Oui, utiliser cela au lieu de stat *est bizarre. Mais je voulais juste trouver un moyen général d'appliquer la même commande sur un tas de noms de fichiers en bash, en utilisant une boucle for. Ainsi statpourrait représenter gzip, gpgou rm.

user258532
la source
1
qu'est-ce qui ne va pas stat *? (;-)
Rmano
J'utilise juste stat comme exemple. :) Je veux collecter les noms de fichiers avec ls, puis utiliser les résultats de ls dans une boucle bash.
user258532

Réponses:

12

La citation multiple de la echo 'complique la chose.

Vous pouvez simplement utiliser:

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

Mais aussi

stat -- * 

... et si vous voulez collecter les fichiers et ensuite appliquer la commande (pourquoi?) vous pouvez aller avec (mais attention aux fichiers contenant de nouvelles lignes ... (1))

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

... et si vous voulez aussi des fichiers cachés, utilisez-les simplement * .*comme modèle, mais n'oubliez pas cela .et ..ils seront dans le jeu .

En passant, vous ne devriez pas analyser la lssortie .


(1) mais si vous avez des noms de fichiers avec des retours à la ligne, vous le méritez un peu ... ;-)

Rmano
la source
"et ensuite appliquer la commande (pourquoi?)" -> stat sert simplement de commande arbitraire, car j'essaie de faire des boucles bash avec des noms de fichiers. Cela peut être gpg, gzip ou autre.
user258532
@ user258532 quelle que soit la commande, utilisez toujours for f in *; do command "$f"; done. Ne jamais analyser ls, certainement jamais le faire en for boucle et pourquoi l'utiliser echo?
terdon
echo: Parce que j'aime écrire les commandes sur plusieurs lignes ... :)
user258532
2
@ user258532 Hein? Pourquoi auriez-vous besoin d'écho pour cela? Appuyez simplement sur Entrée et continuez sur une nouvelle ligne. Si vous finissez une ligne avec une citation ouverte ou sur l' un do, |, &&etc, vous pouvez continuer sur la nouvelle ligne. Soit cela, soit utilisez heredocs. Aucune raison d'utiliser echoet cela peut aussi causer des problèmes.
terdon
"Appuyez simplement sur Entrée et continuez sur une nouvelle ligne." Aie. :-D Maintenant, mon QI est officiellement inférieur à 0 - vous savez, je suis vraiment nouveau pour bash et des choses comme ça. Mais je gagne réellement mon argent avec R (la suite statistique et le langage de script).
user258532
6

Sur une note latérale: vous pouvez diviser les commandes longues / compliquées sur plusieurs lignes en ajoutant un espace suivi d'une barre oblique inverse et en frappant Enterchaque fois que vous voulez commencer à écrire dans une nouvelle ligne, au lieu de bifurquer plusieurs processus en utilisant echo [...] | bash; vous devez également les mettre entre $fileguillemets, pour éviter statde casser en cas de noms de fichiers contenant des espaces:

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

Le problème est que cela se $(ls)développe en une liste de noms de fichiers contenant des espaces, et la même chose se produira également avec "$(ls)".

Même en résolvant ce problème, cette méthode se cassera toujours sur les noms de fichiers contenant des barres obliques inverses et sur les noms de fichiers contenant des sauts de ligne (comme souligné par terdon).

Une solution pour les deux problèmes serait de diriger la sortie de findvers une whileboucle en cours d'exécution de read -rsorte qu'à chaque itération, read -relle stocke une ligne de findsortie dans $file:

find . -maxdepth 1 -type f | while read -r file; do \
    stat "$file"; \
done
kos
la source
1
et les fichiers cachés? :)
AB
5
Cela échouera toujours sur les noms de fichiers contenant des sauts de ligne. Ne pas analyser ls. Déjà.
terdon
4
@ user258532 non, ce n'est pas le cas. Sérieusement, ne pas analyserls . Il existe des moyens meilleurs et plus robustes. Vous pourriez également vouloir lire ceci: Pourquoi * pas * analyser `ls`? pour plus de détails.
terdon
1
Le problème ici n'est pas ls, c'est for- foritère sur les mots (séparés par IFS) donnés après le inmot clé`
glenn jackman
1
@glennjackman bien oui, c'est la combinaison de foret ls. for f in *serait bien, par exemple.
terdon
3

Utilisez le bon vieux find, fonctionne avec des fichiers cachés, des nouvelles lignes et des espaces.

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

ou tout autre au lieu de stat

find . -print0 | xargs -I {} -0 file {}
find . -print0 | xargs -I {} -0 cat {}
UN B
la source
1

En tant que gars R, j'ai déjà trouvé une solution de contournement dans 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]
 )
)
}

Je sais, c'est fou. Je souhaite que la sortie de lssoit plus facile à analyser ... R peut gérer les espaces, car dir () renvoie une valeur de caractère entre guillemets. Tout ce qui se trouve entre les guillemets est alors un nom de fichier valide avec des espaces.

user258532
la source
3
Ne vous embêtez pas (mais +1 pour l'effort! :). Utilisez simplement for f in *, appuyez sur Entrée et continuez sur une nouvelle ligne do:, appuyez de nouveau sur stat "$f"Entrée,, Entrez de nouveau, doneentrez. C'est une commande bien répartie sur 4 lignes et ne se cassera pas sur tout type de nom de fichier.
terdon
1

J'ai rencontré d'autres instances de problèmes d'espaces dans les boucles for, donc la commande suivante (imo plus robuste) est ce que j'utilise généralement. Il s'intègre également bien dans les tuyaux.

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

Vous pouvez combiner cela avec grepou utiliser à la findplace:

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;
Tyzoïde
la source
Votre première réponse échoue sur les noms de fichiers contenant une barre oblique inverse ou des espaces de début ou de fin. Votre deuxième réponse échoue totalement sauf si vous ajoutez l' -Eoption à grep, sans laquelle elle ne sera pas reconnue +dans une expression régulière. Même alors, c'est une balançoire et un échec sur cette question, car votre grep supprime les noms de fichiers contenant des espaces . Il supprime également les noms de fichiers contenant des chiffres (chiffres) et la ponctuation (par exemple, les parenthèses), comme le font les exemples de la question. Et cela ne mentionne même pas les noms de fichiers qui contiennent une nouvelle ligne ou commencent par -(tiret).
Scott
-1

Cette réponse résoudra le problème de l'analyse lset prendra soin des backspaces et des nouvelles lignes

Essayez ceci, cela résoudra votre problème en utilisant IFS séparateur de champ interne.

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

Mais vous pouvez également le résoudre facilement sans avoir besoin d'analyser la sortie ls en utilisant

for f in *; do   stat "$f"; done
Maythux
la source
1
ne fonctionne pas pour les fichiers cachés.
AB
2
OP n'a pas demandé de fichiers cachés
Maythux
1
Je ne vois pas pourquoi vous devez modifier IFSici: la citation de la variable devrait être suffisante pour empêcher le fractionnement de mots, sûrement?
steeldriver
pour l'analyse ls .
Maythux
Pour quel est le downvote !!!
Maythux
-1

Au lieu de cela, vous pouvez renommer vos fichiers en remplaçant l'espace par un autre caractère tel que le trait de soulignement, afin de vous débarrasser de ce problème:

Pour ce faire, exécutez facilement la commande:

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