GNU의 재귀 와일드 카드는?


92

을 (를) 사용 make한지 오래 되었으니 참아주세요 ...

flac.FLAC 파일이 포함 된 디렉토리가 있습니다. mp3MP3 파일이 포함 된 해당 디렉토리가 있습니다. FLAC 파일이 해당 MP3 파일보다 새로운 경우 (또는 해당 MP3 파일이 존재하지 않음) FLAC 파일을 MP3 파일로 변환하고 태그를 복사하는 명령을 실행하고 싶습니다.

키커 : flac디렉터리를 재귀 적 으로 검색하고 디렉터리에 해당 하위 디렉터리를 만들어야합니다 mp3. 디렉토리와 파일은 이름에 공백이있을 수 있으며 UTF-8로 이름이 지정됩니다.

그리고 나는 make이것을 운전하는 데 사용하고 싶습니다 .


1
이 목적을 위해 make를 선택하는 이유가 있습니까? bash 스크립트를 작성하는 것이 더 간단 할 것이라고 생각했을 것입니다

4
@Neil, 패턴 기반 파일 시스템 변환이라는 make의 개념은 원래 문제에 접근하는 가장 좋은 방법입니다. 아마도이 접근 방식의 구현에는 한계가 있지만 makebare보다 구현에 더 가깝습니다 bash.
P Shved 2010 년

1
@Pavel 음, shFLAC 파일 (목록을 안내 스크립트 find | while read flacname), A는 만드는 mp3name것과, "MKDIR -p"를 실행에 dirname "$mp3name", 다음, 및 if [ "$flacfile" -nt "$mp3file"]변환 "$flacname""$mp3name"정말 마법되지 않습니다. make기반 솔루션에 비해 실제로 손실되는 유일한 기능은 .NET Framework와 N병렬로 파일 변환 프로세스 를 실행할 수 있다는 것 입니다 make -jN.
ndim

4
@ndim make의 구문이 "nice"로 설명되는 것을 처음 들었습니다. :-)

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합니다. 공백을 사용하여 메이크 파일에서 항목을 분리하고이를 해결하는 방법을 모릅니다. 그래서 쉘 스크립트 만 남깁니다. 두렵습니다. :-/


여기가 제가 가고 있던 곳입니다 ... 승리를위한 빠른 손가락. 를 사용하여 영리한 작업을 수행 할 수있는 것처럼 보이지만 vpath. 요즘 그거 공부해야 해.
dmckee --- ex-moderator kitten

1
디렉토리 이름에 공백이 있으면 작동하지 않는 것 같습니다.
Roger Lipscombe

I가 밖으로 껍질해야 할 것 몰랐어요 find... 재귀 적으로 이름을 얻기 위해
로저 Lipscombe

1
@PaulKonova : 실행 make -jN. 병렬로 실행해야하는 N변환 수 를 사용합니다 make. 주의 : make -j없이 실행 N하면 포크 폭탄에 해당 할 수있는 모든 변환 프로세스가 동시에 시작됩니다.
ndim

1
@Adrian :이 .PHONY: all줄은 모든 .NET Framework 보다 newer all라는 파일이 있더라도 대상에 대한 레시피 가 실행 되도록 make에 알려줍니다 . all$(MP3_FILES)
ndim

52

다음과 같이 고유 한 재귀 와일드 카드 함수를 정의 할 수 있습니다.

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 2013

3
나는 GNU Make 3.81을 사용하고 있는데 그것은 나를 위해 작동하는 것 같습니다. 그러나 파일 이름에 공백이 있으면 작동하지 않습니다. 반환하는 파일 이름에는 하위 디렉터리에있는 파일 만 나열하는 경우에도 현재 디렉터리에 상대적인 경로가 있습니다.
larskholte 2013

2
이것이 바로 makeTuring Tar Pit (여기 참조 : yosefk.com/blog/fun-at-the-turing-tar-pit.html )입니다. 그다지 어렵지는 않지만 gnu.org/software/make/manual/html_node/Call-Function.html 을 읽고 "반복 이해"를해야합니다. 당신은 이것을 축어적인 의미로 재귀 적으로 써야했습니다. "하위 디렉토리의 항목을 자동으로 포함"에 대한 일상적인 이해가 아닙니다. 실제 RECURRENCE입니다. 그러나 기억하십시오- "재발을 이해하려면 재발을 이해해야합니다".
Tomasz Gandor 2014-08-06

@TomaszGandor 재발을 이해할 필요가 없습니다. 재귀를 이해하려면 먼저 재귀를 이해해야합니다.
user1129682

내 나쁜, 나는 언어 적 거짓 친구에 빠졌다. 오랜만에 댓글을 수정할 수 없지만, 모두가 이해했으면 좋겠습니다. 그리고 그들은 또한 재귀를 이해합니다.
Tomasz Gandor

2

FWIW, Makefile 에서 다음과 같은 것을 사용했습니다 .

RECURSIVE_MANIFEST = `find . -type f -print`

위의 예는 현재 디렉토리 ( '.' )에서 모든 "일반 파일"( '-type f' )을 검색하고 RECURSIVE_MANIFEST찾은 모든 파일에 make 변수를 설정합니다 . 그런 다음 패턴 대체를 사용하여이 목록을 줄이거 나, 반환되는 항목을 좁히기 위해 find 에 더 많은 인수를 제공 할 있습니다. find 는 man 페이지를 참조하십시오 .


1

내 솔루션은 위의 것을 기반으로하며 sed대신에 공백을 이스케이프하고 patsubst출력 find하는 데 사용합니다.

flac /에서 ogg /로 이동

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

주의 사항 :

  1. 파일 이름에 세미콜론이 있으면 여전히 소리가 나지만 매우 드뭅니다.
  2. $ (@ D) 트릭은 작동하지 않지만 (무의미한 출력) oggenc는 사용자를 위해 디렉토리를 생성합니다!

1

다음은 원래 문제를 해결하기 위해 빠르게 해킹 한 Python 스크립트입니다. 음악 라이브러리의 압축 된 사본을 보관하세요. 스크립트는 AAC 파일이 이미 존재하고 ALAC 파일보다 최신 버전이 아닌 경우 .m4a 파일 (ALAC로 가정)을 AAC 형식으로 변환합니다. 라이브러리의 MP3 파일은 이미 압축되어 있으므로 링크됩니다.

스크립트 ( ctrl-c) 를 중단하면 반 변환 된 파일이 남게됩니다.

나는 원래 이것을 처리하기 위해 Makefile을 작성하고 싶었지만 파일 이름의 공백을 처리 할 수 ​​없기 때문에 (허용되는 답변 참조) bash 스크립트를 작성하면 고통의 세계에 빠질 수 있기 때문에 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 Make 3.81
akauppi

2
나는 $ (wildcard ./**/*.py)를 시도했고 $ (wildcard ./*/*.py)와 동일하게 작동했습니다. 나는 make가 실제로 **를 지원한다고 생각하지 않으며, 두 개의 *를 나란히 사용할 때 실패하지 않습니다.
lahwran

@lahwran Bash 셸을 통해 명령을 호출하고 globstar 옵션을 활성화했을 때해야합니다 . GNU make 또는 다른 것을 사용하지 않을 수 있습니다. 대신 이 구문을 사용해 볼 수도 있습니다 . 몇 가지 제안에 대한 의견을 확인하십시오. 그렇지 않으면 새로운 질문에 대한 것입니다.
kenorb 2016

@kenorb no no, 나는이 특정 일에 대한 쉘 호출을 피하고 싶었 기 때문에 당신의 것을 시도하지 않았습니다. 나는 akauppi가 제안한 것을 사용하고 있었다. 내가 함께 갔던 것은 larskholte의 대답처럼 보였지만 여기의 의견이 미묘하게 깨 졌다고 말했기 때문에 다른 곳에서 얻었습니다. 어깨를 으쓱 :)
lahwran

@lahwran이 경우 **에는 확장 된 globbing이 bash / zsh이기 때문에 작동하지 않습니다.
kenorb
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.