GNU make'deki yinelemeli joker karakterler?


93

Kullandığımdan beri epey zaman geçti make, bu yüzden bana katlanın ...

Ben, bir dizin var flac.flac dosyalarını içeren,. mp3MP3 dosyalarını içeren ilgili bir dizinim var . Bir FLAC dosyası karşılık gelen MP3 dosyasından daha yeniyse (veya karşılık gelen MP3 dosyası yoksa), FLAC dosyasını bir MP3 dosyasına dönüştürmek için bir dizi komut çalıştırmak ve etiketleri kopyalamak istiyorum.

Kicker: flacDizini yinelemeli olarak aramam ve dizinde ilgili alt dizinler oluşturmam gerekiyor mp3. Dizinler ve dosyalar adlarında boşluklar olabilir ve bunlar UTF-8 olarak adlandırılır.

Ve bunu makesürmek için kullanmak istiyorum .


1
Bu amaçla make'i seçmek için herhangi bir neden var mı? Bir bash senaryosu yazmanın daha kolay olacağını düşünmüştüm

4
@Neil, make'in desen tabanlı dosya sistemi dönüşümü konsepti, orijinal soruna yaklaşmanın en iyi yoludur. Belki de bu yaklaşımın uygulamalarının sınırlamaları vardır, ancak makeonu uygulamaya çıplak olmaktan daha yakındır bash.
P Shved

1
@Pavel Eh, bir shflac dosyaları (listesinde yürür senaryo find | while read flacname), bir yapar mp3nameböyle olduğunu, "mkdir -p" çalıştırır üzerine dirname "$mp3name", daha sonra, ve if [ "$flacfile" -nt "$mp3file"]dönüştürür "$flacname"içine "$mp3name"gerçekten sihir değil. Temel bir makeçözüme kıyasla gerçekten kaybettiğiniz tek özellik , Ndosya dönüştürme işlemlerini paralel olarak çalıştırma olanağıdır make -jN.
ndim

4
@ndim Bu, make'in sözdiziminin "güzel" olarak tanımlanacağını ilk kez duydum :-)

1
makeDosya adlarında boşluk kullanmak ve bunlara sahip olmak çelişkili gereksinimlerdir. Sorunlu etki alanına uygun bir araç kullanın.
Jens

Yanıtlar:


117

Bu çizgiler boyunca bir şeyler denerdim

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

makeYeni başlayanlar için birkaç kısa not :

  • @Komutlar önler önünde makeaslında çalıştırmadan önce komutunu yazdırmasını.
  • $(@D)hedef dosya adının dizin kısmıdır ( $@)
  • İçlerinde kabuk komutları olan satırların boşluklarla değil, bir sekmeyle başladığından emin olun.

Bu, tüm UTF-8 karakterlerini ve malzemelerini işlemesi gerekse bile, dosya veya dizin adlarındaki boşluklarda başarısız olur, çünkü makemakefile dosyalarındaki öğeleri ayırmak için boşluklar kullanır ve bu konuda çalışmanın bir yolunu bilmiyorum. Bu sizi sadece bir kabuk betiğiyle bırakıyor, korkarım: - /


İşte oraya gidiyordum ... kazanmak için hızlı parmaklar. Zekice bir şeyler yapabileceğin gibi görünse de vpath. Bunu bugünlerde incelemelisin.
dmckee --- eski moderatör yavru kedi

1
Dizinlerin adlarında boşluk olduğunda çalışmıyor gibi görünüyor.
Roger Lipscombe

findİsimleri yinelemeli olarak almak için para harcamam gerekeceğini fark etmemiştim ...
Roger Lipscombe

1
@PaulKonova: Çalıştır make -jN. İçin Ndönüşüm sayısını kullanmak makeparalel çalışmalıdır. Dikkat: Bir çatal bombası make -jolmadan Nçalıştırmak tüm dönüştürme işlemlerini paralel olarak başlatır ve bu da çatal bombasına eşdeğer olabilir.
ndim

1
@Adrian: .PHONY: allSatır, hepsinden daha yeni alladlı bir dosya olsa bile hedef için tarifin yürütüleceğini söyler . all$(MP3_FILES)
ndim

53

Kendi yinelemeli joker karakter işlevinizi şu şekilde tanımlayabilirsiniz:

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

İlk parametre ( $1) dizinlerin bir listesidir ve ikinci ( $2) eşleştirmek istediğiniz kalıpların bir listesidir.

Örnekler:

Geçerli dizindeki tüm C dosyalarını bulmak için:

$(call rwildcard,.,*.c)

Tüm .cve .hdosyaları içinde bulmak için src:

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

Bu işlev, birkaç iyileştirmeyle birlikte bu makaledeki uygulamaya dayanmaktadır .


Bu benim için işe yaramıyor gibi görünüyor. Tam işlevi kopyaladım ve hala yinelemeli görünmeyecek.
Jeroen

3
GNU Make 3.81 kullanıyorum ve benim için çalışıyor gibi görünüyor. Yine de dosya adlarından herhangi birinde boşluk varsa çalışmaz. Yalnızca bir alt dizindeki dosyaları listeliyor olsanız bile, döndürdüğü dosya adlarının geçerli dizine göre yollara sahip olduğuna dikkat edin.
larskholte

2
Bu gerçekten bir örnek, bu makebir Turing Katran Çukuru (buraya bakın: yosefk.com/blog/fun-at-the-turing-tar-pit.html ). O kadar da zor değil, ama kişi şunu okumalı: gnu.org/software/make/manual/html_node/Call-Function.html ve sonra "tekrarlamayı anla". Bunu kelimesi kelimesine tekrar tekrar yazmak zorundaydınız; bu, "alt dizinlerden öğeleri otomatik olarak dahil et" kavramının günlük anlayışı değildir. Bu gerçek RECURRENCE. Ancak unutmayın - "Yinelemeyi anlamak için yinelemeyi anlamanız gerekir".
Tomasz Gandor

@TomaszGandor Yinelemeyi anlamak zorunda değilsiniz. Özyinelemeyi anlamalısınız ve bunu yapmak için önce özyinelemeyi anlamalısınız.
user1129682

Benim hatam, dilsel yanlış bir arkadaşa aşık oldum. Yorumlar bu kadar uzun bir süre sonra düzenlenemez, ancak umarım herkes anladı. Ayrıca özyinelemeyi de anlarlar.
Tomasz Gandor

2

FWIW, Makefile'da buna benzer bir şey kullandım :

RECURSIVE_MANIFEST = `find . -type f -print`

Yukarıdaki örnek, mevcut dizinden ( '.' ) Tüm "düz dosyalar" ( '-tipi f' ) için arama RECURSIVE_MANIFESTyapacak ve make değişkenini bulduğu her dosyaya ayarlayacaktır . Daha sonra içine bu listeyi azaltmak veya alternatif, daha fazla argüman tedarik desen değiştirmelerin kullanabilirsiniz find döndürür neyi daraltmak için. Bulmak için man sayfasına bakın .


1

Benim çözümüm yukarıdakine dayanıyor, AND çıktısını karıştırmak sedyerine boşluklardan kaçmak için kullanıyor.patsubstfind

Flac'tan / ogg'ye /

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

Uyarılar:

  1. Dosya adında noktalı virgül varsa hala kusuyorlar, ancak bunlar oldukça nadirdir.
  2. $ (@ D) hilesi işe yaramaz (anlamsız çıktı), ancak oggenc sizin için dizinler yaratır!

1

İşte orijinal sorunu çözmek için hızla birlikte hacklediğim bir Python betiği: bir müzik kitaplığının sıkıştırılmış bir kopyasını saklayın. Komut dosyası, AAC dosyası zaten mevcut değilse ve ALAC dosyasından daha yeni değilse .m4a dosyalarını (ALAC olduğu varsayılır) AAC biçimine dönüştürür. Kitaplıktaki MP3 dosyaları zaten sıkıştırılmış oldukları için bağlanacaktır.

Sadece script ( ctrl-c) 'i iptal etmenin yarısı dönüştürülmüş bir dosya bırakacağına dikkat edin .

Aslında bunu halletmek için bir Makefile yazmak istedim, ancak dosya adlarındaki boşlukları işleyemediği için (kabul edilen yanıta bakın) ve bir bash betiği yazmanın beni acı dolu bir dünyaya sokması garantili olduğu için Python öyle. Oldukça basit ve kısadır ve bu nedenle ihtiyaçlarınıza göre ayarlanması kolay olmalıdır.

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

Elbette bu, kodlamayı paralel olarak gerçekleştirmek için geliştirilebilir. Bu, okuyucuya bir alıştırma olarak bırakılmıştır ;-)


0

Bash 4.x kullanıyorsanız , yeni bir genelleme seçeneği kullanabilirsiniz , örneğin:

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

Bu tür özyinelemeli joker karakter, ilgilendiğiniz tüm dosyaları ( .flac , .mp3 veya her neyse) bulabilir. Ö


1
Bana göre, sadece $ (wildcard flac / ** / *. Flac) bile çalışıyor gibi görünüyor. OS X, Gnu Make 3.81
akauppi

2
$ (Wildcard ./**/*.py) denedim ve $ (wildcard ./*/*.py) ile aynı şekilde davrandım. Make'in gerçekten ** desteklediğini sanmıyorum ve yan yana iki * kullandığınızda başarısız olmuyor.
lahwran

@lahwran Bash kabuğu aracılığıyla komutları çağırdığınızda ve globstar seçeneğini etkinleştirdiğinizde gerekir . Belki GNU make veya başka bir şey kullanmıyorsunuzdur. Bunun yerine bu sözdizimini de deneyebilirsiniz . Bazı öneriler için yorumları kontrol edin. Aksi takdirde yeni soru için bir şeydir.
kenorb

@kenorb hayır hayır, senin şeyini denemedim bile çünkü bu özel şey için kabuk çağrısından kaçınmak istedim. Akauppi'nin önerdiği şeyi kullanıyordum. Gittiğim şey, Larskholte'nin cevabına benziyordu, ancak başka bir yerden aldım çünkü buradaki yorumlar bunun ince bir şekilde bozuk olduğunu söylüyordu. shrug :)
lahwran

@lahwran Bu durumda **işe yaramaz çünkü genişletilmiş globbing bir bash / zsh şeyidir .
kenorb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.