У меня есть код для BATCH
@echo off
setlocal enabledelayedexpansion
for /d %%a in ("%cd%") do set "directory_name=%%~nxa"
powershell -Command "$content = [IO.File]::ReadAllText('file.txt'); $content = $content -replace '\s\u003Cmod((?:.|\n)).*%directory_name: =_%_v((?:.|\n))*?\u003C/mod\u003E', ''; [IO.File]::WriteAllText('file.txt', $content, [System.Text.Encoding]::UTF8)"
endlocal
pause
Он работал нормально и делал все, что я хотел. Пока я не получил имя папки blabla_-_blabla's_bla. Итак, апостроф теперь является нарушением синтаксиса PowerShell и не может быть завершен. Но... если я пытаюсь изменить символ апострофа на Юникод \u0027, все работает нормально.
любая идея, как преобразовать через BATCH любые конкретные символы в Юникод (исключая латиницу, «_» и «-») перед вводом в код PowerShell через %directory_name: =_% ?
Мы называем это XY-проблемой. Отложенное расширение переменной вообще не требуется, но приводит к неправильной интерпретации имени каталога с одним или несколькими !
. Опция ЗА /D
вообще не нужна. Ссылка на динамическую переменную CD
также не нужна, поскольку .
тоже можно использовать. Текущим каталогом может быть любой каталог при запуске cmd.exe
для обработки пакетного файла, как его определяет родительский процесс. Текущий каталог не должен быть каталогом, содержащим пакетный файл. Пакетный файл вообще не нужен, поскольку Powershell имеет все необходимые функции.
Было бы лучше отредактировать ваш вопрос и описать, что следует делать с помощью PowerShell, и эксперт по кодированию PowerShell обязательно напишет единственную командную строку, необходимую для выполнения работы, используя только PowerShell (и файл ярлыка с этой единственной командной строкой) .
это не XY. У меня есть большой BATCH-скрипт, который делает много вещей. Поэтому я отделил место кода проблемы. Я не думаю, что у кого-то есть время прочитать весь код, понять его и провести рефакторинг. У меня есть конкретная проблема, которую, я надеюсь, кто-нибудь знает, как решить. Вы написали стену текста и ничего полезного. Пустая трата времени
Применить стандарт. Правило цитирования: используйте $content -replace ""\s\u003Cmod((?:.|\n)).*%directory_name: =_%_v((?:.|\n))*?\u003C/mod\u003E"", '';
вместо $content -replace '\s\u003Cmod((?:.|\n)).*%directory_name: =_%_v((?:.|\n))*?\u003C/mod\u003E', '';
. Протестировано с использованием powershell -noprofile -Command "write-output ""\s\u003Cmod((?:.|\n)).*%directory_name: =_%_v((?:.|\n))*?\u003C/mod\u003E"""
(хорошо) и (неправильно) powershell -noprofile -Command "write-output '\s\u003Cmod((?:.|\n)).*%directory_name: =_%_v((?:.|\n))*?\u003C/mod\u003E'"
, что приводит к сообщению об ошибке The string is missing the terminator: '.
Кстати, в целях тестирования использовался жестко запрограммированный set "directory_name=blabla - blabla's bla"
…
Просто используйте powershell.
@JosefZ спасибо Перед вашим комментарием я нашел решение для двойных кавычек. Я попробовал ваш пример и получил ошибку: The string is missing the terminator: ".
Чтобы исправить проблему, это должно быть так $content -replace ""\s\u003Cmod((?:.|\n)).*%directory_name: =_%_v((?:.|\n))*?\u003C/mod\u003E"", """";
Но это не решает проблему с правилом регулярного выражения. Одинарная кавычка нарушает правило регулярного выражения, а «-replace» не делает того, что должно. Я имею в виду, что это не замена, потому что имя «blabla's bla» содержит эту цитату.
Предисловие:
Показанный ниже метод надежно передает значение, хранящееся в переменной пакетного файла, вызову PowerShell CLI (с использованием либо powershell.exe (Windows PowerShell), либо pwsh.exe для PowerShell (Core) 7). , какие бы символы он ни содержал, поэтому не требуется никаких предварительных знаний об этом (но см. предостережение о кодировке символов в нижнем разделе).
Ситуативно, если вы знаете, что значение никогда не содержит "
, но может содержать '
, как в рассматриваемом случае, переключение с использования встроенного '...'
кавычек (одинарных кавычек) на использование встроенного "..."
кавычек (двойных кавычек) в сочетании с предварительным как отмечает JosefZ, возможна интерполяция строк с помощью cmd.exe
; однако:
Вам нужно не только помнить о том, как на литеральные части такой встроенной строки может влиять строковая интерполяция PowerShell в таких расширяемых (интерполирующих) строках ("..."), предварительно развернутом значении сам по себе может стать объектом нежелательной интерпретации, такой как удаление символов `
и непреднамеренное расширение токенов с префиксом $
.
Надежная передача встроенной строки "..."
внутри общей строки "..."
, содержащей код PowerShell, переданный в -Command
из пакетного файла (cmd.exe
), является громоздким, поскольку потенциально возможно нарушение правил синтаксического анализа cmd.exe
. См. этот ответ для объяснения и обходных путей.
Чтобы избежать головной боли при цитировании и экранировании, сделайте ссылочные переменные PowerShell, установленные в пакетных файлах, в качестве переменных среды (все переменные, установленные в пакетных файлах, неизменно также являются переменными среды и, следовательно, видны дочерним процессам).
То есть вместо того, чтобы использовать предварительную интерполяцию строк cmd.exe
через ссылки, такие как %directory_name%
, встроенные в команду, передаваемую -Command
, сделайте так, чтобы команда ссылалась на значение переменной пакетного файла directory_name
следующим образом, используя синтаксис PowerShell для доступа к переменные среды:
$env:directory_name
В вашем случае это также упрощает применение [regex]::Escape()
к значению переменной, что необходимо для обеспечения того, чтобы значение обрабатывалось буквально внутри регулярного выражения , которое вы передаете -replace
(обратите внимание, что $env:directory_name -replace ' ', '_'
является отложенным эквивалентом замена пробелов на _
посредством предварительной интерполяции строки на cmd.exe
, %directory_name: =_%
):
[regex]::Escape(($env:directory_name -replace ' ', '_'))
В рассматриваемом случае, когда интересующим значением является имя текущего рабочего каталога, вы также можете позволить PowerShell определить его:
[regex]::Escape(((Split-Path -Leaf $PWD) -replace ' ', '_'))
Поскольку вы используете '...'
(одинарные кавычки) для регулярного выражения, передаваемого операции $content -replace ...
, вы должны объединить результат приведенного выше выражения в эту строку посредством конкатенации строк (+
) и заключить операцию в (...)
, чтобы уточнить приоритет операторов:
$content -replace ('\s\u003Cmod((?:.|\n)).*' + [regex]::Escape(($env:directory_name -replace ' ', '_')) + '_v((?:.|\n))*?\u003C/mod\u003E'), ''
Чтобы собрать все это вместе, используя упрощенную версию командного файла:
@echo off
setlocal
:: Get the name of the current directory.
for %%a in (.) do set "directory_name=%%~nxa"
:: Invoke PowerShell and make it obtain the value of %directory_name%
:: via $env:directory_name
powershell -Command "$content = [IO.File]::ReadAllText('file.txt'); $content = $content -replace ('\s\u003Cmod((?:.|\n)).*' + [regex]::Escape(($env:directory_name -replace ' ', '_')) + '_v((?:.|\n))*?\u003C/mod\u003E'), ''; [IO.File]::WriteAllText('file.txt', $content, [System.Text.Encoding]::UTF8)"
pause
Общее предостережение относительно кодировки символов пакетного файла (поскольку в заголовке упоминается Unicode):
Чтобы также поддерживать символы, отличные от ASCII, убедитесь, что кодировка символов вашего командного файла соответствует кодовой странице активной консоли, как сообщает chcp.com
Для полной поддержки Unicode через UTF-8:
Сохраните командные файлы в формате UTF-8 без спецификации.
Переключите активную кодовую страницу окна консоли на 65001
(UTF-8):
Примечание:
В Windows 10 и выше есть возможность переключиться на UTF-8 для всей системы на постоянной основе, но это имеет далеко идущие последствия — см. этот ответ.
Специальное переключение на UTF-8 влияет не только на данный пакетный файл, но и на другие процессы, которые могут запускаться позже в том же окне консоли. Если это нежелательно, необходимо сохранить исходную кодовую страницу и восстановить ее позже.
В сеансах оболочки cmd.exe
либо запустите chcp 65001
перед вызовом командного файла, либо поместите его сразу после @echo off
в командном файле (как уже отмечалось, это также влияет на процессы, запускаемые позже в том же окне консоли).
Если вы хотите, чтобы все будущие cmd.exe
сеансы по умолчанию использовали UTF-8 (без вышеупомянутого общесистемного переключения), вы можете использовать команду configure для запуска chcp 65001
каждый раз при создании cmd.exe
процесса:
reg.exe add "HKCU\Software\Microsoft\Command Processor" /v AutoRun /d "chcp 65001 >NUL"
Обратите внимание на использование >NUL
для отключения вывода chcp.com
; удалите его, если предпочитаете напоминание о том, что команда запускалась при запуске.
Конфигурация специфична для текущего пользователя. Если у вас есть права администратора, вы можете альтернативно выбрать куст HKLM
вместо HKCU
из сеанса с повышенными правами, чтобы настроить поведение для всех пользователей.
В сеансах PowerShell использование chcp 65001
невозможно, поскольку .NET кэширует кодировки и не уведомляется об изменении.
Вместо этого используйте следующее магическое заклинание (оно неявно устанавливает кодовую страницу на 65001
, одновременно информируя .NET об изменении; подробности см. в этом ответе):
$OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
Если вы хотите, чтобы для всех будущих сеансов PowerShell по умолчанию использовалась UTF-8 (без вышеупомянутого общесистемного переключения), вы можете добавить указанное выше в свой файл $PROFILE.
Файл $PROFILE
относится к текущему пользователю и хост-программе (обычно это окно консоли). Альтернативно, вы можете сохранить команду в файле $PROFILE.CurrentUserAllHosts
, чтобы настроить таргетинг на все хосты текущего пользователя, и — при условии, что у вас есть права администратора — в аналогичных файлах $PROFILE.AllUsersCurrentHost
и $PROFILE.AllUsersAllHosts
, предназначенных для всех пользователей, из сеанса с повышенными правами.
$content -replace ('\s\u003Cmod((?:.|\n)).' + [regex]::Escape(($env:directory_name -replace ' ', '_')) + '_v((?:.|\n))*?\u003C/mod\u003E'), ''
это тоже работает, но ты забыл * характер, так и должно быть $content -replace ('\s\u003Cmod((?:.|\n)).*' + [regex]::Escape(($env:directory_name -replace ' ', '_')) + '_v((?:.|\n))*?\u003C/mod\u003E'), ''
Спасибо за указание на это, @AleksandrPodaruev - я исправил ответ.
Что позволяет вам думать, что PowerShell, являющийся гораздо более мощным преемником командного процессора Windows, не способен получить имя текущего рабочего каталога? Абсолютно нет необходимости обрабатывать пакетный файл
cmd.exe
просто для того, чтобы получить имя текущего рабочего каталога со всеми ограничениямиcmd
и передать это имя каталога в PowerShell в командной строке. Посмотрите результаты поиска по переполнению стека [powershell] получите текущее имя каталога.