web-dev-qa-db-ja.com

スクリプトを再帰的に適用できるように、 `for filein`を` 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)が成功したと仮定して、「出力を元のビデオとまったく同じフォルダーに保存する必要がある」という要件が満たされていることを確認するにはどうすればよいですか?

7
arvil

あなたはこのコードを持っています:

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ループの入力になるようにするだけです。

このコードをテストしている間、ffmpegrmの両方の前に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
5
roaima

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\)'
6
cuonglm

パイピングなしのスニペットの例(パスを引数として指定していると仮定):

#!/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変数を読み取ります。この変数は、デフォルトで(spacetabnewline)に設定されています。次に、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
2
taliezin

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バイト、ファイル名またはテキストには存在できません)をレコード区切り文字。これにより、たとえば、findsortの間でデータをパイプ処理できます。たとえば、検索出力の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に出力するときのデフォルトなので、それは良いことです。

1
Peter Cordes