I caratteri jolly ricorsivi in ​​GNU fanno?


93

È passato un po 'di tempo da quando l'ho usato make, quindi abbi pazienza ...

Ho una directory flac, contenente i file .FLAC. Ho una directory corrispondente, mp3contenente file MP3. Se un file FLAC è più recente del file MP3 corrispondente (o il file MP3 corrispondente non esiste), allora voglio eseguire una serie di comandi per convertire il file FLAC in un file MP3 e copiare i tag.

Il kicker: devo cercare la flacdirectory in modo ricorsivo e creare le sottodirectory corrispondenti nella mp3directory. Le directory e i file possono avere spazi nei nomi e sono denominati in UTF-8.

E voglio usarlo makeper guidare questo.


1
Qualche motivo per scegliere la marca per questo scopo? Avrei pensato che scrivere un copione bash sarebbe stato più semplice

4
@Neil, il concetto di make come trasformazione del file system basata su pattern è il modo migliore per affrontare il problema originale. Forse le implementazioni di questo approccio hanno i suoi limiti, ma makeè più vicino all'implementazione che alla nuda bash.
P Shved

1
@Pavel Bene, uno shscript che passa attraverso l'elenco dei file flac ( find | while read flacname), ne fa un mp3name, esegue "mkdir -p" su dirname "$mp3name", e poi si if [ "$flacfile" -nt "$mp3file"]converte "$flacname"in "$mp3name"non è veramente magico. L'unica caratteristica che stai effettivamente perdendo rispetto a una makesoluzione basata è la possibilità di eseguire Nprocessi di conversione dei file in parallelo con make -jN.
ndim

4
@ndim È la prima volta che sento la sintassi di make essere descritta come "carina" :-)

1
L'utilizzo makee la presenza di spazi nei nomi dei file sono requisiti contraddittori. Utilizzare uno strumento appropriato per il dominio del problema.
Jens

Risposte:


117

Proverei qualcosa in questo senso

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 "$@"

Un paio di brevi note per i makeprincipianti:

  • La parte @anteriore dei comandi impedisce makedi stampare il comando prima di eseguirlo effettivamente.
  • $(@D)è la parte della directory del nome del file di destinazione ( $@)
  • Assicurati che le righe con i comandi della shell inizino con una tabulazione, non con spazi.

Anche se questo dovrebbe gestire tutti i caratteri UTF-8 e tutto il resto, fallirà negli spazi nei nomi di file o directory, poiché makeutilizza spazi per separare le cose nei makefile e non sono a conoscenza di un modo per aggirare questo problema . Quindi questo ti lascia solo con uno script di shell, temo: - /


Qui è dove stavo andando ... dita veloci per la vittoria. Anche se sembra che tu possa fare qualcosa di intelligente con vpath. Devo studiarlo uno di questi giorni.
dmckee --- gattino ex moderatore

1
Non sembra funzionare quando le directory hanno spazi nei nomi.
Roger Lipscombe

Non sapevo che avrei dovuto sborsare findper ottenere i nomi in modo ricorsivo ...
Roger Lipscombe

1
@PaulKonova: corri make -jN. Per Nutilizzare il numero di conversioni che makedovrebbero essere eseguite in parallelo. Attenzione: l'esecuzione make -jsenza una Navvia tutti i processi di conversione contemporaneamente in parallelo, il che potrebbe essere equivalente a una fork bomb.
ndim

1
@Adrian: La .PHONY: allriga dice a make che la ricetta per il alltarget deve essere eseguita anche se c'è un file chiamato allpiù recente di tutti i $(MP3_FILES).
ndim

53

Puoi definire la tua funzione jolly ricorsiva in questo modo:

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

Il primo parametro ( $1) è un elenco di directory e il secondo ( $2) è un elenco di modelli che desideri abbinare.

Esempi:

Per trovare tutti i file C nella directory corrente:

$(call rwildcard,.,*.c)

Per trovare tutti i file .ce .hin src:

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

Questa funzione si basa sull'implementazione di questo articolo , con alcuni miglioramenti.


Questo non sembra funzionare per me. Ho copiato la funzione esatta e ancora non verrà visualizzata in modo ricorsivo.
Jeroen

3
Sto usando GNU Make 3.81 e sembra funzionare per me. Tuttavia, non funzionerà se uno qualsiasi dei nomi di file contiene spazi. Nota che i nomi dei file che restituisce hanno percorsi relativi alla directory corrente, anche se stai solo elencando i file in una sottodirectory.
Larskholte

2
Questo è davvero un esempio, che makeè un Turing Tar Pit (vedi qui: yosefk.com/blog/fun-at-the-turing-tar-pit.html ). Non è nemmeno così difficile, ma bisogna leggere questo: gnu.org/software/make/manual/html_node/Call-Function.html e poi "capire la ricorrenza". TU dovevi scriverlo ricorsivamente, nel senso letterale; non è la comprensione quotidiana di "includere automaticamente cose da sottodirectory". È l'effettiva RICORRENZA. Ma ricorda: "Per capire la ricorrenza, devi capire la ricorrenza".
Tomasz Gandor

@TomaszGandor Non devi capire la ricorrenza. Devi capire la ricorsione e per farlo devi prima capire la ricorsione.
user1129682

Colpa mia, mi sono innamorata di un falso amico linguistico. I commenti non possono essere modificati dopo così tanto tempo, ma spero che tutti abbiano capito il punto. E comprendono anche la ricorsione.
Tomasz Gandor

2

FWIW, ho usato qualcosa di simile in un Makefile :

RECURSIVE_MANIFEST = `find . -type f -print`

L'esempio sopra cercherà dalla directory corrente ( '.' ) Tutti i "file semplici" ( '-type f' ) e imposterà la RECURSIVE_MANIFESTvariabile make su ogni file che trova. È quindi possibile utilizzare sostituzioni di pattern per ridurre questo elenco o, in alternativa, fornire più argomenti in find per restringere ciò che restituisce. Vedere la pagina man per trovare .


1

La mia soluzione si basa su quella sopra, usa sedinvece di patsubstmanipolare l'output di findAND sfuggire agli spazi.

Passando da flac / a ogg /

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

Avvertenze:

  1. Ancora barf se ci sono punti e virgola nel nome del file, ma sono piuttosto rari.
  2. Il trucco $ (@ D) non funzionerà (produce parole incomprensibili), ma oggenc crea le directory per te!

1

Ecco uno script Python che ho rapidamente modificato insieme per risolvere il problema originale: conservare una copia compressa di una libreria musicale. Lo script convertirà i file .m4a (presumibilmente ALAC) in formato AAC, a meno che il file AAC non esista già e sia più recente del file ALAC. I file MP3 nella libreria verranno collegati, poiché sono già compressi.

Fai solo attenzione che interrompendo lo script ( ctrl-c) lascerai un file convertito a metà.

Inizialmente volevo anche scrivere un Makefile per gestirlo, ma poiché non può gestire gli spazi nei nomi dei file (vedi la risposta accettata) e poiché la scrittura di uno script bash è garantito per mettermi in un mondo di dolore, Python lo è. È abbastanza semplice e breve e quindi dovrebbe essere facile da modificare in base alle tue esigenze.

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])

Naturalmente, questo può essere migliorato per eseguire la codifica in parallelo. Questo è lasciato come esercizio al lettore ;-)


0

Se stai usando Bash 4.x, puoi usare una nuova opzione di globbing , ad esempio:

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

Questo tipo di carattere jolly ricorsivo può trovare tutti i file di tuo interesse ( .flac , .mp3 o altro). O


1
Per me, anche solo $ (wildcard flac / ** / *. Flac) sembra funzionare. OS X, Gnu Make 3.81
akauppi

2
Ho provato $ (carattere jolly ./**/*.py) e si è comportato allo stesso modo di $ (carattere jolly ./*/*.py). Non penso che make supporti effettivamente **, e semplicemente non fallisce quando usi due * uno accanto all'altro.
lahwran

@lahwran Dovrebbe quando si invocano comandi tramite la shell Bash e si abilita l' opzione globstar . Forse non stai usando GNU make o qualcos'altro. Puoi anche provare questa sintassi . Controlla i commenti per alcuni suggerimenti. Altrimenti è una cosa per la nuova domanda.
Kenorb

@kenorb no no, non ho nemmeno provato la tua cosa perché volevo evitare l'invocazione della shell per questa cosa particolare. Stavo usando la cosa suggerita da akauppi. La cosa con cui sono andato sembrava la risposta di Larskholte, anche se l'ho presa da qualche altra parte perché i commenti qui dicevano che questa era sottilmente rotta. scrollata di spalle :)
lahwran

@lahwran In questo caso **non funzionerà, perché il globbing esteso è una cosa bash / zsh.
kenorb
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.