|
Компьютерный форум OSzone.net » Программирование, базы данных и автоматизация действий » Скриптовые языки администрирования Windows » CMD/BAT - [решено] Вывод части бинарного файла |
|
CMD/BAT - [решено] Вывод части бинарного файла
|
Старожил Сообщения: 415 |
Приветствую уважаемых форумчан.
В ходе реализации RFC 7233 (HTTP Range Requests) для батника, запущенного в качестве CGI-скрипта, столкнулся с новой для себя задачей — вывести определённый диапазон байтов бинарного файла в stdout. Первым кандидатом на роль подходящего инструмента стал dd, и путём чтения мануалов и поиска на stackoverflow пришёл к вот этой строчке: где соответственно, %1 и %2 - смещение от начала файла и размер фрагмента в байтах. Проблема в том, что при этом dd работает с размером блока, равным одному байту (а мне именно такая точность и требуется), что совершенно неприемлемо по скорости работы и нагрузке на процессор. Прошу подсказать более подходящий инструмент для этой задачи, или объяснить что я делаю не так, в случае если неправильно использую dd. |
|
Отправлено: 10:10, 27-11-2015 |
Ветеран Сообщения: 1758
|
|
Отправлено: 10:27, 27-11-2015 | #2 |
Для отключения данного рекламного блока вам необходимо зарегистрироваться или войти с учетной записью социальной сети. Если же вы забыли свой пароль на форуме, то воспользуйтесь данной ссылкой для восстановления пароля. |
Старожил Сообщения: 415
|
Профиль | Отправить PM | Цитировать Если возможно, хотелось бы всё же избежать использования powershell, не в последнюю очередь оттого, что я с ним почти не знаком и не смогу полноценно интегрировать предложенное решение в свой скрипт. Ваш пример я так и не смог запустить с бинарным файлом (не выводит ничего), а с текстовым показывает ряд чисел вместо фрагмента содержимого. Интересует решение именно для батника, со сторонними утилитами.
|
Отправлено: 10:56, 27-11-2015 | #3 |
Ветеран Сообщения: 1758
|
|
Отправлено: 14:20, 27-11-2015 | #4 |
Забанен Сообщения: 793
|
Не знаю сколь много считывать, но как вариант:
Foreigner, в случае с PS - не лучше ли через FileStream?
|
|
Отправлено: 15:38, 27-11-2015 | #5 |
Старожил Сообщения: 415
|
Профиль | Отправить PM | Цитировать Цитата Foreigner:
Код скрипта
@Echo Off :: Обрабатываем аргументы Set Data=%* :Next For /F "tokens=1,* delims=;" %%A In ("%Data%") Do ( For /F "tokens=1,2 delims==" %%C In ("%%A") Do (Set %%C=%%D) If Not "%%B"=="" Set Data=%%B&GoTo :Next ) :: Обрабатываем get\post параметры rem Выпилено на время теста, работаем только с заголовками запроса :: Обрабатываем куки If Not Defined H-Cookie Set "H-Cookie=dummy"] :: Set H-Cookie|Findstr /C:"<" /C:">" /C:"&" /C:"|" /C:^"\^"^" /C:"(" /C:")" /C:"\^">nul&&(Call HTTP302Header err_badcookie&Exit) For %%A In ("%H-Cookie:;=" "%") Do For /F "tokens=1,* delims==" %%A In ("%%~A") Do Set "%%A=%%B" :: Проверяем авторизацию и доступ If Not Defined Code Exit /B 3 &:: err_nosession For %%A In (User Access LastSeen) Do Set "%%A=" For /F "tokens=1-3 delims=|" %%A In ('Call GetUserInfo %H-REMOTE_ADDR% %Code%') Do ( Set "UserName=%%A" Set "Access=%%B" Set "LastLogin=%%C" ) If Not Defined Access Exit /B 3 &:: err_nosession If %Access% LSS 5 Exit /B 5 &:: err_noaccess :: ДЛЯ ОТЛАДКИ :: В рабочей копии скрипта все эти данные будут взяты из БД Set File=D:\Projects\cmdserv\wwwroot\test.mp3 For %%A In ("%File%") Do ( Set name=%%~nxA Set size=%%~zA ) Set timestamp=1442271745 :: Приводим имя файла к рабочей кодировке, для отладки, в продакшене имя сразу будет в юникоде For /F "eol= delims=" %%A In ('Echo %name%^|recode cp866..utf8') Do Set "nameUTF8=%%A" :: Конвертируем время для Last-Modified For /F "delims=" %%A In ('UnixTime2HTTPDate %timestamp%') Do (Set timestamp=%%A) :: Получаем текущее время For /F "delims=" %%A In ('GetHTTPDate') Do (Set datetime=%%A) :: Получаем MIME-тип, таблица для сопоставления по умолчанию указана в переменных окружения враппера For /F "delims=" %%A In ('GetMIMEType "%name%"') Do Set "mimetype=%%A" :: Если указан Range переходим к обработчику запроса If Defined H-Range GoTo :Range :: Отдаём заголовки и файл, если не поступало запросов на частичную отдачу Echo HTTP/1.1 200 OK Echo Server: OWS/%__OWS_version% Echo Date: %datetime% Echo Accept-Ranges: bytes Echo Content-Type: %mimetype% Echo Content-Disposition: attachment; filename="%nameUTF8%"; size=%size%; creation-date=%timestamp%; modification-date=%timestamp%; read-date=%datetime% Echo Last-Modified: %timestamp% Echo Content-Length: %size% Echo Connection: close Echo. Type "%File%" Exit :Range SetLocal EnableDelayedExpansion :: Случайная строка для использования в качестве разделителя For /F "delims=" %%A In ('GetRandomName charset=3 length=16') Do (Set boundary=%%A) :: Максимальное значение диапазона = размер-1байт For /F "delims=" %%- In ('cc %size%-1') Do Set MaxRange=%%- :: Имя временного файла, используемого если в одном запросе несколько диапазонов Set TmpFile="%Temp%\~part%boundary%.tmp" :: Выделяем диапазоны из заголовка, если тип не bytes - отдаём HTTP 416 For /F "tokens=1,* delims==" %%A In ("%H-Range%") Do If /I "%%A"=="bytes" (Set "Ranges=%%B") Else (Call :Err416) Set Parts=0 For %%A In (%Ranges%) Do ( Set rejected=false Set Range="%%A" For /F "tokens=1,2 delims=-" %%B In ("!Range:-="-"!") Do ( Set "rMin=%%~B" Set "rMax=%%~C" :: Разворачиваем диапазоны с пустыми значениями If "!rMax!"=="" Set rMax=%MaxRange% cc !rMax! ^>= %size%|ExitCode&&Set rMax=%MaxRange% If "!rMin!"=="" For /F "delims=" %%- In ('cc %MaxRange%-!rMax!+1') Do ( Set rMax=%MaxRange% Set rMin=%%- ) ) :: Проверки If Not Defined rMin Set rejected=true If Not Defined rMax Set rejected=true cc !rMin! ^<= !rMax!|ExitCode||Set rejected=true :: Если диапазон проходит по всем условиям, записываем аргументы для функции, выдающей часть файла If "!rejected!"=="false" ( Set /A Parts+=1 Set $Range=!rMin!-!rMax!/%size% Set Skip=!rMin! For /F "delims=" %%- In ('cc !rMax!+1-!rMin!') Do Set Length=%%- Set Part[!Parts!]="!Skip!" "!Length!" "!$Range!" "%boundary%" "!Parts!" ) ) :: Есть заголовок Range, но ни один из диапазонов невалиден? Отдаём HTTP 416 If "%Parts%"=="0" Call :Err416 Echo HTTP/1.1 206 Partial Content Echo Server: OWS/%__OWS_version% Echo Date: %datetime% Echo Last-Modified: %timestamp% Echo Accept-Ranges: bytes If "%Parts%"=="1" ( Echo Content-Type: %mimetype% ) Else ( Echo Content-Type: multipart/byteranges; boundary=%boundary% ) If "%Parts%"=="1" ( Echo Content-Length: !Length! Echo Content-Range: bytes !rMin!-!rMax!/%size% Echo. :: Нет смысла играться со временным файлом, если диапазон один - выводим напрямую Call :PartialContent !Part[1]! 1 ) Else ( For /L %%A In (1,1,!Parts!) Do ( Call :PartialContent !Part[%%A]! !Parts!>>%TmpFile% ) :: Подсчитываем размер получившихся частей ВМЕСТЕ с заголовками и разделителями Call :PartsSize %TmpFile% Echo. rem Выводим контент и удаляем временный файл Type "%TmpFile%" Del "%TmpFile%" 2>nul ) EndLocal Exit :: Выдаём часть файла согласно аргументам :PartialContent (skip, length, range, boundary, part, parts) rem Echo Skip: "%~1", Length: "%~2", Range: "%~3" --%~4-- [%~5 of %~6] If "%~6"=="1" ( dd if="%File%" ibs=1 skip=%~1 count=%~2 2>nul Exit /B ) Echo --%~4 Echo Content-Type: %mimetype% Echo Content-Range: bytes %~3 Echo. dd if="%File%" ibs=1 skip=%~1 count=%~2 2>nul :: Если это последний блок, пишем финальный разделитель If "%~5"=="%~6" <nul Set /P "Echo=--%~4--" Exit /B :PartsSize (file) For %%A In ("%~1") Do Echo Content-Length: %%~zA Exit /B :Err416 Echo HTTP/1.1 416 Range Not Satisfiable Echo Server: OWS/%__OWS_version% Echo Date: %datetime% Echo Accept-Ranges: bytes Echo Content-Range: bytes */%size% Echo. Exit UPD: Прошу прощения, я не совсем верно вас понял при первом прочтении сообщения. Вы предложили использовать один блок сразу нужного размера, и это до меня только сейчас дошло. Да, это может сработать, сейчас протестирую. greg zakharov, к сожалению, те же проблемы с производительностью, файлы обычно бывают от десятка мегабайт до гигабайта, а запрашиваемая часть файла варьируется в зависимости от браузера либо другого софта, делающего запрос, но редко превышает треть или четверть общего размера. |
|
Последний раз редактировалось Anonymоus, 27-11-2015 в 20:31. Отправлено: 20:13, 27-11-2015 | #6 |
Ветеран Сообщения: 1758
|
|
Отправлено: 20:33, 27-11-2015 | #7 |
Старожил Сообщения: 415
|
Профиль | Отправить PM | Цитировать Протестировал с одним блоком нужного размера, работает действительно быстрее, но точно задать офсет не выйдет — ibs влияет и на это тоже, опять всплывает проблема с кратностью размера блока. А я уже успел обрадоваться, что решение найдено. Про вылеты вы верно заметили, бывало пару раз, я списал это как раз на работу с блоком в один байт — всё же совершенно нетипичные условия работы для этой утилиты.
|
Отправлено: 20:55, 27-11-2015 | #8 |
Старожил Сообщения: 415
|
Профиль | Отправить PM | Цитировать Решил проблему с помощью двух вызовов dd с передачей результата через пайп, первый обеспечивает смещение, второй выдаёт данные нужной длины. Производительность приемлемая даже на больших файлах. Заодно заменил саму утилиту на эту, она не упала ни разу за всё время тестирования.
|
Отправлено: 05:34, 28-11-2015 | #9 |
![]() |
Участник сейчас на форуме |
![]() |
Участник вне форума |
![]() |
Автор темы |
![]() |
Сообщение прикреплено |
| |||||
Название темы | Автор | Информация о форуме | Ответов | Последнее сообщение | |
CMD/BAT - [решено] Правка бинарного файла | sov44 | Скриптовые языки администрирования Windows | 0 | 25-06-2015 13:32 | |
VBS/WSH/JS - [решено] Поиск строки по части названия и вывод данных в ECHO | Kainos | Скриптовые языки администрирования Windows | 14 | 16-04-2015 22:21 | |
VBS/WSH/JS - поиск слова в тексте и вывод части текста после искомого слова | sergey23031978 | Скриптовые языки администрирования Windows | 9 | 18-02-2015 16:54 | |
Скрипт для разбиения файла на части | Diamond | AutoIt | 17 | 17-05-2013 01:42 | |
C/C++ - Чтение из бинарного файла формата GRD | Violetta_ | Программирование и базы данных | 3 | 20-05-2012 19:45 |
|