web-dev-qa-db-fra.com

Caractères génériques récursifs dans GNU faire?

Cela fait longtemps que je n'ai pas utilisé make, alors supporte-moi ...

J'ai un répertoire, flac, contenant des fichiers .FLAC. J'ai un répertoire correspondant, mp3 contenant des fichiers MP3. Si un fichier FLAC est plus récent que le fichier MP3 correspondant (ou que le fichier MP3 correspondant n'existe pas), je souhaite exécuter plusieurs commandes pour convertir le fichier FLAC en un fichier MP3 et copier les balises.

Le kicker: Je dois effectuer une recherche récursive dans le répertoire flac et créer les sous-répertoires correspondants dans le répertoire mp3. Les répertoires et les fichiers peuvent avoir des espaces dans les noms et sont nommés dans UTF-8.

Et je veux utiliser make pour conduire ceci.

73
Roger Lipscombe

Je voudrais essayer quelque chose dans ce sens

FLAC_FILES = $(Shell find flac/ -type f -name '*.flac')
MP3_FILES = $(patsubst flac/%.flac, mp3/%.mp3, $(FLAC_FILES))

.PHONY: all
all: $(MP3_FILES)

mp3/%.mp3: flac/%.flac
    @mkdir -p "$(@D)"
    @echo convert "$<" to "$@"

Quelques notes rapides pour les débutants de make:

  • Le @ devant les commandes empêche make d'imprimer la commande avant de l'exécuter.
  • $(@D) est la partie répertoire du nom du fichier cible ($@
  • Assurez-vous que les lignes contenant des commandes Shell commencent par un onglet et non par des espaces.

Même si cela doit gérer tous les caractères et tout le matériel UTF-8, les espaces dans les noms de fichiers ou de répertoires échoueront, car make utilise des espaces pour séparer les éléments dans les fichiers makefiles et je ne sais pas comment résoudre ce problème. Donc, cela ne vous laisse qu'un script Shell, j'ai bien peur: - /

100
ndim

Vous pouvez définir votre propre fonction générique récursive comme ceci:

rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))

Le premier paramètre ($1) est le nom du répertoire et le second ($2) est le modèle que vous souhaitez associer.

Exemples

Pour trouver tous les fichiers C dans le répertoire actuel:

$(call rwildcard, , *.c)

Pour trouver tous les fichiers .c et .h dans src:

$(call rwildcard, src/, *.c *.h)

La source

40
larskholte

FWIW, j'ai utilisé quelque chose comme ceci dans un Makefile :

RECURSIVE_MANIFEST = `find . -type f -print`

L'exemple ci-dessus recherche dans le répertoire en cours ( '.' ) tous les "fichiers ordinaires" ( "-type f ') et définit la variable RECURSIVE_MANIFEST make sur chaque fichier trouvé. Vous pouvez ensuite utiliser des substitutions de modèle pour réduire cette liste ou vous pouvez également fournir davantage d'arguments dans find pour limiter ce qui est renvoyé. Voir la page de manuel pour find .

2
Michael Shebanow

Ma solution est basée sur celle ci-dessus, utilise sed au lieu de patsubst pour modifier le résultat de find ET échapper aux espaces.

Aller de flac/à ogg /

OGGS = $(Shell find flac -type f -name "*.flac" | sed 's/ /\\ /g;s/flac\//ogg\//;s/\.flac/\.ogg/' )

Mises en garde:

  1. Toujours barfs s'il y a des points-virgules dans le nom du fichier, mais ils sont plutôt rares.
  2. L'astuce $ (@ D) ne fonctionnera pas (sorties charabia), mais oggenc crée des répertoires pour vous!
1
user3199485

Si vous utilisez Bash 4.x, vous pouvez utiliser une nouvelle option de globbing , par exemple:

Shell:=/bin/bash -O globstar
list:
  @echo Flac: $(Shell ls flac/**/*.flac)
  @echo MP3: $(Shell ls mp3/**/*.mp3)

Ce type de joker récursif peut trouver tous les fichiers qui vous intéressent (.flac, .mp3 ou autre). O

0
kenorb

Voici un script Python que j'ai rapidement piraté pour résoudre le problème initial: conserver une copie compressée d'une bibliothèque musicale. Le script convertira les fichiers .m4a (supposés être ALAC) au format AAC, sauf si le fichier AAC existe déjà et est plus récent que le fichier ALAC. Les fichiers MP3 de la bibliothèque seront liés, car ils sont déjà compressés.

Méfiez-vous simplement que l'avortement du script (ctrl-c) laissera un fichier à moitié converti.

Au départ, je voulais aussi écrire un Makefile pour gérer cela, mais comme il ne peut pas gérer les espaces dans les noms de fichiers (voir la réponse acceptée) et que l'écriture d'un script bash garantit de me mettre dans un monde de douleur, c'est Python. C'est assez simple et court, et devrait donc être facile à ajuster à vos besoins.

from __future__ import print_function


import glob
import os
import subprocess


UNCOMPRESSED_DIR = 'Music'
COMPRESSED = 'compressed_'

UNCOMPRESSED_EXTS = ('m4a', )   # files to convert to lossy format
LINK_EXTS = ('mp3', )           # files to link instead of convert


for root, dirs, files in os.walk(UNCOMPRESSED_DIR):
    out_root = COMPRESSED + root
    if not os.path.exists(out_root):
        os.mkdir(out_root)
    for file in files:
        file_path = os.path.join(root, file)
        file_root, ext = os.path.splitext(file_path)
        if ext[1:] in LINK_EXTS:
            if not os.path.exists(COMPRESSED + file_path):
                print('Linking {}'.format(file_path))
                link_source = os.path.relpath(file_path, out_root)
                os.symlink(link_source, COMPRESSED + file_path)
            continue
        if ext[1:] not in UNCOMPRESSED_EXTS:
            print('Skipping {}'.format(file_path))
            continue
        out_file_path = COMPRESSED + file_path
        if (os.path.exists(out_file_path)
            and os.path.getctime(out_file_path) > os.path.getctime(file_path)):
            print('Up to date: {}'.format(file_path))
            continue
        print('Converting {}'.format(file_path))
        subprocess.call(['ffmpeg', '-y', '-i', file_path,
                         '-c:a', 'libfdk_aac', '-vbr', '4',
                         out_file_path])

Bien entendu, cela peut être amélioré pour effectuer le codage en parallèle. Ceci est laissé comme exercice au lecteur ;-)

0
Brecht Machiels