1 (изменено: wisgest, 2025-12-14 16:32:43)

Тема: CMD/BAT: Перекодировка текстовых файлов

CMD/BAT: перекодировка текста dos866&↔win1251:

The gray Cardinal пишет:

Есть немало консольных утилит для перекодировки текста, но многие забывают про встроенные возможности.

Ещё до создания указанной темы я думал над решением этого вопроса теми же средствами и тоже дальше построчной обработки файлов пойти не смог. А хотелось бы обработать весь файл одним махом, что-то наподобие

chcp XXX
type in.txt>out.txt

Не обязательно совсем так просто: допускалась, например, необходимость нескольких вложенных вызовов CMD.EXE — главное, обойтись небольшим числом обращений к утилите CHCP.COM, а не дёргать её для каждой строки. К сожалению, мне ничего не удалось, хотя я по прежнему допускаю существование решения на этом пути.

Так что решение от 01MDM имеет полное право на существование в Коллекции, хотя и не лишено недостатков, как неустранимых (FOR /F пропускает пустые строки; есть сложности со спецсимволами вплоть до угрозы инъекции кода), так и устранимых, которые следовало бы убрать, подправив тему без создания новых сообщений (не помню, то ли не обрабатываются пробелы в начале строк, то ли непустые строки не содержащие ничего кроме пробелов). Но сейчас речь не об этом.

То что не получилось одним махом (командой TYPE) можно сделать двумя: перекодировкой исходного файла в промежуточный файл в UTF-16, а затем промежуточного — в файл в нужной кодировке. В качестве кодировок исходного и конечного файла могут быть любые однобайтные кодировки, а также UTF-8 без BOM. В основе такого решения лежит соединение вместе уже упоминавшихся мною приёмов по перекодировке файлов в/из UTF-16 с помощью команды TYPE.

Вот пример (без необходимых в полевых условиях проверок), раскрывающий эту мысль (этот код содержит непечатные знаки, не копируйте его, а берите из присоединённого файла!):

@echo off
set /p IN=Исходный файл: 
set /p IN_CP=Его кодировка: 
set /p OUT=Выходной файл: 
set /p OUT_CP=Желаемая кодировка: 
for /f "tokens=2 delims=:" %%i in ('chcp.com') do set CP=%%i
::в следующей строке выводятся байты \xFF\xFE (BOM):
set /p p= ■<nul >utf-16.tmp
(
chcp.com %IN_CP%>nul
cmd.exe /u /c type %IN%>>utf-16.tmp
chcp.com %OUT_CP%>nul
cmd.exe /c type utf-16.tmp>%OUT%
chcp.com %CP%>nul
)
del utf-16.tmp

Может возникнуть вопрос: зачем здесь несколько команд объединены круглыми скобками в одну составную?
Отвечаю: В качестве кодировки как входного так и выходного файлов могут выступать не только однобайтные кодировки, но и UTF-8 (кодовая страница 65001), а работа командных файлов при переключении на неё, по крайней мере в более ранних изданиях Windows, например в XP, имеет одну неприятную особенность — они тихо завершают работу на следующей же команде, но текущая (в том числе, составная) команда всё-таки выполняется и если в конце неё переключится обратно на однобайтную кодировку, то вылета не произойдёт. Но переключение кодировок в составной команде не влияет на входящие в неё внутренние команды обработчика, поэтому, чтобы воспользоваться её плодами, необходимо команду вызывать через новую копию CMD.EXE с ключом /C. Это одна из причин, по которой таким образом вызывается вторая TYPE, но, собственно говоря, её всё равно следовало бы вызывать таким образом, если учесть исчезающе малую возможность, что командный файл вызван из CMD.EXE, уже запущенного с ключом /U (на самом деле, для полного учёта такой возможности следовало бы добавить ещё «cmd.exe /c » в начало строки c  выводом BOM).

Еще по поводу UTF-8: Если она используется во входном файле, то он не должен содержать BOM, иначе в начале выходного будет лишний символ и как его обрезать мне на ум не приходит. Если она используется для выходного файла, то он создаются без BOM, но это легко обойти тем же путем что и при создании промежуточного файла в UTF-16.

(Что-то похожее мною уже затрагивалось.)


___
2025-12-14: Предложенный пример был проверен мною в Windows XP и без изменений не будет работать в более новых выпусках Windows. Добавил к теме сообщение о причине неработоспособности и её несложном устранении.

Post's attachments

recode.zip 402 b, 59 downloads since 2013-06-24 

You don't have the permssions to download the attachments of this post.

2 (изменено: wisgest, 2025-12-16 03:37:31)

Re: CMD/BAT: Перекодировка текстовых файлов

Первоначальный пример был проверен мною в Windows XP. К сожалению, в более новых выпусках Windows (вероятно, начиная с Windows 7 или, даже, Vista; мною обнаружено в Windows 10/11) предложенный пример не работает как следует. А именно, из-за того, что команда

::в следующей строке выводятся байты \xFF\xFE (BOM):
set /p p= ■<nul >utf-16.tmp

выводит лишь второй символ (\xFE). Как я понимаю, причина в том, что символ \xFF рассматривается CMD как пробельный (впрочем, заранее оговорюсь, это зависит от кодировки), а с некоторых пор пробельный символы в начале подсказки команды SET/P теряются.

Вместо этого можно было бы использовать заранее подготовленный файл или создавать его каждый раз с помощью CERTUTIL (но, опять же эта утилита может быть доступна не во всех ранних выпусках Windows):

echo ff fe>hex.txt
certutil -f -decodehex hex.txt utf-16.tmp >nul
del hex.txt

Но можно обойтись и без этого. Прикреплённый к первому сообщению файл был сохранён в кодировке cp866, но обратим внимание, что в Windods-1251 символ \xFF — это просто буква «я», символ явно не пробельный (и не специальный), и если командный файл сохранить в этой кодировке и в нём самом явно переключатся на неё, то всё работает и на Windows 11, я даже не буду прикреплять пример отдельным файлом, так как никаких нечитаемых символов в нём нет (но обращаю внимание, что сохранять его нужно в Windows-1251, а не в cp866):

@echo off
for /f "tokens=2 delims=:" %%i in ('chcp.com') do set CP=%%i
chcp 1251>nul
set /p IN=Исходный файл: 
set /p IN_CP=Его кодировка: 
set /p OUT=Выходной файл: 
set /p OUT_CP=Желаемая кодировка: 
::в следующей строке выводятся байты \xFF\xFE (BOM):
set /p "p=яю"<nul >utf-16.tmp
(
chcp.com %IN_CP%>nul
cmd.exe /u /c type %IN%>>utf-16.tmp
chcp.com %OUT_CP%>nul
cmd.exe /c type utf-16.tmp>%OUT%
chcp.com %CP%>nul
)
del utf-16.tmp

Впрочем, можно было бы оставит исходный пример в кодировке cp866, а  строку

chcp 1251>nul

добавить не перед чтением пользовательского ввода с выводимыми пользователю подсказками, а после них, непосредственно перед строкой с командой SET/P, выводящей BOM в файл utf-16.tmp.


___
P.S.: Обнаружил, что послуживший основой моего решения и упомянутый в исходном сообщении приём «Convert Unicode to ASCII vv» на сайте Rob van der Woude тоже содержит совет Carlos M. переключаться на кодировку ANSI (в его случае 1252) перед выводом BOM с помощью SET/P. Вероятно, я не обратил на это внимание, так как счёл, что это нужно лишь для учёта именно кодировки ANSI, а не OEM при преобразовании в/из UTF-16, хотя для языков с латинским алфавитом это менее существенно…

К слову, получить номера кодовых страниц ANSI и OEM (зависят от локализации системы) можно из реестра:

: : ANSI
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage" /v ACP

: : OEM
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage" /v OEMCP