내 스크립트가 재귀적으로 적용될 수 있도록 "for file in"을 "find"로 변환하세요.

내 스크립트가 재귀적으로 적용될 수 있도록 "for file in"을 "find"로 변환하세요.

저는 이런 생각을 했습니다. 특정 조건을 확인하는 bash 스크립트를 실행하고 이를 사용하여 ffmpeg디렉터리의 모든 비디오를 모든 형식에서 모든 형식으로 변환하면 .mkv훌륭하게 작동합니다!

문제는 for file in루프가 재귀적으로 작동하지 않는다는 점입니다.https://stackoverflow.com/questions/4638874/how-to-loop-through-a-directory-recursively)

그러나 나는 "파이프라인"을 거의 이해하지 못하며 예제를 보고 불확실성을 일부 해소할 수 있기를 기대하고 있습니다.

이 장면이 머릿속에 떠올랐는데, 이해하는데 많은 도움이 된 것 같아요.

다음 bash 스크립트 조각이 있다고 가정해 보겠습니다.

for file in *.mkv *avi *mp4 *flv *ogg *mov; do
target="${file%.*}.mkv"
    ffmpeg -i "$file" "$target" && rm -rf "$file"
done

이것이 하는 일은 현재 디렉터리에 대해 *.mkv *avi *mp4 *flv *ogg *mov확장자를 검색한 다음 출력을 선언한 다음 .mkv원본 파일을 삭제하고 출력을 원본 비디오가 있던 동일한 폴더에 저장하는 것입니다.

  1. 이것을 재귀적으로 실행되도록 어떻게 변환할 수 있나요? 을 사용하는 경우 find변수를 어디에서 선언합니까 $file? 어디에 신고해야 합니까 $target? find정말 모두 한 줄 일까요 ? $file조건부 검사를 실행해야 하므로 파일을 변수에 전달해야 합니다 .

  2. 그리고 (1)이 성공한다고 가정하면 "출력은 원본 비디오가 있는 동일한 폴더에 저장되어야 합니다"라는 요구 사항이 충족되는지 어떻게 확인할 수 있습니까?

답변1

POSIX를 사용하여 다음을 찾으세요.

find . \( -name '*.mkv' -o -name '*avi' -o -name '*mp4' -o -name '*flv' -o \
          -name '*ogg' -o -name '*mov' \) -exec sh -c '
  for file do
    target="${file%.*}.mkv"
    echo ffmpeg -i "$file" "$target"
  done' sh {} +

echo사용하려는 명령으로 바꾸십시오 .

GNU 찾기 또는 BSD 찾기가 있는 경우 다음을 사용할 수 있습니다 -regex.

find . -regex '.*\.\(mkv\|avi\|mp4\|flv\|ogg\|mov\)'

답변2

다음 코드가 있습니다.

for file in *.mkv *avi *mp4 *flv *ogg *mov; do
target="${file%.*}.mkv"
    ffmpeg -i "$file" "$target" && rm -rf "$file"
done

현재 디렉터리에서 실행됩니다. 이를 재귀 프로세스로 전환하려면 몇 가지 옵션이 있습니다. 가장 쉬운 (IMO) 제안대로 사용하는 것입니다 find. for의 구문은 find매우 "유닉스와 유사"하지만 여기서의 원칙은 각 매개변수가 AND 또는 OR 조건과 함께 적용될 수 있다는 것입니다. 여기서 우리는 "이 파일 이름이 일치하거나 이 파일 이름이 일치하면 인쇄하십시오.". 파일 이름 패턴은 따옴표로 묶여 있어 쉘이 이를 얻을 수 없습니다(쉘은 인용되지 않은 모든 패턴을 확장하는 역할을 담당하므로 인용되지 않은 패턴이 있고 해당 패턴이 *.mp4현재 janeeyre.mp4디렉토리에 있는 경우 쉘은 *.mp4항목을 대체하고 find사용자는' -name janeeyre.mp4당신이 원하는 것 대신에 그것을 보게 될 것입니다 ; 만약 당신이 하나 이상의 이름과 일치한다면 -name *.mp4그것은 더 나빠질 것입니다 *.mp4...) \, 당신이 원한다면 '(':).

find . \( -name '*.mkv' -o -name '*avi' -o -name '*mp4' -o -name '*flv' -o -name '*ogg' -o -name '*mov' \) -print

while출력은 각 파일을 차례로 처리하는 루프의 입력 으로 공급되어야 합니다 .

while IFS= read file    ## IFS= prevents "read" stripping whitespace
do
    target="${file%.*}.mkv"
    ffmpeg -i "$file" "$target" && rm -rf "$file"
done

이제 남은 것은 |출력이 find루프의 입력이 되도록 두 부분을 함께 파이프하는 것입니다 while.

이 코드를 테스트할 때 접두사를 붙이는 ffmpeg것이 좋습니다 rm.echo회의실행되고 어떤 경로가 사용되는지.

echo테스트를 권장하는 내용을 포함한 최종 결과는 다음과 같습니다 .

find . \( -name '*.mkv' -o -name '*avi' -o -name '*mp4' -o -name '*flv' -o -name '*ogg' -o -name '*mov' \) -print |
    while IFS= read file    ## IFS= prevents "read" stripping whitespace
        do
            target="${file%.*}.mkv"
            echo ffmpeg -i "$file" "$target" && echo rm -rf "$file"
        done

답변3

파이프가 없는 예제 스니펫(경로를 매개변수로 제공한다고 가정):

#!/bin/bash

backup_dir=/backup/

OIFS="$IFS"
IFS=$'\n'

files="$(find "$1" -type f -name '*.mkv' -or -name '*.avi' -or -name '*.mp4' -or -name '*.ogg' -or -name '*.mov' -or -name '*.flv')"

for f in $files; do
    # get path
    d="${f%/*}"
    # get filename
    b="$(basename "$f")"
    ttarget="${b%.*}.mkv"

    # this is your final target
    target="$d/$ttarget"
    echo $target
    # mv $f "$backup_dir" 
done

IFS="$OIFS"

쉘은 이 변수를 읽으며 기본 IFS설정은 ( space, tab, )입니다. newline그런 다음 출력의 각 문자를 살펴봅니다 find. 따라서 하나를 찾으면 space파일 이름의 끝이라고 생각합니다(공백이 포함된 파일, 예를 들어 "Sin City.avi"는 "Sin"과 "City.avi"라는 두 개의 파일로 처리됩니다). 따라서 IFS=$'\n'을 사용하여 입력을 newlines. 마지막으로 변수 IFS에 저장된 $OIFS이전(기본값) 변수를 복원합니다 .
또는 의견에서 제안한 대로 더 나은 접근 방식은 다음과 같습니다.

#!/bin/bash

backup_dir=/backup/

find "$1" -type f \( -name '*.mkv' -or -name '*.avi' -or -name '*.mp4' -or -name '*.ogg' -or -name '*.mov' -or -name '*.flv' \) -print0 | while IFS= read -r -d '' f
do
    # get path
    d="${f%/*}"
    # get filename
    b="$(basename "$f")"
    ttarget="${b%.*}.mkv"

    # this is your final target
    target="$d/$ttarget"
    echo $target
    # mv $f "$backup_dir"
done

답변4

유닉스에 오신 것을 환영합니다 :)

주요 질문에 대한 답변에서 다루지 않는 몇 가지 작은 질문:

쉘 스크립트에는 공백이 있는 파일 이름으로 인해 많은 문제가 발생하기 때문에 약간의 거친 부분이 있습니다. 거의 모든 파일 이름은 개행 문자로 구분됩니다(다행히도 일부러 이렇게 하는 사람은 없습니다). 전역 문자(예: , 및 )가 포함된 [파일 ]이름 *도 문제가 되는 경우가 있습니다. 때로는 다음 기준을 충족하는 읽기 어려운 셸 코드를 작성하는 것이 가치가 없는 경우도 있습니다.Woolwich의 BashGuide, 자신이 사용하거나 일회성으로 사용하려면 파일 이름을 아는 것이 당연합니다.

변수를 선언하는 위치:

쉘 변수는 선언할 필요가 없습니다. Bash에서는 shopt -o nounset참조된 변수와 설정되지 않은 변수를 오류로 설정할 수 있지만 이는 선언되지 않은 것과는 다릅니다. 변수를 설정하지 않는 것이 유용할 수 있습니다. 쉘 함수 내에서 모든 임시 변수를 선언하여 local foo bar baz;쉘 환경을 변수로 어지럽히거나 더 나쁘게는 동일한 이름의 호출자의 변수를 밟지 않도록 하는 것이 좋습니다 .

나는 "파이프라인"을 거의 이해하지 못합니다.

쉘을 사용할 때 대량 데이터 전송은 데이터를 표준 출력으로 인쇄하여 수행됩니다. 파이프는 해당 데이터를 표준 입력으로 데이터를 읽는 다른 프로그램으로 보냅니다(그리고 일반적으로 표준 출력으로 무언가를 인쇄합니다). 명령 대체를 사용하여 출력을 쉘 변수로 캡처할 수 있습니다 $(). 예를 들어 for i in $( locate foo | grep bar );do echo "$i"; done. (많은 쉘 코드처럼 조심하지 않으면 파일 이름이 공백으로 깨집니다. read신뢰할 수 있는 스크립트를 작성하려면 이것을 사용하십시오.) locate인쇄하고, grep읽고, 인쇄한 다음 쉘 읽기 grep(쉘은 다음과 같이 출력을 얻습니다. grep을 실행 grep하고 그 출력을 셸에서 생성된 파이프의 입력에 연결합니다(셸은 파이프의 출력을 읽습니다).

파이프는 프로그램이 파일에 쓰는 것처럼 작동하는 방법일 뿐이지만 실제로는 작은 버퍼에 쓰는 것입니다. 파이프에서 읽는 프로세스는 데이터가 사용 가능할 때 시스템 호출을 반환하며 read(2), 이는 파이프의 다른 쪽 끝에 데이터가 기록될 때만 발생합니다.

쉘의 |$()기타 구문 요소는 프로그램 간, 프로그램과 쉘 간 파이프 연결을 설정하는 방법을 쉘에 알려주는 데 사용됩니다.

쉘 프로그래밍의 나쁜 관용구를 배우는 것은 쉽습니다. 많은 명백한 것들과 오래된 작업 방식이 이상한 파일 이름으로 인해 무너질 수 있는 함정을 숨기고 있기 때문입니다. 예시 보기http://mywiki.wooledge.org/BashFAQ/001.

입력하기 너무 어렵지 않은 한, 이상한 파일 이름을 엉망으로 만드는 방법을 배우는 것보다 처음부터 안전한 스크립팅 방법을 배우는 것이 더 낫습니다. :)

많은 GNU 유틸리티에는 레코드 구분 기호로 ASCII NUL(0바이트, 파일 이름이나 텍스트에 나타날 수 없음)을 사용하는 -0 옵션이 있습니다. 예를 들어, 찾기 출력의 한 "행"을 정렬된 입력의 여러 행으로 변환할 가능성 없이 find데이터를 전송할 수 있습니다. sortbash는 구분된 행을 읽을 수 있는 방법이 없기 때문에 데이터를 쉘 변수에 저장하려고 할 때 이는 별로 유용하지 않습니다 \0. (이것은 IFS에 유효한 값이 아니라고 생각합니다.)

어쨌든 쉘이 데이터를 코드로 처리하지 않도록 하는 것은 단어 분리를 실제로 원하지 않는 한 가능한 모든 것을 항상 큰따옴표로 묶는 이유입니다. 복잡한 쉘 코드를 보는 것이 골치아프다면 bash 완성 코드를 살펴보세요. (프로그램 가능한 완성을 처리하고 ls --colo => --color압축 해제를 위해 *.zip 파일을 완성하거나 완성하는 등 영리한 작업을 수행할 수 있습니다 .) set -x그리고 탭을 누르세요:P. (실행 추적을 끄려면 +x를 설정합니다.)

Re: for 루프: *.mkv패턴 중 하나로 이러한 입력 파일에 대해 source = dest를 제공합니다. ffmpeg각 파일의 출력 파일을 덮어쓰라는 메시지가 표시됩니다.

또한 오디오를 실제로 트랜스코딩해야 합니까? -c:a copy좋은 생각일 수도 있습니다. 일반적으로 비디오 비트 전송률이 더 중요합니다. -preset slow(또는 slower) 을 사용하여 veryslow비트 전송률당 더 높은 품질을 얻을 수 있지만 CPU 사용량이 더 많이 소모됩니다. 또한 -crf 20(기본값 23). https://trac.ffmpeg.org/wiki/Encode/H.264. Bash 스크립트와 아무 관련이 없기 때문에 이미 알고 계시고 무시하시기를 바랍니다. 그러나 혹시라도... -c:v libx264mkv로 출력할 때 :P가 기본값이므로 괜찮습니다.

관련 정보