Comment utiliser la recherche lorsque le nom de fichier contient des espaces?

17

Je veux diriger les noms de fichiers vers d'autres programmes, mais ils s'étouffent tous lorsque les noms contiennent des espaces.

Disons que j'ai un fichier appelé.

foo bar

Comment puis-je obtenir findle nom correct?

Evidemment je veux:

foo\ bar

ou:

"foo bar"

EDIT : Je ne veux pas passer par xargs, je veux obtenir une chaîne correctement formatée findafin que je puisse diriger la chaîne de noms de fichiers directement vers un autre programme.

punaise
la source
5
à quoi le sifflez-vous? connaissez-vous le -execdrapeau avec find? vous pourriez potentiellement atténuer cette erreur et rendre votre commande plus efficace en la faisant -execau lieu de la rediriger vers d'autres commandes. Just my $ .02
h3rrmiller
6
@bug: findformate très bien les noms de fichiers; ils sont écrits un nom par ligne. (Bien sûr, cela est ambigu si un nom de fichier contient un caractère de nouvelle ligne.) Le problème est donc que l'extrémité de réception "s'étouffe" lorsqu'elle obtient un espace, ce qui signifie que vous devez nous dire quelle est l'extrémité de réception si vous voulez une réponse significative. .
rici
2
Ce que vous appelez "correctement formaté" est vraiment "échappé à la consommation par le shell". La plupart des utilitaires qui peuvent lire un tas de noms de fichiers s'étoufferaient avec un nom échappé par le shell, mais il serait en fait logique findde proposer (par exemple) une option pour sortir les noms de fichiers dans un format adapté au shell. En général, cependant, l' extension -print0GNU findfonctionne bien pour de nombreux autres scénarios (aussi), et vous devez apprendre à l'utiliser dans tous les cas.
tripleee
2
@bug: Au fait, ls $(command...)ne fait pas passer la liste stdin. Il place la sortie de $(command...)directement dans la ligne de commande. Dans ce cas, c'est le shell qui lit à partir du c, et il utilisera la valeur actuelle de $IFSpour décider comment séparer les mots en sortie. En général, il vaut mieux utiliser xargs. Vous ne remarquerez pas un coup de performance.
rici
2
find -printf '"%p"\n'ajoutera des guillemets doubles autour de chaque nom trouvé, mais ne citera pas correctement les guillemets doubles dans un nom de fichier. Si vos noms de fichiers n'ont pas de guillemets doubles incorporés, vous pouvez ignorer le problème: ou passer par sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Si vos noms de fichiers finissent par être traités par le shell, vous devriez probablement utiliser des guillemets simples au lieu de guillemets doubles (sinon sweet$HOMEcela deviendra quelque chose comme sheet/home/you). Et ce n'est toujours pas très robuste contre les noms de fichiers avec des retours à la ligne. Comment voulez-vous gérer cela?
tripleee

Réponses:

18

POSIX:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

Avec findsupports -print0et xargssupports -0:

find . -type f -print0 | xargs -0 <command>

-0 L'option indique à xargs d'utiliser le caractère ASCII NUL au lieu de l'espace pour terminer (séparer) les noms de fichiers.

Exemple:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
cuonglm
la source
Ça ne marche pas. Quand je cours, ls $(find . -maxdepth 1 -type f -print0 | xargs -0)je reçois ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
bug
1
L'avez-vous essayé comme Gnouc l'a écrit? Si vous insistez pour le faire à votre façon, essayez de $(..)"$(..)"
joindre les
3
@bug: votre commande est erronée. Essayez exactement I worte et lisez la page de manuel de findet xargs.
cuonglm
Je vois, puis encore une fois, je veux obtenir une chaîne formatée que je pourrais diriger directement.
bug
1
@bug: Utilisez simplement xargs -0 <votre programme>
cuonglm
10

L'utilisation -print0est une option, mais tous les programmes ne prennent pas en charge l'utilisation de flux de données délimités par des octets, vous devrez donc utiliser xargsavec le-0 option pour certaines choses, comme l'a noté la réponse de Gnouc.

Une alternative serait d'utiliser findles options -execou -execdir. Le premier des éléments suivants alimentera les noms de fichiers somecommandun par un, tandis que le second s'étendra à une liste de fichiers:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Vous trouverez peut-être qu'il vaut mieux utiliser la globalisation dans de nombreux cas. Si vous avez un shell moderne (bash 4+, zsh, ksh), vous pouvez obtenir un globbing récursif avec globstar( **). En bash, vous devez définir ceci:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

J'ai une ligne disant shopt -s globstar extglob dans mon .bashrc, donc c'est toujours activé pour moi (et les globs étendus aussi, qui sont également utiles).

Si vous ne voulez pas de récursivité, utilisez simplement à la ./*.txtplace, pour utiliser chaque * .txt dans le répertoire de travail. finda des capacités de recherche fine très utiles et est obligatoire pour des dizaines de milliers de fichiers (auquel cas vous rencontrerez le nombre maximal d'arguments du shell), mais pour une utilisation quotidienne, il est souvent inutile.

evilsoup
la source
Hé @evilsoup, que fait le {} dans ce script?
Ayusman
3

Personnellement, j'utiliserais l' -execaction de recherche pour résoudre ce genre de problème. Ou, si nécessaire, xargsce qui permet une exécution parallèle.

Cependant, il existe un moyen d'obtenir findune liste de noms de fichiers lisibles par bash. Sans surprise, il utilise -execet bashnotamment une extension de la printfcommande:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

Cependant, bien que cela imprime correctement les mots échappés du shell, il ne sera pas utilisable avec $(...), car $(...)n'interprète pas les guillemets ou les échappements. (Le résultat de $(...)est sujet au fractionnement de mots et à l'expansion des noms de chemin, sauf s'il est entouré de guillemets.) Donc, ce qui suit ne fera pas ce que vous voulez:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

Ce que vous auriez à faire, c'est:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Notez que je n'ai fait aucune tentative réelle pour tester la monstruosité ci-dessus.)

Mais alors vous pourriez aussi bien faire:

find ... -exec ls {} +
rici
la source
Je ne pense pas que le lsscénario capture correctement le cas d'utilisation de l'OP, mais ce n'est que de la spéculation, car on ne nous a pas montré ce qu'il essayait réellement d'accomplir. Cette solution fonctionne en fait très bien; J'obtiens la sortie que j'attendais (vaguement) pour tous les noms de fichiers amusants que j'ai essayés, y compristouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee
@triplee: Je n'ai aucune idée de ce que OP veut faire non plus. Le seul véritable avantage de la construction de la chaîne entre guillemets à passer evalest que vous n'avez pas encore à la passer eval; vous pouvez l'enregistrer dans un paramètre et l'utiliser plus tard, peut-être plusieurs fois avec différentes commandes. Cependant, OP ne donne aucune indication que c'est le cas d'utilisation (et si c'était le cas, il serait peut-être préférable de mettre les noms de fichiers dans un tableau, bien que ce soit aussi délicat.)
rici
0
find ./  | grep " "

vous obtiendrez les fichiers et répertoires contient des espaces

find ./ -type f  | grep " " 

vous obtiendrez les fichiers contient des espaces

find ./ -type d | grep " "

vous obtiendrez les répertoires contient des espaces

Kannan Kumarasamy
la source
-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'
user283965
la source
C'est une réponse intéressante, mais ce n'est pas une réponse à cette question.
Scott