как вы получаете длину строки в пакетном файле?

<p>кажется, нет простого способа получить длину строки в командном файле. например.,</p><pre><code>set my\_string=abcdefg set /a my\_string\_len=??? </code></pre><p>как найти длину строки <code>my\_string</code>?</p><p>бонусные баллы, если функция длины строки обрабатывает все возможные символы в строках, включая escape-символы, например: <code>!%^^()^!</code>.</p>

<p>поскольку встроенной функции для длины строки не существует, вы можете написать свою собственную функцию следующим образом: </p><pre><code>@echo off setlocal set "mystring=abcdef!%%^^()^!" call :strlen result mystring echo %result% goto :eof :strlen <resultvar> <stringvar> ( setlocal enabledelayedexpansion set "s=!%~2!#" set "len=0" for %%p in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do ( if "!s:~%%p,1!" neq "" ( set /a "len+=%%p" set "s=!s:~%%p!" ) ) ) ( endlocal set "%~1=%len%" exit /b ) </code></pre><p>эта функция всегда нуждается в 13 циклах, вместо простой функции strlen, которая нуждается в циклах strlen.<br> он обрабатывает все символы.</p>

<p>вы можете сделать это в две строки, полностью в пакетном файле, записав строку в файл и затем получив длину файла. вы просто должны вычесть два байта, чтобы учесть автоматический cr + lf, добавленный в конец.</p><p>допустим, ваша строка находится в переменной с именем <code>strvar</code>:</p><pre><code>echo %strvar%> tempfile.txt for %%? in (tempfile.txt) do ( set /a strlength=%%~z? - 2 ) </code></pre><p>длина строки теперь находится в переменной с именем <code>strlength</code>.</p><p>чуть более подробно:</p><ul> <li><code>for %%? in (filename) do ( ...</code>: получает информацию о файле</li> <li><code>set /a [variable]=[expression]</code>: вычислить выражение численно</li> <li><code>%%~z?</code>: специальное выражение для получения длины файла </li> </ul><p>разбить всю команду в одну строку:</p><pre><code>echo %strvar%>x&for %%? in (x) do set /a strlength=%%~z? - 2&del x </code></pre>

<p>я предпочитаю <a href="https://stackoverflow.com/a/5841587/1012053">принятый ответ джеба</a> - это самое быстрое из известных решений, которое я использую в своих сценариях. (на самом деле в dostips есть несколько дополнительных оптимизаций, но я не думаю, что они того стоят)</p><p>но интересно придумывать новые эффективные алгоритмы. вот новый алгоритм, который использует опцию findstr/o:</p><pre><code>@echo off setlocal set "test=hello world!" :: echo the length of test call :strlen test :: store the length of test in len call :strlen test len echo len=%len% exit /b :strlen strvar [rtnvar] setlocal disabledelayedexpansion set len=0 if defined %~1 for /f "delims=:" %%n in ( '"(cmd /v:on /c echo(!%~1!&echo()|findstr /o ^^"' ) do set /a "len=%%n-3" endlocal & if "%~2" neq "" (set %~2=%len%) else echo %len% exit /b </code></pre><p>код вычитает 3, потому что анализатор выполняет жонглирование команды и добавляет пробел перед тем, как cmd/v/c ее выполнит. это можно предотвратить с помощью <code>(echo(!%~1!^^^)</code>.</p><hr><p>для тех, кто хочет максимально возможной производительности, <a href="https://stackoverflow.com/a/5841587/1012053">jeb's answer</a> может быть принят для использования в качестве <a href="http://www.dostips.com/forum/viewtopic.php?f=3&t=2518">пакетного «макроса» с аргументами</a> . это продвинутая пакетная техника, разработанная в dostips, которая устраняет медленный процесс вызова c: подпрограммы. вы можете получить <a href="http://www.dostips.com/forum/viewtopic.php?t=1827">более подробную информацию о концепциях пакетных макросов здесь</a> , но эта ссылка использует более примитивный, менее желательный синтаксис.</p><p>ниже приведен оптимизированный макрос @strlen с примерами, показывающими различия между использованием макроса и: подпрограммы, а также различия в производительности.</p><pre><code>@echo off setlocal disabledelayedexpansion :: -------- begin macro definitions ---------- set ^"lf=^ %= this creates a variable containing a single linefeed (0x0a) character =% ^" :: define %\n% to effectively issue a newline with line continuation set ^"\n=^^^%lf%%lf%^%lf%%lf%^^" :: @strlen strvar [rtnvar] :: :: computes the length of string in variable strvar :: and stores the result in variable rtnvar. :: if rtnvar is is not specified, then prints the length to stdout. :: set @strlen=for %%. in (1 2) do if %%.==2 (%\n% for /f "tokens=1,2 delims=, " %%1 in ("!argv!") do ( endlocal%\n% set "s=a!%%~1!"%\n% set "len=0"%\n% for %%p in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (%\n% if "!s:~%%p,1!" neq "" (%\n% set /a "len+=%%p"%\n% set "s=!s:~%%p!"%\n% )%\n% )%\n% for %%v in (!len!) do endlocal^&if "%%~2" neq "" (set "%%~2=%%v") else echo %%v%\n% )%\n% ) else setlocal enabledelayedexpansion^&setlocal^&set argv=, :: -------- end macro definitions ---------- :: print out definition of macro set @strlen :: demonstrate usage set "teststring=this has a length of 23" echo( echo testing %%@strlen%% teststring %@strlen% teststring echo( echo testing call :strlen teststring call :strlen teststring echo( echo testing %%@strlen%% teststring rtn set "rtn=" %@strlen% teststring rtn echo rtn=%rtn% echo( echo testing call :strlen teststring rtn set "rtn=" call :strlen teststring rtn echo rtn=%rtn% echo( echo measuring %%@strlen%% time: set "t0=%time%" for /l %%n in (1 1 1000) do %@strlen% teststring testlength set "t1=%time%" call :printtime echo( echo measuring call :strlen time: set "t0=%time%" for /l %%n in (1 1 1000) do call :strlen teststring testlength set "t1=%time%" call :printtime exit /b :strlen strvar [rtnvar] :: :: computes the length of string in variable strvar :: and stores the result in variable rtnvar. :: if rtnvar is is not specified, then prints the length to stdout. :: ( setlocal enabledelayedexpansion set "s=a!%~1!" set "len=0" for %%p in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do ( if "!s:~%%p,1!" neq "" ( set /a "len+=%%p" set "s=!s:~%%p!" ) ) ) ( endlocal if "%~2" equ "" (echo %len%) else set "%~2=%len%" exit /b ) :printtime setlocal for /f "tokens=1-4 delims=:.," %%a in ("%t0: =0%") do set /a "t0=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100 for /f "tokens=1-4 delims=:.," %%a in ("%t1: =0%") do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100 set /a tm=t1-t0 if %tm% lss 0 set /a tm+=24*60*60*100 echo %tm:~0,-2%.%tm:~-2% msec exit /b </code></pre><p>- пример вывода -</p><pre><code>@strlen=for %. in (1 2) do if %.==2 ( for /f "tokens=1,2 delims=, " %1 in ("!argv!") do ( endlocal set "s=a!%~1!" set "len=0" for %p in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do ( if "!s:~%p,1!" neq "" ( set /a "len+=%p" set "s=!s:~%p!" ) ) for %v in (!len!) do endlocal&if "%~2" neq "" (set "%~2=%v") else echo %v ) ) else setlocal enabledelayedexpansion&setlocal&set argv=, testing %@strlen% teststring 23 testing call :strlen teststring 23 testing %@strlen% teststring rtn rtn=23 testing call :strlen teststring rtn rtn=23 measuring %@strlen% time: 1.93 msec measuring call :strlen time: 7.08 msec </code></pre>

<p>первые несколько строк просто демонстрируют функцию: strlen. </p><pre><code>@echo off set "strtomeasure=this is a string" call :strlen strtomeasure strlen echo.string is %strlen% characters long exit /b :strlen setlocal enabledelayedexpansion :strlen\_loop if not "!%1:~%len%!"=="" set /a len+=1 & goto :strlen\_loop (endlocal & set %2=%len%) goto :eof </code></pre><p>конечно, это не так эффективно в версии с 13 циклами, предоставляемой jeb. но это легче понять, и ваш компьютер 3ghz может пройти несколько тысяч итераций за небольшую долю секунды.</p>

<p>да, конечно, есть простой способ, используя vbscript (или powershell). </p><pre><code>wscript.echo len( wscript.arguments(0) ) </code></pre><p>сохранить это как <code>strlen.vbs</code> и в командной строке</p><pre><code>c:\test> cscript //nologo strlen.vbs "abcd" </code></pre><p>используйте цикл for для захвата результата (или используйте vbscript полностью для задачи сценариев)</p><p>конечно, это лучше, чем создавать громоздкие обходные пути, используя пакет, и нет никаких оправданий, чтобы не использовать его, так как vbscript доступен в каждом дистрибутиве windows (и powershell позже).</p>

<p>только что нашел ultimate решение:</p><pre><code>set "mystring=abcdef!%%^^()^!" (echo "%mystring%" & echo.) | findstr /o . | more +1 | (set /p result= & call exit /b %%result%%) set /a strlength=%errorlevel%-5 echo string "%mystring%" length = %strlength% </code></pre><p>результат:</p><pre><code>string "abcdef!%^^()^!" length = 14 </code></pre><p>он обрабатывает escape-символы, на порядок проще, чем большинство решений выше, и не содержит циклов, магических чисел, delayedexpansion, временных файлов и т.д.</p><p>в случае использования вне пакетного сценария (имеется в виду ввод команд на консоль вручную), замените ключ <code>%%result%%</code> на <code>%result%</code>.</p><p>при необходимости переменная <code>%errorlevel%</code> может быть установлена в <code>false</code> с помощью любой команды nop, например, <code>echo. >nul</code></p>

<p>если вы используете windows vista +, попробуйте этот метод powershell:</p><pre><code>for /f %%l in ('powershell $env:my_string.length') do ( set my_string_len=%%l ) или альтернативно:powershell $env:my\_string.length > %temp%\tmpfile.txt set /p my\_string\_len = < %temp%\tmpfile.txt del %temp%\tmpfile.txt </code></pre><p>я на windows 7 x64, и это работает для меня.</p>

<p>мне нравится <a href="https://stackoverflow.com/a/8566001/463115">двухстрочный подход</a> из jmh\_gr.</p><p>он не будет работать с однозначными числами, если вы не поместите <code>()</code> вокруг части команды перед перенаправлением. поскольку <code>1></code> - это специальная команда «echo is on» будет перенаправлена в файл.</p><p>этот пример должен заботиться об однозначных числах, но не о других специальных символах, таких как <code><</code>, которые могут быть в строке.</p><pre><code>(echo %strvar%)> tempfile.txt </code></pre>

<pre><code>@echo off & setlocal enabledelayedexpansion set var=finding the length of strings for /l %%a in (0,1,10000) do if not "%var%"=="!var:~0,%%a!" (set /a length+=1) else (echo !length! & pause & exit /b) </code></pre><p>установите переменную на любую длину, которую вы хотите найти, или измените ее на set/p var =, чтобы пользователь ввел ее . поместите это здесь для дальнейшего использования.</p>

<p>просто еще один пакетный скрипт для расчета длины строки всего за несколько строк. возможно, он не самый быстрый, но довольно маленький. подпрограмма ": len" возвращает длину во втором параметре. первый параметр - это текущая строка, которая анализируется обратите внимание: специальные символы должны быть экранированы, как в случае с любой строкой в пакетном файле.</p><pre><code>@echo off setlocal call :len "sample text" a echo the string has %a% characters. endlocal goto :eof :len <string> <length\_variable> - note: string must be quoted because it may have spaces setlocal enabledelayedexpansion&set l=0&set str=%~1 :len\_loop set x=!str:~%l%,1!&if not defined x (endlocal&set "%~2=%l%"&goto :eof) set /a l=%l%+1&goto :len\_loop </code></pre>

<p>это<strong> намного проще </strong>!</p><p>чистый пакетный раствор. нет временных файлов. нет длинных скриптов.</p><pre><code>@echo off setlocal enabledelayedexpansion set string=abcde12345 for /l %%x in (1,1,1000) do ( if "!string:~%%x!"=="" set lenght=%%x & goto result ) :result echo lenght: !lenght! </code></pre><p><code>1000</code> - это максимальная предполагаемая длина строки. измените его в зависимости от ваших потребностей.</p>

<pre><code>@echo off :: warning doesn't like * ( in mystring setlocal enabledelayedexpansion set mystring=this is my string to be counted forty one call :getsize %mystring% echo count=%count% of "%mystring%" set mystring=this is my string to be counted call :getsize %mystring% echo count=%count% of "%mystring%" set mystring=this is my string call :getsize %mystring% echo count=%count% of "%mystring%" echo. pause goto :eof :: get length of mystring line ######### subroutine getsize ######## :getsize set count=0 for /l %%n in (0,1,2000) do ( set chars= set chars=!mystring:~%%n! if defined chars set /a count+=1 ) goto :eof :: ############## end of subroutine getsize ######################## </code></pre>

<p>я хочу предвосхитить это, сказав, что я мало знаю о написании кода/скрипта/и т.д. но думал, что поделюсь решением, которое, кажется, придумал. большинство ответов здесь, вроде как, пошли мне на ум, поэтому мне было любопытно узнать, сопоставимо ли то, что я написал.</p><pre><code>@echo off set stringlength=0 call:stringeater "it counts most characters" echo %stringlength% echo.&pause&goto:eof :stringeater set var=%~1 :substring set n=%var:~0,1% if "%n%"=="" ( goto:eof ) else if "%n%"==" " ( set /a stringlength=%stringlength%+1 ) else ( set /a stringlength=%stringlength%+1 ) set var=%var:~1,1000% if "%var%"=="" ( goto:eof ) else ( goto substring ) goto:eof </code></pre>