อักขระตัวแทนแบบเรียกซ้ำใน GNU ทำให้?


93

เป็นเวลานานแล้วที่ฉันใช้makeดังนั้นอดทนกับฉัน ...

ฉันมีไดเร็กทอรีflacซึ่งมีไฟล์. FLAC ฉันมีไดเร็กทอรีที่เกี่ยวข้องซึ่งmp3มีไฟล์ MP3 หากไฟล์ FLAC ใหม่กว่าไฟล์ MP3 ที่เกี่ยวข้อง (หรือไม่มีไฟล์ MP3 ที่เกี่ยวข้อง) ฉันต้องการเรียกใช้คำสั่งมากมายเพื่อแปลงไฟล์ FLAC เป็นไฟล์ MP3 และคัดลอกแท็ก

นักเตะ: ฉันต้องการค้นหาflacไดเร็กทอรีซ้ำและสร้างไดเร็กทอรีย่อยที่เกี่ยวข้องในmp3ไดเร็กทอรี ไดเร็กทอรีและไฟล์สามารถมีช่องว่างในชื่อและตั้งชื่อใน UTF-8

และฉันต้องการใช้makeเพื่อขับเคลื่อนสิ่งนี้


1
เหตุผลใดในการเลือกยี่ห้อเพื่อจุดประสงค์นี้ ฉันคิดว่าการเขียนสคริปต์ทุบตีจะง่ายกว่านี้

4
@Neil แนวคิดของ make เนื่องจากการแปลงระบบไฟล์ตามรูปแบบเป็นวิธีที่ดีที่สุดในการแก้ไขปัญหาเดิม บางทีการใช้งานของวิธีการนี้มีข้อ จำกัด ของมัน แต่จะอยู่ใกล้กับการใช้มันกว่าเปลือยmake bash
P Shved

1
@Pavel ดีเป็นshสคริปต์ที่เดินผ่านรายการของไฟล์ FLAC (คนfind | while read flacname) ทำให้mp3nameจากที่ทำงาน "mkdir -p" บนdirname "$mp3name"แล้วif [ "$flacfile" -nt "$mp3file"]แปลง"$flacname"ลงใน"$mp3name"ไม่ได้วิเศษจริงๆ เพียงคนเดียวที่มีคุณเป็นจริงการสูญเสียเมื่อเทียบกับmakeวิธีการแก้ปัญหาตามความเป็นไปได้ที่จะเรียกใช้การแปลงไฟล์กระบวนการในแบบคู่ขนานกับN make -jN
ndim

4
@ndim นั่นเป็นครั้งแรกที่ฉันเคยได้ยินว่าไวยากรณ์ของ make ถูกอธิบายว่า "ดี" :-)

1
การใช้makeและการมีช่องว่างในชื่อไฟล์เป็นข้อกำหนดที่ขัดแย้งกัน ใช้เครื่องมือที่เหมาะสมกับโดเมนปัญหา
Jens

คำตอบ:


117

ฉันจะลองทำอะไรบางอย่างตามแนวเหล่านี้

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

บันทึกย่อสองสามข้อสำหรับmakeผู้เริ่มต้น:

  • @ในด้านหน้าของป้องกันคำสั่งmakeจากการพิมพ์คำสั่งก่อนที่จริงการทำงาน
  • $(@D)เป็นส่วนไดเร็กทอรีของชื่อไฟล์เป้าหมาย ( $@)
  • ตรวจสอบให้แน่ใจว่าบรรทัดที่มีคำสั่งเชลล์เริ่มต้นด้วยแท็บไม่ใช่ด้วยการเว้นวรรค

แม้ว่าสิ่งนี้ควรจัดการอักขระ UTF-8 และสิ่งของทั้งหมด แต่ก็จะล้มเหลวที่ช่องว่างในชื่อไฟล์หรือไดเร็กทอรีเนื่องจากmakeใช้ช่องว่างเพื่อแยกสิ่งต่างๆใน makefiles และฉันไม่ทราบวิธีแก้ไขปัญหานั้น ดังนั้นสิ่งนี้ทำให้คุณมีเพียงเชลล์สคริปต์ฉันกลัว: - /


นี่คือที่ที่ฉันกำลังจะไป ... แม้ว่าคุณจะสามารถทำอะไรบางอย่างที่ชาญฉลาดvpathได้ ต้องศึกษาว่าวันนี้
dmckee --- อดีตผู้ดูแลลูกแมว

1
ดูเหมือนจะไม่ทำงานเมื่อไดเรกทอรีมีช่องว่างในชื่อ
Roger Lipscombe

ไม่ทราบว่าฉันจะต้องออกไปfindเพื่อให้ได้ชื่อซ้ำ ...
Roger Lipscombe

1
@PaulKonova: วิ่งmake -jN. สำหรับNใช้จำนวนการแปลงที่makeควรทำงานควบคู่กัน ข้อควรระวัง: การรันmake -jโดยไม่มีNจะเริ่มกระบวนการแปลงทั้งหมดพร้อมกันซึ่งอาจเทียบเท่ากับส้อมระเบิด
ndim

1
@ เอเดรียน: .PHONY: allบรรทัดบอก make ว่าสูตรสำหรับallเป้าหมายจะถูกดำเนินการแม้ว่าจะมีไฟล์ที่เรียกว่าallใหม่กว่าไฟล์$(MP3_FILES).
ndim

53

คุณสามารถกำหนดฟังก์ชันตัวแทนแบบเรียกซ้ำของคุณเองได้ดังนี้:

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

พารามิเตอร์แรก ( $1) คือรายการของไดเร็กทอรีและตัวที่สอง ( $2) คือรายการของรูปแบบที่คุณต้องการจับคู่

ตัวอย่าง:

หากต้องการค้นหาไฟล์ C ทั้งหมดในไดเร็กทอรีปัจจุบัน:

$(call rwildcard,.,*.c)

ในการค้นหาไฟล์.cและ.hไฟล์ทั้งหมดในsrc:

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

ฟังก์ชันนี้อ้างอิงจากการนำไปใช้งานจากบทความนี้โดยมีการปรับปรุงเล็กน้อย


ดูเหมือนจะไม่ได้ผลสำหรับฉัน ฉันได้คัดลอกฟังก์ชันที่แน่นอนแล้ว แต่มันก็ยังดูไม่ซ้ำ
Jeroen

3
ฉันใช้ GNU Make 3.81 และดูเหมือนว่าจะได้ผลสำหรับฉัน จะไม่ทำงานหากชื่อไฟล์ใด ๆ มีช่องว่างอยู่ โปรดทราบว่าชื่อไฟล์ที่ส่งคืนมีเส้นทางที่สัมพันธ์กับไดเร็กทอรีปัจจุบันแม้ว่าคุณจะแสดงรายการไฟล์ในไดเร็กทอรีย่อยเท่านั้น
larskholte

2
นี่เป็นตัวอย่างที่แท้จริงนั่นmakeคือ Turing Tar Pit (ดูที่นี่: yosefk.com/blog/fun-at-the-turing-tar-pit.html ) มันไม่ได้ยากขนาดนั้น แต่ต้องอ่านสิ่งนี้: gnu.org/software/make/manual/html_node/Call-Function.htmlแล้วจึง "เข้าใจการเกิดซ้ำ" คุณต้องเขียนสิ่งนี้ซ้ำในความหมายคำต่อคำ; ไม่ใช่ความเข้าใจในชีวิตประจำวันของ "รวมสิ่งต่างๆจาก subdirs" โดยอัตโนมัติ มันเป็น RECURRENCE ที่แท้จริง แต่จำไว้ว่า - "หากต้องการเข้าใจการเกิดซ้ำคุณต้องเข้าใจการเกิดซ้ำ"
Tomasz Gandor

@TomaszGandor คุณไม่จำเป็นต้องเข้าใจการเกิดซ้ำ คุณต้องเข้าใจการเรียกซ้ำและก่อนอื่นคุณต้องเข้าใจการเรียกซ้ำ
user1129682

แย่จังฉันตกหลุมรักเพื่อนจอมปลอมทางภาษา ไม่สามารถแก้ไขความคิดเห็นได้หลังจากผ่านไปนานแล้ว แต่ฉันหวังว่าทุกคนจะเข้าใจตรงกัน และพวกเขายังเข้าใจการเรียกซ้ำ
Tomasz Gandor

2

FWIW ฉันเคยใช้สิ่งนี้ในMakefile :

RECURSIVE_MANIFEST = `find . -type f -print`

ตัวอย่างด้านบนจะค้นหาจากไดเร็กทอรีปัจจุบัน ( "." ) สำหรับ "ไฟล์ธรรมดา" ( '-type f' ) ทั้งหมดและตั้งค่าRECURSIVE_MANIFESTตัวแปร make เป็นทุกไฟล์ที่พบ จากนั้นคุณสามารถใช้การแทนที่รูปแบบเพื่อลดรายการนี้หรืออีกวิธีหนึ่งคือจัดหาอาร์กิวเมนต์เพิ่มเติมเพื่อค้นหาเพื่อ จำกัด สิ่งที่ส่งคืน ดูหน้าคนสำหรับการค้นหา


1

วิธีแก้ปัญหาของฉันขึ้นอยู่กับวิธีการข้างต้นใช้sedแทนpatsubstการทำลายผลลัพธ์ของfindAND Escape

ไปจาก flac / ถึง ogg /

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

ข้อควรระวัง:

  1. ยังคง barfs หากมีเครื่องหมายกึ่งโคลอนในชื่อไฟล์ แต่หายาก
  2. เคล็ดลับ $ (@ D) จะใช้ไม่ได้ (แสดงว่าพูดพล่อยๆ) แต่ oggenc สร้างไดเรกทอรีให้คุณ

1

นี่คือสคริปต์ Python ที่ฉันแฮ็กเข้าด้วยกันอย่างรวดเร็วเพื่อแก้ปัญหาเดิม: เก็บสำเนาไลบรารีเพลงที่บีบอัดไว้ สคริปต์จะแปลงไฟล์. m4a (สมมติว่าเป็น ALAC) เป็นรูปแบบ AAC เว้นแต่จะมีไฟล์ AAC อยู่แล้วและใหม่กว่าไฟล์ ALAC ไฟล์ MP3 ในไลบรารีจะถูกเชื่อมโยงเนื่องจากถูกบีบอัดแล้ว

เพียงระวังว่าการยกเลิกสคริปต์ ( ctrl-c) จะทิ้งไฟล์ที่แปลงแล้วครึ่งหนึ่ง

แต่เดิมฉันต้องการเขียน Makefile เพื่อจัดการสิ่งนี้ แต่เนื่องจากไม่สามารถจัดการช่องว่างในชื่อไฟล์ได้ (ดูคำตอบที่ยอมรับ) และเนื่องจากการเขียนสคริปต์ทุบตีนั้นรับประกันว่าจะทำให้ฉันอยู่ในโลกแห่งความเจ็บปวด Python จึงเป็นเช่นนั้น ค่อนข้างตรงไปตรงมาและสั้นดังนั้นจึงควรปรับเปลี่ยนตามความต้องการของคุณได้ง่าย

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

แน่นอนว่าสิ่งนี้สามารถปรับปรุงได้เพื่อทำการเข้ารหัสควบคู่กันไป ที่ฝากไว้เป็นแบบฝึกหัดให้กับผู้อ่าน ;-)


0

หากคุณใช้ Bash 4.x คุณสามารถใช้ตัวเลือก globbing ใหม่ได้เช่น:

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

สัญลักษณ์แสดงซ้ำประเภทนี้สามารถค้นหาไฟล์ทั้งหมดที่คุณสนใจ ( .flac , .mp3หรืออะไรก็ได้) โอ


1
สำหรับฉันแค่ $ (wildcard flac / ** / *. flac) ก็ดูเหมือนจะใช้ได้ OS X, Gnu ทำ 3.81
akauppi

2
ฉันลอง $ (wildcard ./**/*.py) แล้วมันก็ทำงานเหมือนกับ $ (wildcard ./*/*.py) ฉันไม่คิดว่า make รองรับ ** ได้จริงและมันก็ไม่ล้มเหลวเมื่อคุณใช้สอง * s ติดกัน
lahwran

@lahwran ควรเมื่อคุณเรียกใช้คำสั่งผ่าน Bash shell และคุณได้เปิดใช้งานตัวเลือกglobstar บางทีคุณอาจไม่ได้ใช้ GNU make หรืออย่างอื่น คุณอาจลองใช้ไวยากรณ์นี้แทน ตรวจสอบความคิดเห็นสำหรับคำแนะนำ มิฉะนั้นจะเป็นเรื่องสำหรับคำถามใหม่
kenorb

@kenorb ไม่ไม่ฉันไม่ได้ลองของคุณเพราะฉันต้องการหลีกเลี่ยงการเรียกเชลล์สำหรับสิ่งนี้โดยเฉพาะ ฉันใช้สิ่งที่แนะนำของ akauppi สิ่งที่ฉันไปด้วยดูเหมือนคำตอบของ larskholte แม้ว่าฉันจะได้มาจากที่อื่นเพราะความคิดเห็นที่นี่บอกว่าอันนี้เสียอย่างละเอียด ยัก :)
lahwran

@lahwran ในกรณีนี้**จะใช้ไม่ได้เพราะ globbing ที่ขยายออกไปนั้นเป็นสิ่งที่ bash / zsh
kenorb
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.