なぜこの関数の実行はwhileループから外れますか?

なぜこの関数の実行はwhileループから外れますか?

次のスクリプトは、現在の作業ディレクトリ内のすべてのメディアファイルを切り捨てるように設計されています。

#!/usr/bin/bash

trimmer() {
  of=$(echo "${if}"|sed -E "s/(\.)([avimp4kvweb]{3,3}$)/\1trimmed\.\2/")
  ffmpeg -hide_banner -loglevel warning -ss "${b}" -to "${ddecreased}" -i "${if}" -c copy "${of}"
  echo "Success. Exiting .."
}

get_ddecreased() {
  duration="$(ffprobe -v quiet -show_entries format=duration -hide_banner "${if}"|grep duration|sed -E s/duration=\([0-9]*\)\..*$/\\1/)"
  echo ${duration} 
  ddecreased="$(echo "${duration} - ${trimming}"|bc -l)"
  echo ${ddecreased} 
}

rm_source() {
  echo -e "Remove ${if}?[Y/n]?"
  read ch
  if [[ "${ch}" == 'y' ]]; then
    rm "${if}"
  fi 
}


echo "How much of the beginning would you like to trim?"
read b
echo "How much of the end would you like to trim?"
read trimming

ls *.avi *.mkv *.mp4 *.vob >list_of_files

echo "Prompt before removing the source[Y/n]?"
read ch
while IFS="" read -r if || [[ -n "${if}" ]]; do
  if [[ "${ch}" == 'y' ]]; then
    get_ddecreased  && trimmer && rm_source
  elif [[ "${ch}" == 'n' ]]; then
    get_ddecreased && trimmer && rm "${if}"
  fi
  echo $if
done <list_of_files

echo -e "Removing list_of_files."
rm list_of_files 

最初のファイルは、ユーザーに要求に応じてyトリミングを選択しPrompt before removing the source[Y/n]て完了するかどうかを尋ねるメッセージを表示するように設計されていますtrimmerrm_source彼らの入力を待つソースファイルを削除する前に、これはスクリプトが入力を待たず、echo -e "Removing list_of_files."whileループがまったくないかのようにすぐに続くため、機能しません。ユーザーがn要求したときに選択すると、whileループも実行されませんPrompt before removing the source[Y/n]。スクリプトがecho -e "Removing list_of_files."繰り返すのではなく直接続行されますlist_of_files。なぜですか?しかし、これらすべての行をコメントアウトすると

if [[ "${ch}" == 'y' ]]; then
    get_ddecreased  && trimmer && rm_source
  elif [[ "${ch}" == 'n' ]]; then
    get_ddecreased && trimmer && rm "${if}"
  fi

whileループ内では、すべての行がlist_of_files画面に印刷されます。

私のコードに問題がありますか?

ベストアンサー1

コードはデフォルトで次のことを行います。

foo () {
    read variable
}

while read something; do
    foo
done <input-file

目的は、端末から何かをread読むfooことですが、いくつかのファイルからリダイレクトされる標準入力ストリームのコンテキストで呼び出されます。

readこれは、inがfoo端末ではなく入力ファイルの入力ストリームから読み取られることを意味します。

標準入力以外のファイル記述子からループを読み取ると、この問題を回避できます。

foo () {
    read variable
}

while read something <&3; do
    foo
done 3<input-file

ここで、readループはキーワードの後に​​入力ファイルに関連付けられたファイル記述子3から読み取られますdone。これにより、関数read内の関数が生のfoo標準入力ストリームを自由に使用できます。

bashシェルでは、追加のファイル記述子にハードコードされた値を使用する代わりに、シェル変数に記述子を割り当てることができます。

foo () {
    read variable
}

while read something <&"$fd"; do
    foo
done {fd}<input-file

$fd10以上の整数に設定できます。正確な値は重要ではありません。


問題の現在のコードでは、ファイルのリストを生成して読み取ることを避け、代わりにファイルglobを直接使用して問題を解決することもできます。

for filename in *.avi *.mkv *.mp4 *.vob; do
    if [ ! -e "$filename" ]; then
        # skip non-existing names
        continue
    fi

    # use "$filename" here
    # ... and call your rm_source function
done

これにより、リダイレクトが完全に防止されます。これにより、コード内の名前に改行文字を含む奇妙なファイルを処理することもできます。

指定されたファイルが存在するかどうかをテストするループのステートメントは、パターンに一致する名前がifない場合、デフォルトではシェルがワイルドカードパターンを保持するために必要です。以下を設定して、シェルifからステートメントを削除できます。bashnullglob シェルオプションの使用 shopt -s nullglob。このオプションを設定すると、bashシェルは一致しないグローブを完全に削除します。

また、ワイルドカードパターンと一致する名前目次。たとえば、ディレクトリがある場合は一覧表示されますmydir.mp3lsコンテンツこのディレクトリの。また、パターンに一致するファイル名がダッシュで始まる場合、使用されたコードはlsオプションセットの名前を間違える可能性があります。

おすすめ記事