Скрипт оболочки под вопросом
Позвольте мне объяснить, что я пытаюсь сделать, например, чтобы вы могли лучше понять. Допустим, у меня есть 100 файлов .torrent в каталоге. 2 из них загрузят xxx.epub и yyy.epub соответственно, если добавить их в клиент BitTorrent, но я не знаю, какие именно 2 из 100.
Итак, мой скрипт делает следующее: (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
содержит кавычки/код оболочки. Фактически,произвольный код может быть выполнен. Пример: в этом конкретном случае входной файл может содержать строку'; 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
На основе предложений @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