Pull to refresh

Расширяем возможности wget

Reading time 6 min
Views 24K
Здравствуйте.

Вот я и стал полноправным пользователем хабра. Хочу поблагодарить человека, который дал мне инвайт за эту статью:

Не так давно я начал постигать работу с Linux (в частности Ubuntu 8.10) и у меня появилась задача автоматического скачивания файлов по списку. «wget -i» конечно вещь хорошая, но мне хотелось большего, а именно:
  1. Скачивание списка ссылок из файла
  2. Скачивание одновременно нескольких файлов
  3. Перенос неудавшихся закачек в отдельный список для дальнейших повторных попыток.

Так что появилась необходимость в чуть более продвинутом инструменте для скачивания файлов, чем может предложить wget. Я решил реализовать его с помощью bash. Правда может помешать отсутствие опыта написания bash-скриптов, но как раз пришли выходные и часы проведенные за материалом по теме не прошли зря.

Результатом моей работы стал такой скрипт:

Update: Благодаря совету zencd использовал команду wait для ожидания завершения закачек.
Update 2: shulc указал на ошибку: заменил #!/binbash на #!/bin/sh. darkk подсказал о существовании mktemp для создания временных файлов.
#!/bin/sh
log_dir="${PWD}/log"
list_dir="${PWD}/list"
output_dir=${PWD}
# download_list - файл ссылок для скачивания
download_list="${list_dir}/download.lst"
# В active_list записываются активные закачки
active_list="${list_dir}/active.lst"
# В done_list записываются скачанные ссылки
done_list="${list_dir}/done.lst"
# В error_list записываются неудавшиеся закачки
error_list="${list_dir}/error.lst"
# $timeout - время перед повторной попыткой скачивания неудавшейся закачки
timeout=5

# Перемещает строку $1 из файла $2 в файл $3, нужна для манипуляций со списками
# move_line line source_file dest_file
move_line()
{
  tmp_file=`mktemp -t downloader.XX`
  echo $1 >> $3
  cat $2 | grep -v $1 > $tmp_file
  mv $tmp_file $2
}

# Функция скачивания, в $1 передается номер потока скачивания
download_thread()
{
  thread=$1
  # Цикл скачивания, пока файлы download.lst и error.lst не станут пустыми
  while [ -s $download_list ] || [ -s $error_list ]
  do  
    # Если download.lst пустой - переносим в него строку из error.lst
    if [ ! -s $download_list ]  
    then
      read url < $error_list
      move_line $url $error_list $download_list
      sleep $timeout
    fi
    read url < $download_list
    move_line $url $download_list $active_list
    echo "[Thread ${thread}]Starting download: $url"
    # Старт закачки
    wget -c -o "${log_dir}/wget_thread${thread}.log" -O "${output_dir}/$(basename "$url")" $url
    # Проверка кода завершения wget (Если 0 - закачка успешная)
    if [ $? -eq 0 ]
    then
      # Закачка файла завершилась удачно
      move_line $url $active_list $done_list
      echo "[Thread ${thread}]Download successful: $url"
    else
      # Ошибка закачки - перемещаем в файл с ошибочными ссылками
      move_line $url $active_list $error_list
      echo "[Thread ${thread}]Error download: $url"
    fi
  done
  return 0
}

# Завершает ранее запущенные процессы скрипта и закачки из active.lst
stop_script()
{
  # Убиваем все процессы этого скрипта кроме текущего
  kill -9 `ps ax | grep $0 | grep -v "grep" | awk '{print $1}' | grep -v $$`
  # Убиваем все процессы закачек из active.lst
  while [ -s $active_list ]
  do
    read url < $active_list
    move_line $url $active_list $download_list
    kill -9 `ps ax | grep $url | grep -v "grep" | awk '{print $1}'`
  done
}

case "$1" in
"stop" )
  echo "Stoping downloader..."
  stop_script
  echo "Done..."
  ;;
"start" )
  # Проверка наналичие файла со ссылками для скачивания
  if [ ! -e $download_list ];
  then
    echo "[Error] There is no ${list_dir}/download.lst file"
    exit
  fi
  echo "Starting downloader..."
  # На случай вторичного запуска скрипта останавливаем ранее запущенные процессы
  stop_script
  # Если не задано кол-во одновременных закачек в $2, устанавливаем 1 поток
  if [ -z $2 ]
  then
    threads=1
  else
    threads=$2
  fi
  # Запускаем в фоне закачки
  i=1
  while [ $i -le $threads ]
  do
    download_thread $i &
    downloader_pid="${downloader_pid} $!"
    sleep 1
    i=`expr $i + 1`
  done
  if [ ! -e $error_list ]; then touch $error_list; fi
  # Ждем окончания всех закачек
  wait $downloader_pid
  # Все скачали...
  echo "All completed"
  ;;
* )
  echo "Usage:"
  echo "\t$0 start [number of threads]"
  echo "\t$0 stop"
  ;;
esac

return 0


* This source code was highlighted with Source Code Highlighter.

Для работы скрипта необходимо сделать его исполняемым и создать файл "./list/download.lst" со списком ссылок для скачивания.

Запуск:
sh downloader start [количество одновременных скачиваний]
или, как правильно заметил Mezomish, так:
./downloader start [количество одновременных скачиваний]

Параметр после 'start' необязательный (если его не указать — используется «1»).
Т.е. `sh downloader start 2` запустит скрипт с одновременным скачиванием 2-х файлов.

Остановка:
sh downloader stop
или
./downloader stop

При завершении скрипта при помощи «Ctrl+C» закачки не завершаются, т.к. работают в фоне, поэтому необходимо выполнить вышеуказанную команду команду для остановки скачивания.

Я решил не загромождать скрипт, но в принципе, не сложно реализуется работа со списками (show — вывод на экран, add — добавление закачки, wipe — очистка). А так он рабочий хоть и с минимальной функциональностью.

Т.к. это мой первый bash-скрипт, то любые замечания/пожелания/рекомендации очень приветствуются.

Дальше я вкратце опишу принципы работы скрипта, чтобы желающим легче было модифицировать его под свои нужды.

В константах указаны:
log_dir — папка с логами wget'a (по умолчанию "./log")
list_dir — папка со списками download_list, active_list, done_list, error_list (по умолчанию "./list")
output_dir — папка куда будут сохраняться скачиваемые файлы (по умолчанию ".")
download_list список ссылок для скачивания
active_list — список активных закачек
done_list — список завершенных закачек
error_list — список неудавшихся закачек
timeout — время перед повторной попыткой скачивания неудавшейся закачки

В начале работы скрипт останавливает ранее запущенные его копии, а также закачки из active_list (конечно если такие имеются) с переносом их в download_list. Это делается на случай повторного запуска скрипта до завершения скачивания ранее запущенным процессом. Дальше в цикле создается необходимое количество фоновых закачек. Каждый такой фоновый поток реализуется функцией download_thread(). Ее работа заключается в скачивании файлов из списка пока списки download_list и error_list не станут пустыми. Таким образом основная часть скрипта, проверяя эти файлы узнает закончилась ли скачка. Перед запуском wget'a ссылка переносится из файла download_list в файл active_list. После завершения работы wget'a ссылка переносится, либо в done_list (если код возврата был '0'), либо в error_list (если код возврата был не равен '0').
После того как все скачано (списки download_list и error_list пусты) скрипт завершает свою работу.

На этом все. При желании любой, кто немного знаком со скриптописанием, может добавить в него нужные для себя функции.
Tags:
Hubs:
+61
Comments 48
Comments Comments 48

Articles