web-dev-qa-db-fra.com

La recherche et le nom de base ne fonctionnent pas correctement

Je veux faire écho à la partie du nom de fichier d'une recherche sur la ligne de commande Linux. J'ai essayé d'utiliser les éléments suivants:

find www/*.html -type f -exec sh -c "echo $(basename {})" \;

et

find www/*.html -type f -exec sh -c "echo `basename {}`" \;

et toute une série d'autres combinaisons d'échappement et de citation de différentes parties du texte. Le résultat est que le chemin n'est pas supprimé:

www/channel.html
www/definition.html
www/empty.html
www/index.html
www/privacypolicy.html

Pourquoi pas?

Mise à jour: Bien que j'ai une solution de travail ci-dessous, je suis toujours intéressé par pourquoi "nom de base" ne fait pas ce qu'il devrait faire.

39
Matt Parkins

Le problème avec votre tentative initiale:

find www/*.html -type f -exec sh -c "echo $(basename {})" \;

est que le code $(basename {}) est exécuté une fois, avant que la commande find ne soit exécutée. La sortie du single basename est {} Car c'est le nom de base de {} Comme nom de fichier. Ainsi, la commande exécutée par find est:

sh -c "echo {}" 

pour chaque fichier trouvé, mais find remplace en fait le nom de fichier d'origine (non modifié) à chaque fois car les caractères {} apparaissent dans la chaîne à exécuter.

Si vous vouliez que cela fonctionne, vous pouvez utiliser des guillemets simples au lieu de guillemets doubles:

find www/*.html -type f -exec sh -c 'echo $(basename {})' \;

Cependant, faire répéter echo à la sortie standard ce que basename aurait écrit de toute façon à la sortie standard est un peu inutile:

find www/*.html -type f -exec sh -c 'basename {}' \;

et nous pouvons encore réduire cela, bien sûr, à:

find www/*.html -type f -exec basename {} \;

Pourriez-vous également expliquer la différence entre les guillemets simples et les guillemets doubles ici?

Il s'agit du comportement de routine du shell. Prenons une commande légèrement différente (mais seulement légèrement - les noms des fichiers peuvent être n'importe où sous le répertoire www, pas seulement un niveau plus bas), et regardons les guillemets simples (SQ) et les guillemets doubles (DQ) versions de la commande:

find www -name '*.html' -type f -exec sh -c "echo $(basename {})" \;   # DQ
find www -name '*.html' -type f -exec sh -c 'echo $(basename {})' \;   # SQ

Les guillemets simples transmettent le matériel inclus directement à la commande. Ainsi, dans la ligne de commande SQ, le shell qui lance find supprime les guillemets englobants et la commande find voit son argument $9 Comme:

echo $(basename {})

car le Shell supprime les guillemets. Par comparaison, le matériel entre guillemets doubles est traité par le Shell. Ainsi, dans la ligne de commande DQ, le Shell (qui lance find - pas celui lancé par find) voit la partie $(basename {}) de la chaîne et l'exécute, récupérant {}, donc la chaîne qu'elle passe à find comme argument $9 est:

echo {}

Maintenant, quand find fait son action -exec, Dans les deux cas, il remplace le {} Par le nom de fichier qu'il vient de trouver (pour les besoins de l'argument, www/pics/index.html ). Ainsi, vous obtenez deux commandes différentes en cours d'exécution:

sh -c 'echo $(basename www/pics/index.html)'    # SQ
sh -c "echo www/pics/index.html"                # DQ

Il y a une (légère) triche de notation en cours - ce sont les commandes équivalentes que vous taperiez sur le Shell. Le $2 Du Shell qui est lancé n'a en fait aucune citation dans les deux cas - le Shell lancé ne voit aucune citation.

Comme vous pouvez le voir, la commande DQ fait simplement écho au nom du fichier; la commande SQ exécute la commande basename et capture sa sortie, puis fait écho à la sortie capturée. Un peu de pensée réductionniste montre que la commande DQ pourrait être écrite comme -print Au lieu d'utiliser -exec, Et la commande SQ pourrait être écrite comme -exec basename {} \;.

Si vous utilisez GNU find, il prend en charge l'action -printf Qui peut être suivie par Format Directives de telle sorte que l'exécution de basename n'est pas nécessaire. Cependant, cela n'est disponible que dans GNU find; le reste de la discussion ici s'applique à toute version de find vous sont susceptibles de rencontrer.

53
Jonathan Leffler

Essayez plutôt ceci:

 find www/*.html -type f -printf '%f\n'

Si vous voulez le faire avec un tuyau (plus de ressources nécessaires):

find www/*.html -type f -print0 | xargs -0 -n1 basename
13
Gilles Quenot

C'est comme ça que je redimensionne des fichiers par lots avec imagick, redivant le nom du fichier de sortie à partir de la source

find . -name header.png -exec sh -c 'convert -geometry 600 {} $(dirname {})/$(basename {} ".png")_mail.png' \;
7
maosmurf

J'ai dû accomplir quelque chose de similaire, et j'ai trouvé en suivant les pratiques mentionnées pour éviter de boucler sur la sortie de find et d'utiliser find avec sh évité ces problèmes avec {} et -printfentièrement.

Vous pouvez l'essayer comme ceci:

find www/*.html -type f -exec sh -c 'echo $(basename $1)' find-sh {} \;

Le résumé est "Ne référencez pas {} directement à l'intérieur d'un sh -c mais passez-le à la place à sh -c comme argument, alors vous pouvez le référencer avec une variable numérique à l'intérieur de sh -c" le find-sh est juste là comme un mannequin pour prendre le $0, il y a plus d'utilité à le faire de cette façon et à utiliser {} pour $1.

Je suppose que l'utilisation de echo est vraiment pour simplifier le concept et la fonction de test. Il existe des moyens plus simples de faire écho simplement comme d'autres l'ont mentionné, mais un cas d'utilisation idéal pour ce scénario pourrait être d'utiliser cp, mv, ou toute commande plus complexe où vous souhaitez référencer le fichier trouvé noms plusieurs fois dans la commande et vous devez vous débarrasser du chemin, par exemple. lorsque vous devez spécifier le nom de fichier dans la source et la destination ou si vous renommez des choses.

Ainsi, par exemple, si vous souhaitez copier uniquement les documents html dans votre public_html répertoire (Pourquoi? car Exemple!) alors vous pourriez:

 find www/*.html -type f -exec sh -c 'cp /var/www/$(basename $1) /home/me/public_html/$(basename $1)' find-sh {} \;

Sur unix stackexchange, la réponse du joker de l'utilisateur sur le bouclage avec find va dans quelques grands joyaux sur l'utilisation de -exec et sh -c. (Vous pouvez le trouver ici: https://unix.stackexchange.com/questions/321697/why-is-looping-over-finds-output-bad-practice )

0
Sheldon