Je travaille sur un script qui doit effectuer une action dans chaque sous-répertoire d'un dossier spécifique.
Quelle est la façon la plus efficace d'écrire cela?
bash
command
directory-traversal
mikewilliamson
la source
la source
mkdir 'foo * bar'
causerafoo
etbar
sera réitéré même si ils n'existent pas et*
seront remplacés par une liste de tous les noms de fichiers, même ceux qui ne sont pas des répertoires).mkdir -p '/tmp/ /etc/passwd /'
, si quelqu'un s'exécute - si quelqu'un exécute un script suivant cette pratique/tmp
pour, par exemple, trouver des répertoires à supprimer, il pourrait finir par les supprimer/etc/passwd
.Réponses:
la source
find
paramètres si vous souhaitez un comportement récursif ou non récursif.find . -mindepth 1 -type d
mkdir 'directory name with spaces'
en quatre mots distincts.-mindepth 1 -maxdepth 1
ou c'est allé trop profondément.Une version qui évite de créer un sous-processus:
Ou, si votre action est une commande unique, c'est plus concis:
Ou une version encore plus concise ( merci @enzotib ). Notez que dans cette version, chaque valeur de
D
aura une barre oblique de fin:la source
if
ou[
avec:for D in */; do
for D in *; do [ -d "${D}" ] && my_command; done
ou une combinaison des deux dernières:for D in */; do [ -d $D ] && my_command; done
for D in .* *; do
plutôtfor D in *; do
.La manière non récursive la plus simple est:
À
/
la fin indique, utilisez uniquement les répertoires.Il n'y a pas besoin de
la source
shopt -s dotglob
pour inclure des fichiers / dotdirs lors de l'extension des caractères génériques. Voir aussi: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html/*
lieu de*/
avec/
représentant le chemin que vous souhaitez utiliser./*
serait pour le chemin absolu alors*/
qu'il inclurait les sous-répertoires de l'emplacement actuel${d%/*}
Utilisez la
find
commande.Dans GNU
find
, vous pouvez utiliser le-execdir
paramètre:ou en utilisant le
-exec
paramètre:ou avec
xargs
commande:Ou en utilisant pour la boucle:
Pour la récursivité, essayez
**/
plutôt le globbing étendu ( ) (activer par :)shopt -s extglob
.Pour plus d'exemples, voir: Comment accéder à chaque répertoire et exécuter une commande? à SO
la source
-exec {} +
est spécifié par POSIX,-exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +
est une autre option légale et appelle moins de shells que-exec sh -c '...' {} \;
.Doublures pratiques
L'option A est correcte pour les dossiers avec des espaces entre les deux. En outre, généralement plus rapide car il n'imprime pas chaque mot d'un nom de dossier en tant qu'entité distincte.
la source
find . -type d -print0 | xargs -0 -n 1 my_command
la source
my_command
.Cela créera un sous-shell (ce qui signifie que les valeurs des variables seront perdues à la fin de la
while
boucle):Cela ne va pas:
L'un ou l'autre fonctionnera s'il y a des espaces dans les noms de répertoire.
la source
find ... -print0
etwhile IFS="" read -r -d $'\000' dir
-d ''
c'est moins trompeur sur la syntaxe et les capacités de bash, car cela-d $'\000'
implique (faussement) qui$'\000'
est en quelque sorte différent de''
- en effet, on pourrait facilement (et encore, faussement) déduire de il que bash prend en charge les chaînes de style Pascal (longueur spécifiée, capable de contenir des littéraux NUL) plutôt que les chaînes C (délimitées NUL, incapable de contenir des NUL).Tu pourrais essayer:
ou similaire...
Explication:
find . -maxdepth 1 -mindepth 1 -type d
: Trouver uniquement les répertoires avec une profondeur récursive maximale de 1 (uniquement les sous-répertoires de 1 $) et une profondeur minimale de 1 (exclut le dossier actuel.
)la source
cd "$f"
. Cela ne fonctionne pas lorsque la sortie defind
est divisée en chaînes, vous aurez donc les pièces séparées du nom en tant que valeurs distinctes dans$f
, ce qui améliore la façon dont vous faites ou ne citez pas$f
l'expansion de moot.find
La sortie de est orientée ligne (un nom sur une ligne, en théorie - mais voir ci-dessous) avec l'-print
action par défaut .find -print
n'est pas un moyen sûr de passer des noms de fichiers arbitraires, car on peut exécuter quelque chosemkdir -p $'foo\n/etc/passwd\nbar'
et obtenir un répertoire qui a/etc/passwd
une ligne distincte dans son nom. Gérer les noms des fichiers/upload
ou des/tmp
répertoires sans précaution est un excellent moyen d'obtenir des attaques par élévation de privilèges.la réponse acceptée se brisera sur les espaces blancs si les noms de répertoire les ont, et la syntaxe préférée est
$()
pour bash / ksh. Utilisez l'GNU find
-exec
option avec+;
par exemplefind .... -exec mycommand +;
#this is same as passing to xargs
ou utilisez une boucle while
la source
find -exec
et passant àxargs
:find
ignorera la valeur de sortie de la commande en cours d'exécution, tandisxargs
qu'échouera sur une sortie différente de zéro. Les deux peuvent être corrects, selon vos besoins.find ... -print0 | while IFS= read -r d
est plus sûr - prend en charge les noms qui commencent ou se terminent par des espaces, et les noms qui contiennent des littéraux de nouvelle ligne.