Если вы работаете с shell-скриптами, рано или поздно столкнётесь с задачей: скачать файл по URL и сохранить его оригинальное имя, которое выдаёт сервер. Казалось бы, и wget, и curl умеют автоматически определять имя из заголовков (вроде Content-Disposition), но вот загвоздка – эти команды не возвращают имя файла в stdout. А значит, нельзя просто взять и присвоить его переменной, как в примере ниже:
newfilename=$(wget ...) # Так не сработает
Почему так? Возможно, разработчики опасаются инъекций (если имя файла содержит опасные символы), но сама команда всё равно создаёт файл с этим именем. Получается парадокс: имя используется, но скрипту его не видно. К счастью, есть обходные пути – от парсинга логов до расширенных атрибутов. Давайте разберём их по шагам.
Решение для curl: магия –write-out и xattr
Curl – гибкий инструмент, и его опция –write-out позволяет выводить в stdout любые метаданные о загрузке. Например, чтобы получить имя сохранённого файла, используйте плейсхолдер %{filename_effective}
:
newfilename=$(curl -L -O -J --silent --remote-header-name --write-out "%{filename_effective}" "${myurl}")
Кстати, ключ -O
(заглавная буква O) включает сохранение под оригинальным именем, а -J
активирует парсинг Content-Disposition. Но если сервер не передаёт этот заголовок, имя может быть взято из URL (например, после последнего слеша).
А ещё curl умеет записывать URL в расширенные атрибуты файла (xattr) – это удобно для аудита. Просто добавьте опцию --xattr
, и ссылка сохранится в атрибут user.xdg.origin.url
:
curl --xattr -O -J "${myurl}"
Теперь проверить URL можно командой:
xattr -p user.xdg.origin.url filename.txt
Wget: парсим логи и настраиваем xattr
С wget история немного сложнее. Команда не выводит имя файла напрямую, но сохраняет его в лог. Вот как извлечь его через sed:
logfile=$(mktemp /tmp/wgetlog.XXXXXX)
wget --content-disposition --trust-server-names -o "$logfile" "${myurl}"
newfilename=$(sed -n 's/^Saving to: ‘(.*)’/1/p' "$logfile")
rm "$logfile"
Обратите внимание на кавычки в логе – они могут быть «умными» (типографскими), поэтому в sed используется ‘ и ’ вместо обычных. Если столкнётесь с ошибкой «No match», попробуйте сменить локаль на UTF-8:
LC_ALL=C.utf-8 wget ...
Для автоматического сохранения URL в xattr добавьте в ~/.wgetrc строку:
~/.wgetrc
xattr = on
Теперь wget запишет URL в атрибут user.xdg.origin.url
, и вам не придётся делать это вручную через xattr -w
.
А если сервер не отдаёт имя файла?
Иногда сервер не передаёт Content-Disposition, и тогда имя берётся из URL. Но что, если в URL нет явного имени (например, генерация файла через скрипт)? Тут поможет таймстамп:
wget --timestamping "${myurl}"
Команда проверит дату изменения файла на сервере и скачает его только при обновлении. А если файл уже есть локально, просто обновит его. Правда, в этом случае имя файла будет зависеть от предыдущих загрузок – возможно, стоит комбинировать методы.
И напоследок: не забывайте про безопасность. Если имя файла формируется удалённо, всегда проверяйте его на наличие нежелательных символов (вроде / или ;). Например, обрежьте всё, кроме букв, цифр и точек:
safe_name=$(echo "$newfilename" | sed 's/[^а-яА-Яa-zA-Z0-9.]//g')
(Да, иногда проще переименовать файл, чем полагаться на сервер).