簡單的 shell 腳本無法遍歷數千個檔案;開始正常,但一段時間後拋出“在尋找匹配的`”`時出現意外的EOF

簡單的 shell 腳本無法遍歷數千個檔案;開始正常,但一段時間後拋出“在尋找匹配的`”`時出現意外的EOF

有問題的 Shell 腳本

讓我解釋一下我正在嘗試做的事情,以便您可以更好地理解。假設我的目錄中有 100 個 .torrent 檔案。如果新增至 BitTorrent 用戶端,其中 2 個將分別下載 xxx.epub 和 yyy.epub,但我不知道這 100 個是哪 2 個。

所以我的腳本所做的是,(1) 用於find遍歷所有 .torrent 文件pwd並傳遞每個 .torrent 文件,該文件將transmission-show解析 .torrent 文件並以人類可讀格式輸出元資料。然後,我們將使用它awk來獲取 torrent 檔案將下載的檔案名,並針對 list.txt 運行該檔案名,其中包含我們正在尋找的檔案名,即 xxx.epub 和 yyy.epub。

文件: findtor-array.sh

#! /bin/bash
#
# Search .torrent file based on 'Name' field.
#
# USAGE:
# cd ~/myspace # location of .torrent files
# Run `findtor ~/list.txt` (if `findtor.sh` is placed in `~/bin` or `~/.local/bin`)

# Turn the list of file names from ~/list.txt (or any file passed as argument) into an array
readarray -t FILE_NAMES_TO_SEARCH < "$1"

# For each file name from the list...
for FILE_NAME in "${FILE_NAMES_TO_SEARCH[@]}"
do
    # In `pwd` and 1 directory-level under, look for .torrent files and search them for the file name
    find . -maxdepth 2 -name '*.torrent' -type f -exec bash -c "transmission-show \"\$1\" | awk '/^Name\: / || /^File\: /' | awk -F ': ' '\$2 ~ \"$FILE_NAME\" {getline; print}'" _ {} \; >> ~/torrents.txt

    # The `transmission-show` command included in `find`, on it own, for clarity:
    # transmission-show xxx.torrent | awk '/^Name: / || /^File: /' | awk -F ': ' '$2 ~ "SEARCH STRING" {getline; print}'
done

我認為這個過程很簡單,而且我做得很正確(除了沒有檢查,我知道)。但不知怎的,整個任務對於腳本來說似乎太多了,因為運行它之後,一段時間後它開始連續拋出這些錯誤,直到我Ctrl+C它:

_: -c: line 0: unexpected EOF while looking for matching `"'
_: -c: line 1: syntax error: unexpected end of file

這些是「規模化」問題嗎?我缺少什麼以及我可以做什麼來解決它?

答案1

FILE_NAME直接傳遞到bash -c-exec的命令選項find。如果FILE_NAME包含引號/shell 程式碼,這會導致問題。實際上,可以執行任意程式碼。範例:在這種特殊情況下,輸入檔案可能包含一行'; echo "run commands";'

相反,將循環 varbash -c作為位置參數傳遞。例如:

find . -maxdepth 2 -name '*.torrent' -type f -exec sh -c '
transmission-show "$2" |
awk -v search="$1" '\''/^Name: / {name = substr($0,7)} /^File: / && name ~ search {print; exit}'\' \
_ "$FILE_NAME" {} \;

此外,循環遍歷每個文件的所有搜尋字詞似乎效率很低。考慮循環文件並使用以下命令進行搜尋grep -f file

find . -maxdepth 2 -name '*.torrent' -type f -exec sh -c '
file=$1
shift
if transmission-show "$file" | head -n 1 | cut -d" " -f2- | grep -q "$@"; then
    printf "%s\n" "$file"
fi' _ {} "$@" \;

或沒有find

for file in *.torrent */*.torrent; do
    if transmission-show "$file" | head -n 1 | cut -d' ' -f2- | grep -q "$@"; then
        printf '%s\n' "$file"
    fi
done
  • 上面只是將所有參數傳遞給grep,因此用法是findtor -f ~/list.txt從列表中獲取模式,-F對於固定字串-e expression等。

答案2

基於 @Kusalananda 的建議、答案(@guest 和 @Jetchisel),以及凱文的詳細回答,我想出了這個:

#! /bin/bash
#
# Search for 'Name' field match in torrent metadata for all .torrent files in
# current directory and directories 1-level below.
#
# USAGE e.g.:
# cd ~/torrent-files # location of .torrent files
# Run `~/findtor.sh ~/list.txt`

# Get one file name at a time ($FILE_NAME_TO_SEARCH) to search for from list.txt
# provided as argument to this script.
while IFS= read -r FILE_NAME_TO_SEARCH; do

    # `find` .torrent files in current directory and directories 1-level under
    # it. `-print0` to print the full file name on the standard output, followed
    # by a null character (instead of the newline character that `-print` uses).
    #
    # While that's happening, we'll again use read, this time to pass one
    # .torrent file at a time (from output of `find`) to `transmission-show`
    # for the latter to output the metadata of the torrent file, followed by
    # `awk` commands to look for the file name match ($FILE_NAME_TO_SEARCH) from
    # list.txt.
    find . -maxdepth 2 -name '*.torrent' -type f -print0 |
        while IFS= read -r -d '' TORRENT_NAME; do
            transmission-show "$TORRENT_NAME" | awk '/^Name: / || /^File: /' | awk -F ': ' -v search_string="$FILE_NAME_TO_SEARCH" '$2 ~ search_string {getline; print}';
        done >> ~/torrents-found.txt

done < "$1"

我剛剛運行了這個,到目前為止它似乎工作得很好。非常感謝所有參與者!

雖然我已盡力而為,但歡迎任何修復和進一步的建議。

答案3

我會這樣寫。

#!/usr/bin/env bash

pattern_file="$1"

while IFS= read -r -d '' file; do
    transmission-show "$file" | awk .... "$pattern_file"   ##: Figure out how to do the awk with a file rather than looping through an array.
done < <(find . -maxdepth 2 -name '*.torrent' -type f -print0)

這應該可以避免引用地獄:-)

好吧,也許nullglob不需要。

編輯

嘗試 find 命令並將其用於原始腳本。

find . -maxdepth 2 -name '*.torrent' -type f -exec bash -c 'transmission-show "$1" | awk "/^Name\: / || /^File\: /" | awk -F ": " "\$2 ~ \"$FILE_NAME\" {getline; print}"' _ {} + >> ~/torrents.txt

相關內容