Question bouclage à travers les résultats de `ls` dans le script shell bash


Quelqu'un at-il un script shell modèle pour faire quelque chose avec ls pour une liste de noms de répertoires et une boucle à travers chacun et faire quelque chose?

Je prévois de faire ls -1d */ pour obtenir la liste des noms de répertoires.


71
2017-08-28 17:03


origine




Réponses:


Édité pour ne pas utiliser ls où un glob ferait, comme @ shawn-j-goff et d'autres suggéré.

Il suffit d'utiliser un for..do..done boucle:

for f in *; do
  echo "File -> $f"
done

Vous pouvez remplacer le * avec *.txt ou tout autre glob qui renvoie une liste (de fichiers, répertoires ou autre), une commande qui génère une liste, par exemple, $(cat filelist.txt), ou le remplacer par une liste.

Dans le do loop, vous faites juste référence à la variable de boucle avec le préfixe du signe dollar (donc $f dans l'exemple ci-dessus). Vous pouvez echo le faire ou faire autre chose que vous voulez.

Par exemple, pour renommer tous les .xml fichiers dans le répertoire en cours à .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

Ou mieux encore, si vous utilisez Bash, vous pouvez utiliser les extensions de paramètres Bash plutôt que de créer un sous-shell:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done

72
2017-08-28 17:09



Et si le nom du fichier contient un espace? - Daniel A. White
Malheureusement, comme dit Daniel, le code dans cette réponse sera rompu si l'un des fichiers ou des dossiers contient un espace ou une nouvelle ligne dans leur nom. Il montre un abus très courant de for boucles, et un piège typique d'essayer d'analyser la sortie de ls. @ DanielA.White, vous pourrait envisagez de ne pas accepter cette réponse si elle n'était pas utile (ou potentiellement trompeuse), puisque, comme vous l'avez dit, vous agissez sur des répertoires. La réponse de Shawn J. Goff devrait apporter une solution plus solide et plus efficace à votre problème. - slhck
-∞ Vous ne devriez pas analyser ls sortie, vous ne devriez pas lire la sortie dans un for boucle, Tu devrais utiliser $() au lieu de `` et Utilisez plus de devis. Je suis sûr que @CoverosGene a bien signifié, mais c'est tout simplement terrible. - l0b0
Une meilleure alternative si vous voulez vraiment utiliser ls est ls -1 | while read line; do stuff; done. Au moins, on ne cassera pas pour les blancs. - Emmanuel Joubaud
avec mv -v tu n'as pas besoin echo "moved $x -> $t" - DmitrySandalov


Utiliser la sortie de ls obtenir des noms de fichiers est une mauvaise idée. Cela peut entraîner des dysfonctionnements et même des scripts dangereux. C'est parce qu'un nom de fichier peut contenir n'importe quel caractère sauf / et le nullcaractère, et ls n'utilise aucun de ces caractères comme délimiteurs, donc si un nom de fichier a un espace ou une nouvelle ligne, vous volonté obtenir des résultats inattendus.

Il existe deux très bonnes façons d’itérer les fichiers. Ici, j'ai utilisé simplement echo pour démontrer faire quelque chose avec le nom de fichier; vous pouvez toutefois utiliser n'importe quoi.

La première consiste à utiliser les fonctions de globalisation natives du shell.

for dir in */; do
  echo "$dir"
done

La coquille se dilate */ dans des arguments distincts que le for lectures en boucle; même s'il y a un espace, une nouvelle ligne ou tout autre caractère étrange dans le nom du fichier, for verra chaque nom complet comme une unité atomique; ce n'est pas l'analyse de la liste en aucune façon.

Si vous voulez aller récursivement dans les sous-répertoires, cela ne fonctionnera pas à moins que votre shell ait des fonctionnalités de globalisation étendues (telles que bash's globstar. Si votre shell n’a pas ces fonctionnalités, ou si vous voulez vous assurer que votre script fonctionnera sur divers systèmes, la prochaine option est d’utiliser find.

find . -type d -exec echo '{}' \;

Ici le find commande appellera echo et lui passer un argument du nom de fichier. Il le fait une fois pour chaque fichier trouvé. Comme dans l'exemple précédent, il n'y a pas d'analyse syntaxique d'une liste de noms de fichiers. au lieu de cela, un fichier est envoyé complètement comme argument.

La syntaxe de la -exec l'argument est un peu drôle. find prend le premier argument après -exec et traite cela comme le programme à exécuter, et chaque argument suivant, cela prend comme argument pour passer à ce programme. Il y a deux arguments spéciaux que -exec a besoin de voir. Le premier est {}; cet argument est remplacé par un nom de fichier que les parties précédentes de find génère. Le second est ;qui laisse findsachez que c'est la fin de la liste des arguments à transmettre au programme; find a besoin de cela parce que vous pouvez continuer avec plus d'arguments qui sont destinés à find et non destiné au programme exec'd. La raison de la \ est que la coquille traite aussi ; spécialement - il représente la fin d'une commande, nous devons donc y échapper pour que le shell le donne comme argument à find plutôt que de le consommer pour lui-même; Une autre façon de faire en sorte que le shell ne le traite pas spécialement est de le mettre entre guillemets: ';' fonctionne aussi bien que \; dans ce but.


62
2018-04-29 22:16



+1 c'est certainement la voie à suivre lorsque vous devez générer une liste de fichiers et l'utiliser dans une commande. find -exec est limité par le fait de ne pouvoir exécuter qu'une seule commande. Avec la boucle, vous pouvez diriger le contenu de votre cœur. - MaQleod
+1. Je souhaite que plus de gens utilisent find. Il y a de la magie qui peut être faite, et même les limites perçues de -exec peut être travaillé autour. le -print0 l'option est également utile pour une utilisation avec xargs. - ghoti


Pour les fichiers avec des espaces, vous devrez vous assurer de citer la variable comme:

 for i in $(ls); do echo "$i"; done; 

ou, vous pouvez modifier la variable d'environnement IFS (Input Field Separator):

 IFS=$'\n';for file in $(ls); do echo $i; done

Enfin, selon ce que vous faites, vous n’avez peut-être même pas besoin du ls:

 for i in *; do echo "$i"; done;

7
2017-08-28 17:26



bon usage d'un sous-shell dans le premier exemple - Jeremy L
Pourquoi IFS a-t-il besoin d'un $ après l'affectation et avant le nouveau personnage? - Andy Ibanez
Les noms de fichiers peuvent également contenir des nouvelles lignes. Briser \n Est insuffisant. Il n’est jamais une bonne idée de recommander d’analyser le résultat de ls. - ghoti


Si vous avez GNU Parallel http://www.gnu.org/software/parallel/ installé, vous pouvez le faire:

ls | parallel echo {} is in this dir

Pour renommer tous les .txt en .xml:

ls *.txt | parallel mv {} {.}.xml

Regardez la vidéo d'introduction pour GNU Parallel pour en savoir plus: http://www.youtube.com/watch?v=OpaiGYxkSuQ


5
2017-08-22 20:02





Pour ajouter à la réponse de CoverosGene, voici un moyen de lister uniquement les noms de répertoires:

for f in */; do
  echo "Directory -> $f"
done

4
2018-02-11 14:36





Pourquoi ne pas définir IFS sur une nouvelle ligne, puis capturer la sortie de ls dans un tableau? Définir IFS sur newline devrait résoudre les problèmes avec des caractères amusants dans les noms de fichiers; en utilisant ls peut être agréable car il dispose d'une fonction de tri intégrée.

(En testant, je rencontrais des difficultés pour configurer IFS \n mais en le définissant sur newline backspace works, comme suggéré ici ailleurs):

Par exemple. (en supposant désiré ls motif de recherche passé $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Ceci est particulièrement pratique sur OS X, par exemple, pour capturer une liste de fichiers triés par date de création (du plus ancien au plus récent), le ls commande est ls -t -U -r.


1
2018-05-10 22:36



Les noms de fichiers peuvent également contenir des nouvelles lignes, ce qu’ils font souvent lorsque les utilisateurs sont autorisés à nommer leurs propres fichiers. Briser \n Est insuffisant. Les seules solutions valides utilisent une boucle for avec extension de chemin, ou find. - ghoti
Le seul moyen fiable de transférer une liste de noms de fichiers est de les séparer avec un caractère NUL, car c'est le seul qui ne soit pas contenu dans un chemin de fichier. - glglgl


C'est comme ça que je le fais, mais il y a probablement des moyens plus efficaces.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

-3
2017-08-28 17:28



S'en tenir aux tuyaux à la place du fichier:>ls | while read i; do echo filename: $i; done - Jeremy L
Cool. Je devrais dire que vous pouvez aussi utiliser $ EDITOR filelist.txt entre les deux commandes. Beaucoup de choses que vous pouvez faire dans un éditeur plus facile que sur la ligne de commande. Pas pertinent pour cette question, cependant. - TREE
Votre solution ne résout pas du tout le problème des noms de fichiers contenant des lignes nouvelles et d’autres éléments sophistiqués. - glglgl