web-dev-qa-db-ja.com

何千ものファイルを処理できない単純なシェルスクリプト。正常に起動しますが、しばらくすると「予期しないEOF一致する `" `」を探しているときにスローされます

問題のシェルスクリプト

私がやろうとしていることを、例えば、説明させてください。だからあなたはよりよく理解することができます。ディレクトリに100個の.torrentファイルがあるとしましょう。それらのうちの2つは、ビットトレントクライアントに追加された場合、それぞれxxx.epubとyyy.epubをダウンロードしますが、100のうちの2つはわかりません。

つまり、私のスクリプトは、(1)findを使用してpwd内のすべての.torrentファイルを調べ、各.torrentファイルをtransmission-showに渡します。 .torrentファイルを解析し、メタデータを人間が読める形式で出力します。次に、awkを使用して、トレントファイルがダウンロードするファイル名を取得し、探しているファイル名(xxx.epubとyyy.epub)を持つlist.txtに対して実行します。

ファイル: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

プロセスは単純だと思います、そして私はそれを正しくやっています(チェックがないことを除いて、私は知っています)。しかし、どういうわけか、タスク全体がスクリプトには多すぎるように見えます。スクリプトを実行した後、しばらくすると、私が CtrlC それ:

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

これらの「スケーリング」の問題はありますか?何が欠けていますか、それを修正するために何ができますか?

1
its_me

@Kusalanandaからの提案、回答(@guestと@Jetchiselによる)、および Kevinによるこの詳細な回答 に基づいて、私はこれを思いつきました:

#! /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"

これを実行したところ、これまでのところうまく機能しているようです。関係者の皆様、本当にありがとうございました!

最善を尽くしましたが、修正やさらなる提案を歓迎します。

2
its_me

FILE_NAMEは、findコマンドのbash -cオプションで-execに直接渡されます。 FILE_NAMEに引用符/シェルコードが含まれている場合、これにより問題が発生します。実際、任意のコードが実行される可能性があります。例:この特定のケースでは、入力ファイルに'; echo "run commands";'という行を含めることができます。

代わりに、ループ変数を位置パラメーターとしてbash -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
guest

このように書きます。

#!/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
0
Jetchisel