スクリプトが再帰的に適用されるように、「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?どこに報告する必要がありますか$targetfind本当にすべて一行でしょうか?$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非常に「Unixに似ています」が、ここでの原則は、各パラメータがANDまたはOR条件と共に適用できることです。ここで私たちはこのファイル名が一致するか、このファイル名が一致する場合は印刷します。ファイル名パターンは引用符で囲まれているため、シェルはこれを取得できません(シェルはすべての引用符で囲まれていないパターンを拡張する役割を果たすため、引用符で囲まれていないパターンがあり、そのパターンが*.mp4現在のjaneeyre.mp4ディレクトリにある場合、シェルはアイテム*.mp4を置き換えますそして、findユーザーは-name janeeyre.mp4あなたが望むものの代わりにそれを見るでしょう;もしあなたが1つ以上の名前と一致するならば、-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ループの入力になるように2つの部分を一緒にパイプすることですwhile

このコードをテストするときは、プレフィックスを付けることをお勧めしffmpegますrmecho会議実行され、どのパスが使用されるか。

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。したがって、1つを見つけると、spaceファイル名の終わりだと思います(スペースを含むファイル、たとえば「Sin City.avi」は、「Sin」と「City.avi」という2つのファイルとして扱われます)。したがって、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

Unixへようこそ:)

主な質問への回答で扱わないいくつかの小さな質問:

シェルスクリプトには、空白のファイル名が原因で多くの問題が発生するため、少し粗い部分があります。ほとんどすべてのファイル名は改行文字で区切られています(幸いにもわざわざこれを行う人はいません)。グローバル文字(たとえば、、および)を含む[ファイル]*も問題になることがあります。時には、次の基準を満たす読みにくいシェルコードを書くことが価値がない場合があります。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オプションがあります。たとえば、検索出力の1つの「行」をソートされた入力の複数行に変換することなくfindデータを転送できます。sortbashには区切られた行を読み取る方法がないため、データをシェル変数に保存しようとすると、これはあまり役に立ちません\0。 (これはIFSに有効な値ではないと思います。)

とにかく、シェルがデータをコードで処理しないようにするのは、単語の区切りが実際に必要でない限り、可能なすべてを常に二重引用符で囲む理由です。複雑なシェルコードを見るのが面倒な場合は、bashコンプリートコードを見てください。 (プログラム可能な完成を処理し、ls --colo => --color解凍のために* .zipファイルを完成または完成するなど、巧妙なタスクを実行できます。)set -xそして、タブを押します。 (実行追跡をオフにするには+ xを設定します。)

Re:forループ:*.mkvパターンの1つとして、これらの入力ファイルに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がデフォルト値なので大丈夫です。

関連情報