Comment faire une boucle sur des fichiers dans le répertoire et changer le chemin et ajouter un suffixe au nom de fichier

563

J'ai besoin d'écrire un script qui démarre mon programme avec différents arguments, mais je suis nouveau sur Bash. Je commence mon programme avec:

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt].

Voici le pseudocode de ce que je veux faire:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end for
end for

Je suis donc vraiment perplexe sur la façon de créer un deuxième argument à partir du premier, il ressemble donc à dataABCD_Log1.txt et démarre mon programme.

Dobrobobr
la source

Réponses:

739

Quelques notes d'abord: lorsque vous utilisez Data/data1.txtcomme argument, cela devrait-il vraiment être /Data/data1.txt(avec une barre oblique)? En outre, la boucle externe doit-elle analyser uniquement les fichiers .txt ou tous les fichiers dans / Data? Voici une réponse, en supposant /Data/data1.txtque les fichiers .txt:

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

Remarques:

  • /Data/*.txts'étend aux chemins des fichiers texte dans / Data ( y compris la partie / Data /)
  • $( ... ) exécute une commande shell et insère sa sortie à ce point de la ligne de commande
  • basename somepath .txtaffiche la partie de base de somepath, avec .txt supprimé de la fin (par exemple /Data/file.txt-> file)

Si vous devez exécuter MyProgram avec Data/file.txtau lieu de /Data/file.txt, utilisez "${filename#/}"pour supprimer la barre oblique principale. D'un autre côté, si ce n'est vraiment Datapas /Datavous voulez scanner, utilisez simplement for filename in Data/*.txt.

Gordon Davisson
la source
1
Apparemment, basename -sc'est une extension non standard - je vais modifier ma réponse pour utiliser la syntaxe standard.
Gordon Davisson
21
Si aucun fichier n'est trouvé / correspond au caractère générique, je trouve que le bloc d'exécution des boucles for est toujours entré une fois avec filename = "/Data/*.txt". Comment puis-je éviter ça?
Oliver Pearmain
20
@OliverPearmain Soit utiliser shopt -s nullglobavant la boucle (et shopt -u nullglobaprès pour éviter les problèmes plus tard), soit ajouter if [[ ! -e "$filename ]]; then continue; fiau début de la boucle, donc il sautera les fichiers inexistants.
Gordon Davisson
9
Cela ne fonctionne pas lorsqu'il existe des fichiers contenant des espaces dans leur nom.
Isa Hassen
4
@Isa Cela devrait fonctionner avec des espaces, tant que tous les guillemets doubles sont en place. Laissez les guillemets doubles et vous aurez des problèmes avec les espaces.
Gordon Davisson
345

Désolé pour le nécromancement du thread, mais chaque fois que vous parcourez des fichiers en globulant, il est recommandé d'éviter le cas d'angle où le glob ne correspond pas (ce qui fait que la variable de boucle se développe jusqu'à la chaîne de motif de glob (non-correspondante) elle-même).

Par exemple:

for filename in Data/*.txt; do
    [ -e "$filename" ] || continue
    # ... rest of the loop body
done

Référence: Bash Pitfalls

Cong Ma
la source
8
Il s'agit toujours d'un avertissement opportun. Je pensais que j'avais créé mon script de manière incorrecte, mais j'avais mon extension de fichier en minuscules au lieu de majuscules, il n'a trouvé aucun fichier et a renvoyé le modèle global. Pouah.
RufusVS
5
Puisque la balise Bash est utilisée: c'est pour ça shopt nullglob! (ou shopt failglobpeut également être utilisé, selon le comportement que vous souhaitez).
gniourf_gniourf
En outre, cela vérifie également les fichiers supprimés pendant le traitement, avant que la boucle ne les atteigne.
loxaxs
1
dir.txt
Gère
75
for file in Data/*.txt
do
    for ((i = 0; i < 3; i++))
    do
        name=${file##*/}
        base=${name%.txt}
        ./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
    done
done

La name=${file##*/}substitution ( expansion des paramètres du shell ) supprime le chemin d'accès principal jusqu'au dernier /.

La base=${name%.txt}substitution supprime le suivi .txt. C'est un peu plus compliqué si les extensions peuvent varier.

Jonathan Leffler
la source
3
Je crois qu'il y a une erreur dans votre code. La seule ligne devrait être base=${name%.txt}, au lieu de base=${base%.txt}.
caseklim
4
@CaseyKlimkowsky: Oui; lorsque le code et les commentaires sont en désaccord, au moins l'un d'entre eux est erroné. Dans ce cas, je pense que ce n'est que l'un - le code; souvent, ce sont en fait les deux qui ont tort. Merci d'avoir fait remarquer cela; Je l'ai corrigé.
Jonathan Leffler
8

Vous pouvez utiliser l'option de sortie séparée par des résultats avec lecture pour parcourir les structures de répertoires en toute sécurité.

#!/bin/bash
find . -type f -print0 | while IFS= read -r -d $'\0' file; 
  do echo "$file" ;
done

Donc pour votre cas

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done

aditionellement

#!/bin/bash
while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done < <(find . -maxdepth 1 -type f  -print0)

exécutera la boucle while dans la portée actuelle du script (processus) et autorisera la sortie de find à définir des variables si nécessaire

James
la source
2
$'\0'est une façon étrange d'écrire ''. Vous êtes absent IFS=et le -rpassage à read: votre relevé de lecture doit être: IFS= read -rd '' file.
gniourf_gniourf
Je pense que certains auraient besoin de rechercher $'\0'et de répartir des points de pile autour. Je vais apporter les modifications que vous avez signalées. Quels sont les effets néfastes de ne pas y avoir IFS=essayé echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; donene semblent pas en avoir. Aussi -r je vois est souvent par défaut, mais n'a pas pu trouver d'exemple pour ce qu'il empêche de se produire.
James
4
IFS=est nécessaire dans le cas où un nom de fichier se termine par un espace: essayez est avec touch 'Prospero '(notez l'espace de fin). Vous avez également besoin du -rcommutateur dans le cas où un nom de fichier a une barre oblique inverse: essayez-le avec touch 'Prospero\n'.
gniourf_gniourf
-1

On dirait que vous essayez d'exécuter un fichier Windows (.exe) Vous devriez sûrement utiliser PowerShell. Quoi qu'il en soit, sur un shell bash Linux, une simple ligne suffit.

[/home/$] for filename in /Data/*.txt; do for i in {0..3}; do ./MyProgam.exe  Data/filenameLogs/$filename_log$i.txt; done done

Ou dans un bash

#!/bin/bash

for filename in /Data/*.txt; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done
user3183111
la source