私は、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
であることを宣言し、その後元のファイルを削除すると、出力はまったく同じファイルに保存されます。元のビデオが入っているフォルダ。
これを再帰的に実行するように変換するにはどうすればよいですか? find
を使用する場合、変数$file
をどこで宣言しますか?そして、どこで$target
を宣言する必要がありますか?すべてのfind
は本当にワンライナーですか?条件チェックを実行する必要があるため、ファイルを変数$file
に渡す必要があります。
そして、(1)が成功したと仮定して、「出力を元のビデオとまったく同じフォルダーに保存する必要がある」という要件が満たされていることを確認するにはどうすればよいですか?
あなたはこのコードを持っています:
for file in *.mkv *avi *mp4 *flv *ogg *mov; do target="${file%.*}.mkv" ffmpeg -i "$file" "$target" && rm -rf "$file" done
これは現在のディレクトリで実行されます。それを再帰的なプロセスに変えるには、いくつかの選択肢があります。最も簡単な(IMO)は、提案したようにfind
を使用することです。 find
の構文は非常に「UNIXライクではない」ですが、ここでの原則は、各引数をANDまたはOR条件で適用できることです。ここでは、 「このファイル名が一致する場合OR that-ファイル名が一致する場合はprint-it」。ファイル名のパターンは引用符で囲まれているため、シェルは取得できません。それら(シェルは引用符で囲まれていないすべてのパターンを拡張する責任があるため、引用符で囲まれていない*.mp4
のパターンがあり、現在のディレクトリにjaneeyre.mp4
がある場合、シェルは*.mp4
を-name janeeyre.mp4
に置き換えます。一致すると、find
は目的の-name *.mp4
の代わりに*.mp4
を表示します。\
が複数の名前と一致するとさらに悪化します...)。括弧の前には_が付きます。 [SOMECODE] _また、シェルがサブシェルマーカーとしてそれらを実行しようとしないようにします(必要に応じて、代わりに括弧を引用することもできます:'('
)。
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
あとは、パイプ|
で2つの部分を結合して、find
の出力がwhile
ループの入力になるようにするだけです。
このコードをテストしている間、ffmpeg
とrm
の両方の前にecho
を付けることをお勧めします。そうすれば、何が実行されるかを確認できますwouldそしてどのような道で。
テストに推奨する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
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 findまたはBSDfindがある場合は、-regex
を使用できます。
find . -regex '.*\.\(mkv\|avi\|mp4\|flv\|ogg\|mov\)'
パイピングなしのスニペットの例(パスを引数として指定していると仮定):
#!/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」の2つのファイルとして扱われます)。したがって、IFS = $ '\ n'を使用すると、newlines
で入力を分割するように指示されます。そして最後に、$OIFS
変数に保存されている古い(デフォルト)IFS
を復元します。
またはコメントで示唆されているように、より良いアプローチは次のようになります。
#!/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
Unixへようこそ:)
主な質問に答えるあなたのマイナーな質問のいくつかに答えるためにカバーしませんでした:
スペースのあるファイル名では多くのことが壊れるので、シェルスクリプトには確かにいくつかの荒削りな部分があります。そして、ほとんどすべてが改行を含むファイル名で壊れます(幸いなことに、誰も意図的にそれらを作成しません)。 _[
_、_]
_、_*
_などのグロブ文字を含むファイル名も問題になることがあります。 wooledgeのBashGuide の標準まで、読みにくいシェルコードを書くだけの価値がない場合もあります。自分で使用する場合や、ファイル名がおかしくないことがわかっている場合は1回限りです。
変数を宣言する場所:
シェル変数を宣言する必要はありません。 bashでは、_shopt -o nounset
_を使用して、変数の参照とunSETをエラーにすることができますが、それは宣言されていないものとまったく同じではありません。変数の設定を解除すると便利な場合があります。シェル関数では、すべての一時関数を_local foo bar baz;
_で宣言することをお勧めします。そうすれば、シェル環境に変数を散らかしたり、さらに悪いことに、同じ名前の呼び出し元の変数を踏んだりすることはありません。
「配管」がほとんどわかりません。
シェルを使用する場合、データをstdoutに出力することにより、多くのデータが渡されます。パイプはそのデータを別のプログラムに送信し、プログラムはそれをstdinで読み取ります(通常はstdoutで何かを出力します)。コマンド置換$()
を使用して、出力をシェル変数にキャプチャできます。例えばfor i in $( locate foo | grep bar );do echo "$i"; done
。 (これは、注意しないと多くのシェルコードのように、スペースが含まれるファイル名で壊れます。信頼できるスクリプトを作成する場合は、read
を使用してください。)locate
prints、 grep
は読み取りと出力を行い、シェルはgrep
の出力を読み取ります。 (シェルは、シェルが作成したパイプの入力側に接続された出力でgrepを開始することにより、grep
の出力を取得します。シェルはパイプの出力側を読み取ります。)
パイプは、プログラムがファイルに書き込んでいるように機能するための単なる方法ですが、実際には小さなバッファーに書き込んでいます。パイプから読み取るプロセスは、使用可能なデータがある場合にread(2)
システムコールを返します。これは、パイプのもう一方の端に何かが書き込まれた場合にのみ発生します。
シェルの_|
_、$()
、およびその他のいくつかの構文要素は、プログラムを相互に、およびシェルに接続する配管をセットアップする方法をシェルに指示する方法です。
シェルプログラミングの悪いイディオムを学ぶのは簡単です。なぜなら、多くの明白なことや古いやり方には、奇妙なファイル名で壊れる落とし穴が隠されているからです。たとえば http://mywiki.wooledge.org/BashFAQ/001 を参照してください。
入力するのが面倒でない限り、奇妙なファイル名で壊れる方法を学ぶよりも、最初から安全なスクリプトの方法を学ぶ方が良いでしょう。 :)
多くのGNU utilsには-0オプションがあり、ASCII NUL(0バイト、ファイル名またはテキストには存在できません)をレコード区切り文字。これにより、たとえば、find
とsort
の間でデータをパイプ処理できます。たとえば、検索出力の1つの「行」がソート入力の複数の行に変わる可能性はありません。 bashには_\0
_で区切られた行を読み取る方法がないため、データをShell変数に入れたい場合に非常に便利です(これはIFSの有効な値ではないと思います)。
とにかく、本当に単語分割が必要な場合を除いて、シェルがデータをコードとして扱わないようにすることが、可能な限りすべてを常に二重引用符で囲む理由です。複雑なシェルコードを見て脳を傷つけたい場合は、bash-completionコードを見てください。 (_ls --colo => --color
_を完了する、または* .Zipファイルのみを解凍するなどの巧妙な処理を行うプログラム可能な完了を処理します。)_set -x
_およびタブ:Pを押します。 (+ xを設定して、実行トレースをオフにします。)
re:forループ:パターンの1つとして_*.mkv
_を使用すると、これらの入力ファイルのsource = destになります。 ffmpeg
は、それぞれの出力ファイルを上書きするように求めます。
また、本当にオーディオをトランスコードする必要がありますか? _-c:a copy
_は良い考えかもしれません。通常、ビデオのビットレートは大きな問題です。また、CPU使用率を犠牲にして、ビットレートあたりの品質を高めるために_-preset slow
_(またはslower
、さらにはveryslow
)を使用することもできます。 _-crf 20
_(デフォルトは23)もあります。 https://trac.ffmpeg.org/wiki/Encode/H.264 。うまくいけば、すでにこれを知っていて、bashスクリプトに関連していなかったので省略しましたが、念のため...:P _-c:v libx264
_がmkvに出力するときのデフォルトなので、それは良いことです。