Ký tự đại diện đệ quy trong GNU thực hiện?


93

Đã lâu rồi tôi không sử dụng make, vì vậy hãy chịu đựng tôi ...

Tôi có một thư mục flac, chứa các tệp .FLAC. Tôi đã có một thư mục tương ứng, mp3chứa các tệp MP3. Nếu tệp FLAC mới hơn tệp MP3 tương ứng (hoặc tệp MP3 tương ứng không tồn tại), thì tôi muốn chạy một loạt lệnh để chuyển đổi tệp FLAC thành tệp MP3 và sao chép các thẻ trên.

Người khởi xướng: Tôi cần phải tìm kiếm flacthư mục một cách đệ quy và tạo các thư mục con tương ứng trong thư mục mp3. Các thư mục và tệp có thể có khoảng trắng trong tên và được đặt tên bằng UTF-8.

Và tôi muốn sử dụng makeđể lái xe này.


1
Bất kỳ lý do để chọn thực hiện cho mục đích này? Tôi đã nghĩ việc viết một kịch bản bash sẽ đơn giản hơn

4
@Neil, khái niệm của make là chuyển đổi hệ thống tệp dựa trên mẫu là cách tốt nhất để tiếp cận vấn đề ban đầu. Có lẽ việc triển khai phương pháp này có những hạn chế của nó, nhưng makegần với việc triển khai nó hơn là trần trụi bash.
P Shved

1
@Pavel Chà, một shtập lệnh duyệt qua danh sách các tệp flac ( find | while read flacname), tạo một mp3nametừ đó, chạy "mkdir -p" trên dirname "$mp3name", và sau đó, if [ "$flacfile" -nt "$mp3file"]chuyển đổi "$flacname"thành "$mp3name"không thực sự kỳ diệu. Tính năng duy nhất mà bạn thực sự đang mất so với một makegiải pháp dựa trên là khả năng chạy Ncác quá trình chuyển đổi tệp song song với make -jN.
ndim

4
@ndim Đó là lần đầu tiên tôi nghe thấy cú pháp của make được mô tả là "hay" :-)

1
Sử dụng makevà có khoảng trắng trong tên tệp là các yêu cầu trái ngược nhau. Sử dụng một công cụ thích hợp cho miền vấn đề.
Jens

Câu trả lời:


117

Tôi sẽ thử một cái gì đó dọc theo những dòng này

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

Một số lưu ý nhanh cho makengười mới bắt đầu:

  • Mặt @trước của các lệnh ngăn không cho makein lệnh trước khi thực sự chạy nó.
  • $(@D)là phần thư mục của tên tệp đích ( $@)
  • Đảm bảo rằng các dòng có lệnh shell trong chúng bắt đầu bằng tab, không phải bằng dấu cách.

Ngay cả khi điều này sẽ xử lý tất cả các ký tự và nội dung UTF-8, nó sẽ không thành công ở khoảng trắng trong tên tệp hoặc thư mục, vì makesử dụng khoảng trắng để phân tách các nội dung trong tệp trang điểm và tôi không biết cách giải quyết vấn đề đó. Vì vậy, điều đó để lại cho bạn chỉ với một kịch bản shell, tôi sợ: - /


Đây là nơi tôi sẽ ... nhanh tay để giành chiến thắng. Mặc dù có vẻ như bạn có thể làm điều gì đó thông minh vpath. Phải nghiên cứu điều đó một trong những ngày này.
dmckee --- ex-moderator kitten

1
Dường như không hoạt động khi các thư mục có khoảng trắng trong tên.
Roger Lipscombe

Đã không nhận ra rằng tôi sẽ phải bỏ ra để findcó được tên đệ quy ...
Roger Lipscombe

1
@PaulKonova: Chạy đi make -jN. Để Nsử dụng số lượng chuyển đổi makesẽ chạy song song. Thận trọng: Việc chạy make -jmà không có dấu Nsẽ bắt đầu song song tất cả các quá trình chuyển đổi, điều này có thể tương đương với một quả bom ngã ba.
ndim

1
@Adrian: .PHONY: allDòng này cho biết rằng công thức cho allmục tiêu sẽ được thực thi ngay cả khi có một tệp được gọi là allmới hơn tất cả $(MP3_FILES).
ndim

53

Bạn có thể xác định hàm ký tự đại diện đệ quy của riêng mình như sau:

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

Tham số đầu tiên ( $1) là danh sách các thư mục và tham số thứ hai ( $2) là danh sách các mẫu bạn muốn so khớp.

Ví dụ:

Để tìm tất cả các tệp C trong thư mục hiện tại:

$(call rwildcard,.,*.c)

Để tìm tất cả .c.hcác tệp trong src:

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

Chức năng này dựa trên việc triển khai từ bài viết này , với một số cải tiến.


Điều này dường như không hiệu quả với tôi. Tôi đã sao chép hàm chính xác và nó vẫn sẽ không giống đệ quy.
Jeroen

3
Tôi đang sử dụng GNU Make 3.81 và nó có vẻ phù hợp với tôi. Tuy nhiên, nó sẽ không hoạt động nếu bất kỳ tên tệp nào có khoảng trắng trong đó. Lưu ý rằng các tên tệp mà nó trả về có đường dẫn liên quan đến thư mục hiện tại, ngay cả khi bạn chỉ liệt kê các tệp trong một thư mục con.
larskholte

2
Đây thực sự là một ví dụ, đó makelà Turing Tar Pit (xem tại đây: yosefk.com/blog/fun-at-the-turing-tar-pit.html ). Nó thậm chí không khó, nhưng người ta phải đọc điều này: gnu.org/software/make/manual/html_node/Call-Function.html và sau đó "hiểu sự lặp lại". BẠN phải viết điều này một cách đệ quy, theo nghĩa nguyên văn; nó không phải là cách hiểu hàng ngày về "tự động bao gồm nội dung từ các thứ tự con". Đó là sự QUAY LẠI thực tế. Nhưng hãy nhớ - "Để hiểu sự tái diễn, bạn phải hiểu sự tái diễn".
Tomasz Gandor

@TomaszGandor Bạn không cần phải hiểu sự tái diễn. Bạn phải hiểu đệ quy và để làm được điều đó, trước tiên bạn phải hiểu đệ quy.
user1129682

Thật tệ, tôi đã yêu một người bạn sai ngôn ngữ. Không thể chỉnh sửa nhận xét sau một thời gian dài như vậy, nhưng tôi hy vọng mọi người đều hiểu. Và họ cũng hiểu đệ quy.
Tomasz Gandor

2

FWIW, tôi đã sử dụng một cái gì đó như thế này trong Makefile :

RECURSIVE_MANIFEST = `find . -type f -print`

Ví dụ trên sẽ tìm kiếm từ thư mục hiện tại ( '.' ) Cho tất cả "tệp thuần túy" ( '-type f' ) và đặt RECURSIVE_MANIFESTbiến make thành mọi tệp mà nó tìm thấy. Sau đó, bạn có thể sử dụng các thay thế mẫu để giảm danh sách này hoặc cách khác, cung cấp thêm đối số để tìm để thu hẹp những gì nó trả về. Xem trang người đàn ông để tìm .


1

Giải pháp của tôi dựa trên giải pháp ở trên, sử dụng sedthay vì patsubstxử lý đầu ra của findAND thoát khỏi khoảng trắng.

Đi từ flac / sang ogg /

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

Lưu ý:

  1. Vẫn là barfs nếu có dấu chấm phẩy trong tên tệp, nhưng chúng khá hiếm.
  2. Thủ thuật $ (@ D) sẽ không hoạt động (đầu ra vô nghĩa), nhưng oggenc tạo thư mục cho bạn!

1

Đây là một tập lệnh Python mà tôi đã nhanh chóng hack cùng nhau để giải quyết vấn đề ban đầu: giữ một bản sao nén của thư viện nhạc. Tập lệnh sẽ chuyển đổi các tệp .m4a (giả sử là ALAC) sang định dạng AAC, trừ khi tệp AAC đã tồn tại và mới hơn tệp ALAC. Các tệp MP3 trong thư viện sẽ được liên kết, vì chúng đã được nén.

Chỉ cần lưu ý rằng việc hủy bỏ script ( ctrl-c) sẽ để lại một tệp được chuyển đổi một nửa.

Ban đầu tôi cũng muốn viết một Makefile để xử lý điều này, nhưng vì nó không thể xử lý khoảng trắng trong tên tệp (xem câu trả lời được chấp nhận) và bởi vì việc viết một tập lệnh bash được đảm bảo sẽ đưa tôi vào một thế giới đau đớn, Python là vậy. Nó khá đơn giản và ngắn gọn, do đó sẽ dễ dàng điều chỉnh theo nhu cầu của bạn.

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

Tất nhiên, điều này có thể được tăng cường để thực hiện mã hóa song song. Điều đó được để lại như một bài tập cho người đọc ;-)


0

Nếu bạn đang sử dụng Bash 4.x, bạn có thể sử dụng tùy chọn lấp lánh mới , ví dụ:

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

Loại ký tự đại diện đệ quy này có thể tìm thấy tất cả các tệp bạn quan tâm ( .flac , .mp3 hoặc bất cứ thứ gì). O


1
Đối với tôi, thậm chí chỉ $ (ký tự đại diện flac / ** / *. Flac) dường như hoạt động. OS X, Gnu Make 3,81
akauppi

2
Tôi đã thử $ (ký tự đại diện ./**/*.py) và nó hoạt động giống như $ (ký tự đại diện ./*/*.py). Tôi không nghĩ rằng make thực sự hỗ trợ **, và nó không thất bại khi bạn sử dụng hai * s bên cạnh nhau.
lahwran

@lahwran Nên khi bạn gọi lệnh qua Bash shell và bạn đã bật globstar tùy chọn. Có thể bạn không sử dụng GNU make hoặc thứ gì đó khác. Bạn cũng có thể thử cú pháp này . Kiểm tra các nhận xét để biết một số gợi ý. Nếu không, đó là một điều cho câu hỏi mới.
kenorb

@kenorb không không, tôi thậm chí còn không thử thứ của bạn vì tôi muốn tránh gọi shell cho thứ cụ thể này. Tôi đang sử dụng điều được đề xuất của akauppi. Điều tôi đi cùng trông giống như câu trả lời của larskholte, mặc dù tôi đã lấy nó từ một nơi khác vì các bình luận ở đây nói rằng câu này đã bị hỏng một cách tinh vi. nhún vai :)
lahwran

@lahwran Trong trường hợp **này sẽ không hoạt động, vì hình cầu mở rộng là một thứ bash / zsh.
kenorb
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.