Bash

為什麼這些函式的執行會跳出這個while循環?

  • August 21, 2022

以下腳本旨在修剪目前工作目錄中的所有媒體文件。

#!/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]trimmer完成了第一個文件的修剪,rm_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列印到螢幕上。

我的程式碼有什麼問題?

您的程式碼本質上是在執行以下操作:

foo () {
   read variable
}

while read something; do
   foo
done <input-file

目的是讓readinfoo從終端讀取某些內容,但是,它是在標準輸入流從某個文件重定向的上下文中呼叫的。

這意味著readinfoo將從來自輸入文件的輸入流中讀取,而不是從終端讀取。

您可以通過從標準輸入之外的另一個文件描述符讀取循環來規避此問題:

foo () {
   read variable
}

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

在這裡,read循環中的 in 從文件描述符 3 中讀取,該文件描述符在done關鍵字之後連接到輸入文件。這使得函式read中的foo可以自由使用原始標準輸入流。

bashshell 中,您可以讓 shell 在 shell 變數中分配描述符,而不是為額外的文件描述符使用硬編碼值:

foo () {
   read variable
}

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

這可能會設置$fd為 10 或更高的整數。確切的值並不重要。


在問題的目前程式碼中,您還可以通過避免創建和讀取文件列表來解決您的問題,而是直接使用文件 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語句用於測試命名文件是否存在,這是必要的,因為預設情況下,如果該模式沒有匹配的名稱,shell 將保留通配模式。您可以通過使用設置shell 選項來擺脫 shell 中的if語句。設置此選項將使shell 完全刪除不匹配的 glob。bash``nullglob``shopt -s nullglob``bash

還要注意,如果與通配模式匹配的任何名稱是目錄,則這與您的程式碼中的不同。如果您有一個名為 eg 的目錄mydir.mp3,那麼ls將列出該目錄的內容。此外,如果與模式匹配的文件名以破折號開頭,則使用的程式碼ls可能會將該名稱誤認為是一組選項。

引用自:https://unix.stackexchange.com/questions/714373