Count number of letters in CMD and batchfile - windows

I have this project using batch file.My code below can count characters when user type inputs (numbers, letters, symbols),but i want the program count only letters. Can you help me out with this problem. My code is shown below.
#ECHO OFF
echo.
:a
REM Set "string" variable
SET /p string="Type characters to Count:"
REM Set the value of temporary variable to the value of "string" variable
SET temp_str=%string%
REM Initialize counter
SET str_len=1
:loop
if defined temp_str (
REM Remove the first character from the temporary string variable and increment
REM counter by 1. Countinue to loop until the value of temp_str is empty string.
SET temp_str=%temp_str:~1%
SET /A str_len += 1
GOTO loop
)
REM Echo the actual string value and its length.
ECHO %string% is %str_len% characters long!
set /p prompt="Do you want to continue?Y/N:"
if %prompt%==Y goto a
goto b
:b
pause
exit

#ECHO OFF
setlocal enableextensions disabledelayedexpansion
:getString
echo(
REM Set "string" variable
set "string="
SET /p "string=Type characters to Count:"
:calculateLength
if not defined string ( set "str_len=0" ) else (
for /f %%a in ('
cmd /u /v /q /c"(echo(!string!)" %= output unicode string =%
^| find /v "" %= separate lines =%
^| findstr /r /c:"[a-zA-Z]" %= filter characters =%
^| find /c /v "" %= count remaining lines =%
') do set "str_len=%%a"
)
:show
REM Echo the actual string value and its length.
ECHO [%string%] is %str_len% letters long!
echo(
set /p "prompt=Do you want to continue? Y/n: " || set "prompt=Y"
if /i "%prompt%"=="Y" goto :getString
pause
exit /b
This uses a simple trick. If we start a unicode cmd instance, its output (the result from echo in this case) is unicode, that is, two bytes per character, usually a null and the real character.
This output is processed by a filter, more or find, that will process the nulls as line terminators, splitting the output string into lines, one line for each input character.
This set of lines are filtered with findstr to only allow letters. The remaining lines are counted with a find /c /v "" command that will count non empty lines.
edited to adapt to comments.
Input a block of lines and process them
#ECHO OFF
setlocal enableextensions disabledelayedexpansion
set "tempFile=%temp%\%random%%random%%random%.tmp"
:getString
echo(
echo(Please, type your text and end input pressing F6 and Enter
copy con "%tempfile%" >nul 2>nul
:calculateLength
for /f %%a in ('
cmd /u /q /c"(type "%tempFile%")" %= output unicode string =%
^| find /v "" %= separate lines =%
^| findstr /r /c:"[a-zA-Z]" %= filter characters =%
^| find /c /v "" %= count remaining lines =%
') do set "str_len=%%a"
:show
ECHO input data is %str_len% letters long!
echo(
del /q "%tempFile%" 2>nul
set /p "prompt=Do you want to continue? Y/n: " || set "prompt=Y"
if /i "%prompt%"=="Y" goto :getString
pause
exit /b

You are setting initial string length to 1 in line
SET str_len=1
change it to 0 like so:
SET str_len=0

#ECHO OFF
setlocal EnableDelayedExpansion
REM Define a string with all letters:
SET letters=ABCDEFGHIJKLMNOPQRSTUVWXYZ
echo.
:a
REM Set "string" variable
SET /p string="Type characters to Count:"
REM Set the value of temporary variable to the value of "string" variable
SET temp_str=%string%
REM Initialize counter
SET str_letters=0
:loop
if defined temp_str (
REM Get the first character from temporary string variable
SET char=%temp_str:~0,1%
REM If the character is letter, count it
if "!letters:%char%=!" neq "%letters%" SET /A str_letters += 1
REM Remove the first character from the temporary string variable.
REM Countinue to loop until the value of temp_str is empty string.
SET temp_str=%temp_str:~1%
GOTO loop
)
REM Echo the actual string value and its number of letters.
ECHO %string% have %str_letters% letters
set /p prompt="Do you want to continue?Y/N:"
if %prompt%==Y goto a
goto b
:b
pause
exit

Related

Eliminate strings with wildcard in batch file variable

Just noticed that VAR=%VAR:*STRING% does eliminate the previous string but VAR=%VAR:STRING*% doesn't elimiate next string
so how to eliminate the next string ? my current code is :
:CheckEnvironmentVariable Location Variable Value
IF [%1] EQU [System] (
ECHO Querying system
) ELSE (
IF [%1] EQU [User] (
ECHO Querying User Environments
FOR /F "usebackq tokens=*" %%x IN (`REG QUERY "HKCU\Environment"`) DO (
SET CURRVARS=%%x&&SET CURRVARS=!CURRVARS:REG_*=!
ECHO !CURRVARS!
)
) ELSE (
ECHO ERROR ^^! Invalid Environment Variable Location "%1"
)
)
EXIT /B
which is doen't work as expected
I am afraid I don't understand what you want to do. However, I guess that is related to this "possible" solution:
#echo off
setlocal EnableDelayedExpansion
set "VAR=This is a STRING long value"
echo VAR: %VAR%
rem Eliminate string "previous" to "STRING" (including it)
set "tail=%VAR:*STRING=%"
echo Tail: "%tail%"
rem Eliminate string "next" to "STRING" (not including it)
set "head=!VAR:%tail%=!"
echo Head1: "%head%"
rem Eliminate string "next" to "STRING" (including it)
set "head=!VAR:STRING%tail%=!"
echo Head2: "%head%"
Output:
VAR: This is a STRING long value
Tail: " long value"
Head1: "This is a STRING"
Head2: "This is a "
Based upon the code you have submitted, I'm not even sure why you would want to try to split the string at that particular place. There is a consistent string in every single line that would be returned by your reg.exe command, and that is REG_. The beauty of that paricular string is that it will always be non space separated, non localized, and never contain special characters. If you split at that point, you know that the substring you're looking for, will always be every token following its remainder, e.g. EXPAND_SZ your string(s); SZ your string(s).
So here's some example code which uses that method, but please be aware that it wil not work as intended should you have any variable defined within the System or User Environments with names including the case insensitive string REG_:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
:Ask4Var
ClS
Set "sName="
Set /P "sName=Please enter the name of the variable you wish to verify>"
If Not Defined sName GoTo Ask4Var
(
Set %sName%
) 2>NUL | %SystemRoot%\System32\findstr.exe /BIL "%sName%=" 1>NUL || (
Echo There is no variable named %sName% in the current environment
%SystemRoot%\System32\timeout.exe /T 3 1>NUL
GoTo Ask4Var
)
Set "Env=System"
Set "RootKey=HKLM"
Set "SubKey=System\CurrentControlSet\Control\Session Manager\Environment"
%SystemRoot%\System32\choice.exe /C SU /N /M "[S]ystem OR [U]ser?"
If ErrorLevel 2 (
Set "Env=User"
Set "RootKey=HKCU"
Set "SubKey=Environment"
)
Set "Reg=%SystemRoot%\System32\reg.exe"
Set "ValueString="
For /F Delims^=^ EOL^= %%G In (
'%Reg% Query "%RootKey%\%SubKey%" /V /F "%sName%" /E ^|
%SystemRoot%\System32\find.exe "REG_"'
) Do (
Set "Result=%%G"
SetLocal EnableDelayedExpansion
For /F "Tokens=1,*" %%H In ("!Result:*REG_=!") Do (
EndLocal
Set "ValueString=%%I"
)
)
If Not Defined ValueString (
Echo There is no variable named %sName% in the %Env% environment
%SystemRoot%\System32\timeout.exe /T 3 1>NUL
GoTo Ask4Var
)
Echo The expanded string value of %sName% is %ValueString%.
%SystemRoot%\System32\timeout.exe /T 7 1>NUL
GoTo :EOF

Batch - Get block of text between flags, output and iterate over all files

I've got a bunch of text files in a directory that have a block of text I want to extract between two strings into a new text file of a similar name. I've got the single file working but think I've come unstuck with looping through all .txt files. Maybe at the "goto" command?
Here is the original, single file code I used:
Batch File - Find two lines then copy everything between those lines
~Top Break
foobar
~ more data title
more foobar
~Bottom Break
Garbage data
I have this code that works for a single file called FileNumber1.txt.
#echo off
set "FIRSTLINE=~Top Break"
set "LASTLINE=~Bottom Break"
set "INFILE=FileNumber1.txt"
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "%INFILE%_MyData.txt" (
rem findstr configured so that each line in a file is given a "1:" number and colon.
for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem this LINE=!LINE:*:=! removes the any character before the Colon. *:
set "LINE=!LINE:*:=!"
rem this block of code checks to see if line of text = Firstline variable, if so FLAG = TRUE
if "!LINE!"=="%FIRSTLINE%" (
endlocal
set "FLAG=TRUE"
rem this block of code checks to see if line of text = Lastline variable, if so goto :Continue and end the loop
) else if "!LINE!"=="%LASTLINE%" (
endlocal
goto :CONTINUE
) else if defined FLAG (
echo(#!LINE!
endlocal
) else (
endlocal
)
)
)
:CONTINUE
endlocal
NewFile1_MyData.txt Output:
foobar
~ more data title
more foobar
I've tried to wrap this in another "FOR" loop that looks for all txt files in the same directory.
This is my code that isn't working.
#echo off
set "FIRSTLINE=~Top Break"
set "LASTLINE=~Bottom Break"
for /F %%f in (*.txt) do (
set "INFILE=%%f"
setlocal EnableExtensions DisableDelayedExpansion
set "FLAG="
> "%INFILE%_OldHeader.txt" (
rem findstr configured so that each line in a file is given a "1:" number and colon.
for /F "delims=" %%L in ('findstr /N "^" "%INFILE%"') do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem this LINE=!LINE:*:=! removes the any character before the Colon. *:
set "LINE=!LINE:*:=!"
rem this block of code checks to see if line of text = Firstline variable, if so FLAG = TRUE
if "!LINE!"=="%FIRSTLINE%" (
endlocal
set "FLAG=TRUE"
rem this block of code checks to see if line of text = Lastline variable, if so goto :Continue and end the loop
) else if "!LINE!"=="%LASTLINE%" (
endlocal
goto :CONTINUE
) else if defined FLAG (
echo(#!LINE!
endlocal
) else (
endlocal
)
)
)
endlocal
:CONTINUE
))
The Command window gets to the "for /F" statement and exits.
Mmm... I would change the method to extract the lines for a simpler one based on lines to skip at beginning of file and number of lines to extract. After that, I would use a for to process all files and call a subroutine to extract the lines:
#echo off
setlocal EnableDelayedExpansion
set "FirstLine=~Top Break"
set "LastLine=~Bottom Break"
rem Process all text files in this folder
for %%f in (*.txt) do (
rem Search for First line and Number of lines
set "FirstNum="
for /F "delims=:" %%n in ('findstr /C:"%FirstLine%" /C:"%LastLine%" /N "%%f"') do (
if not defined FirstNum (
set "FirstNum=%%n"
) else (
set /A "LastNum=%%n-FirstNum-1"
)
)
rem Copy the lines
call :CopyLines >"%%~Nf_MyData.out" "%%f", !FirstNum!, !LastNum!
)
ren *.out *.txt
goto :EOF
:CopyLines File, Skip, Num
set "Num=%3"
for /F "usebackq skip=%2 delims=" %%a in (%1) do (
setlocal DisableDelayedExpansion
echo %%a
endlocal
set /A Num-=1
if !Num! equ 0 exit /B
)
exit /B

Creating Each line of text as variable and them constantly changing in a loop in batch

So what I'm trying to do is create a find for multiple people where it in the text file it will say names and numbers like
Example of text file:
Beth
1234567891
Jay
2134456544
This is the best way I can explain what I'm trying to do:
#echo off
set "file=Test1.txt"
setlocal EnableDelayedExpansion
<"!file!" (
for /f %%i in ('type "!file!" ^| find /c /v ""') do set /a n=%%i && for /l %%j in (1 1 %%i) do (
set /p "line_%%j="
)
)
set /a Name=1
set /a Number=2
Echo Line_%Name%> %Name%.txt (Im trying to get this to say line_2 to say 1st line in the text file)
Echo Line_%Number%> %Name%.txt (Im trying to get this to say line_2 to say 2nd line in the text file)
:Start
set /a Name=%Name%+2 (These are meant to take off after 1 so lines 3,5,7,9 so on)
set /a Number=%Number%+2 (These are meant to take off after 2 so lines 4,6,8,10 so on)
Echo Line_%Name%
Echo Line_%Number%
GOTO :Start
so the outcome would be
In Beth.txt:
Beth
1234567891
So every name will be a file name and the first line in a file. I will change it later so I can do a addition in each text file.
Name: Beth
Number: 1234567891
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
SET "filename1=%sourcedir%\q65417881.txt"
rem make sure arrays are empty
For %%b IN (name number) DO FOR /F "delims==" %%a In ('set %%b[ 2^>Nul') DO SET "%%a="
rem Initialise counter and entry array
SET /a count=0
SET "number[0]=dummy"
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO (
IF DEFINED number[!count!] (SET /a count+=1&SET "name[!count!]=%%a") ELSE (SET "number[!count!]=%%a")
)
rem clear out dummy entry
SET "number[0]=dummy"
FOR /L %%c IN (1,1,%count%) DO (
rem replace spaces with dashes
SET "name[%%c]=!name[%%c]: =-!"
rem report to console rem report to console
ECHO Name: !name[%%c]! Number: !number[%%c]!
rem generate name.txt file
(
ECHO !name[%%c]!
ECHO !number[%%c]!
)>"%destdir%\!name[%%c]!.txt"
)
GOTO :EOF
You would need to change the values assigned to sourcedir and destdir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
I used a file named q65417881.txt containing your data for my testing.
The line data read from the file is assigned to %%a is assigned to and number[!count!] alternately. The data is retained in these arrays for use by further processing.
[Edited to include conversion of spaces within names to dashes]
If I understand correctly, you want to precede every second line with Number: + SPACE and every other line with Name: + SPACE. For this you do not need to store each line in a variable first, you can use a single for /F loop lo read the file line by line and process every line individually. There are two possibilities:
Temporarily precede every line with a line number plus : using findstr /N:
#echo off
rem // Loop through lines and precede each with line number plus `:`:
for /F "tokens=1* delims=:" %%K in ('findstr /N "^" "Test1.txt"') do (
rem // Calculate remainder of division by two:
set /A "MOD=%%K%%2" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !MOD! neq 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This will fail when a line begins with the a :.
Check whether numeric representation of current line string is greater than 0:
#echo off
rem // Loop through (non-empty) lines:
for /F "usebackq delims=" %%L in ("Test1.txt") do (
rem // Determine numeric representation of current line string:
set /A "NUM=%%L" 2> nul
rem // Toggle delayed expansion to avoid issues with `!`:
setlocal EnableDelayedExpansion
rem // Conditionally return line string with adequate prefix:
if !NUM! equ 0 (
endlocal & echo Name: %%L
) else (
endlocal & echo Number: %%L
)
)
This fails when a name begins with numerals and/or when a numeric line is 0.
And just for the sake of posting something different:
#SetLocal EnableExtensions DisableDelayedExpansion & (Set LF=^
% 0x0A %
) & For /F %%G In ('Copy /Z "%~f0" NUL') Do #Set "CR=%%G"
#For /F "Tokens=1,2* Delims=:" %%G In ('%__AppDir__%cmd.exe /D/V/C ^
"%__AppDir__%findstr.exe /NR "^[a-Z]*!CR!!LF![0123456789]" "Test1?.txt" 2>NUL"
') Do #(SetLocal EnableDelayedExpansion
(Set /P "=Name: %%I!CR!!LF!Number: " 0<NUL & Set "_="
For /F Delims^=^ EOL^= %%J In ('%__AppDir__%more.com +%%H "%%G"') Do #(
If Not Defined _ Set "_=_" & Echo %%J)) 1>"%%I.txt" & EndLocal)
This file should be run with the Test1.txt file in the current working directory. It is important that along side Test1.txt, there are no other .txt files with the same basename followed by one other character, (for example Test1a.txt or Test12.txt). Should you wish to change your filename, just remember that you must suffix its basename in the above code with a ? character, (e.g. MyTextFile.log ⇒ MyTextFile?.log).
I had the rare opportunity to verify that this script worked against the following example Test1.txt file:
Beth
1234567891
Jay
2134456544
Bob
2137856514
Jimmy
4574459540
Mary
3734756547
Gemma
6938456114
Albert
0134056504

Get a variable from filename string in windows batch

good day, i have a folder with 400 files and i want to print a "name" from the filenames
this is the structure
ej:
20201323223_vendo.perfil01_17872513294967257_1601950878_live.mp4
20201323223__vvcastrillon_12_17949951031375250_1601939874_live.mp4
2020323123_yessromero.g_17849208194340047_1601945592_live.mp4
2020323223_ziizii_08_17979840166310477_1601929868_live.mp4
and what i need is
vendo.perfil01
_vvcastrillon_12
yessromero.g
ziizii_08
Im try to loop in the files and separate whit the _ and extract the 2 and 3 token numeral conditioning but the result is wrong and missing variables
#echo off
setlocal EnableDelayedExpansion
:loop
SET max=5000
for /F "delims=" %%I in ('dir "*_*_*.mp4" /A-D /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /L /V "\\outdir\\"') do (for /F "eol=| tokens=2,3 delims=_" %%J in ("%%~nI") do (SET "var="&for /f "delims=0123456789" %%a in ("%%K") do SET var=%%a
if defined var ( set nam=%%J_%%K ) else ( set nam=%%J )
)
echo/!nam!
)
timeout 10 > nul
goto loop
i think the answer is remove the first number before the _ then the string _xxxxxx_xxxxxxx_live.mp4 at the end but i dont know how read in reverse the tokens
tanks for any help
Since you have got different numbers of _-separated items in your file names and even adjacent _, so using for /F to split them into specific tokens with delims=_ is not the best choice.
I would use a standard for-loop instead, which receives modified file names, namely with each _ replaced by " " and enclosed within "", which leads to SPACE-separated partial name items. So:
20201323223_vendo.perfil01_17872513294967257_1601950878_live.mp4
is changed to:
"20201323223" "vendo.perfil01" "17872513294967257" "1601950878" "live.mp4"
before looping. Within the loop implement an index counter and just append those items to a buffer whose index numbers lie within a certain range that depends on the total number of items.
Here is an example script that demonstrates what I mean:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=%~dp0." & rem // (full path to target directory)
set "_MASK=*_*_*_*_live.mp4" & rem // (pattern to match file names)
set "_FILT=^[0123456789][0123456789]*_..*_[0123456789][0123456789]*_[0123456789][0123456789]*_live\.mp4$"
rem // (additional `findstr` filter for file names)
set /A "_POS=1" & rem /* (index of first `_`-separated item to extract;
rem `0` is the first item, `-1` is the last) */
set /A "_NUM=-4" & rem /* (number of `_`-separated items to extract;
rem `-1` means all up to the last item) */
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop through all matching non-hidden, non-system files:
for /F "delims= eol=|" %%F in ('
dir /B /A:-D-H-S "%_MASK%" ^| findstr /I /R /C:"%_FILT%"
') do (
rem // Store current file name, initialise item index, counter and buffer:
set "FILE=%%F" & set /A "IDX=-1, CNT=-1" & set "BUFF=_"
rem // Toggle delayed expansion to avoid troubles with `!`:
setlocal EnableDelayedExpansion
rem // Count number of items and store one less:
rem set "TEST=%FILE:_=" & set /A "CNT+=1" & set "TEST=%"
for %%I in ("!FILE:_=" "!") do set /A "CNT+=1"
rem // Determine item index position from given index and number:
if !_POS! lss 0 (set /A "BEG=CNT+_POS+1") else set /A "BEG=_POS"
if !_NUM! lss 0 (set /A "END=CNT+_NUM+1") else set /A "END=_POS+_NUM-1"
rem // Transport numbers over `endlocal` barrier:
for %%C in (!CNT!) do for %%B in (!BEG!) do for %%A in (!END!) do (
rem // Loop through `_`-separated items of file name:
for %%I in ("!FILE:_=" "!") do (
rem // Store current item, increment item index:
endlocal & set "ITEM=%%~I" & set /A "IDX+=1"
setlocal EnableDelayedExpansion
rem // Append current item to buffer if in range:
if !IDX! geq %%B if !IDX! leq %%A (
rem // Transport buffer over `endlocal` barrier:
for /F "delims=" %%E in ("BUFF=!BUFF!_!ITEM!") do (
endlocal & set "%%E"
setlocal EnableDelayedExpansion
)
)
)
)
rem // Return buffer:
echo(!BUFF:~2!
endlocal
)
rem // Return from target directory:
popd
)
endlocal
exit /B
Something like this should help:
#echo off
setlocal enabledelayedexpansion
for /f "tokens=1* delims=_." %%i in ('dir /b /s /a-d "*_*_*.mp4"2^>nul ^| findstr.exe /ILV "\\outdir\\"') do (
set "var=%%j"
for /f "tokens=2,* delims=_." %%a in ("%%j") do echo !var:_%%b=!
)
Keep in mind that using delims will also split on consecutive characters like double underscore. for those you need to predetermine which has double underscore and let the script add it for you.
#ECHO OFF
SETLOCAL
for %%a in (
20201323223_vendo.perfil01_17872513294967257_1601950878_live.mp4
20201323223__vvcastrillon_12_17949951031375250_1601939874_live.mp4
2020323123_yessromero.g_17849208194340047_1601945592_live.mp4
2020323223_ziizii_08_17979840166310477_1601929868_live.mp4
) do set "filename=%%a" &call :process&echo --------------------------------------
GOTO :EOF
:process
echo stage 1 %filename%
:: step 1 : delete all characters up to and including the first underscore
set "filename=%filename:*_=%"
echo stage 2 %filename%
:: step 2 : find all numeric strings of length 4 or more in remainder
call :strsgt4 %filename:_= %
:: step 3 : replace each numeric string of length 4 or more + preceding underscore with "/" (invalid filename character)
echo stage 3 %filename%
:proc3lp
if "%zapstrings%" neq " " for %%v in (%zapstrings%) do call set "filename=%%filename:_%%v=/%%"&call set "zapstrings=%%zapstrings: %%v=%%"&goto proc3lp
echo stage 4 %filename%
:: step 5 : Remove all charactersincluding and after the first "/"
for /f "delims=/" %%v in ("%filename%") do echo result %%v
goto :eof
:strsgt4
set "zapstrings= "
:strsgt4loop
set "test=%1"
if not defined test goto :eof
set "test=%test:~4%"
if defined test call :isnum %test%&if not defined notnumber set "zapstrings=%zapstrings% %1"
shift
goto strsgt4loop
:: Determine whether %1 is purely numeric
:isnum
SET "notnumber=9%~1"
FOR /l %%z IN (0,1,9) DO CALL SET "notnumber=%%notnumber:%%z=%%"
GOTO :eof
Really a question of working out what your rules are.
I decided that your rules were: string after the first underscore, until before the first subsequent underscore that precedes a numeric string or length greater than 3
The %%a loop simply submits a sequence of sample strings to :process
The inline comments should explain the remainder.

How to limit a batch variable's length

Is it any way to limit the length of a batch variable? I mean, if it is possible to program a variable that only admits between 0 and x characters? So, for an instance, if I entered 123456 and the max length was 4 it wouldn't proceed to continue. I hope you can understand my question.
Thanks in advance.
Demonstration batch code according to suggestions of aschipfl and rojo:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
:UserPrompt
cls
set "UserInput="
set /P "UserInput=Enter string with a length between 1 and 4: "
if not defined UserInput goto UserPrompt
if not "!UserInput:~4!" == "" goto UserPrompt
echo/
echo String entered: !UserInput!
echo/
endlocal
pause
!UserInput:~4! is replaced by command processor on execution of the batch file by the string from user input starting with fifth character. First character of a string value has index value 0 which is reason for number 4 for fifth character. This string is empty if user entered a string not longer than 4 characters, otherwise this substring is not empty resulting in user must input again a string.
Delayed expansion is used to avoid an exit of batch processing caused by a syntax error if the user enters a string containing an odd number of double quotes.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
cls /?
echo /?
endlocal /?
if /?
pause /?
set /?
setlocal /?
If you mean "limit the length of a batch variable when it is read via SET /P command", then you may use the ReadLine subroutine described at this post, that emulates SET /P command using pure Batch file commands, and just insert the maximum length restriction.
#echo off
setlocal
call :ReadNChars string4="Enter 4 characters maximum: " 4
echo String read: "%string4%"
goto :EOF
:ReadNChars var="prompt" maxLen
rem Read a line emulating SET /P command
rem Antonio Perez Ayala
rem Initialize variables
setlocal EnableDelayedExpansion
echo > _
for /F %%a in ('copy /Z _ NUL') do set "CR=%%a"
for /F %%a in ('echo prompt $H ^| cmd') do set "BS=%%a"
rem Show the prompt and start reading
set /P "=%~2" < NUL
set "input="
set i=0
:nextKey
set "key="
for /F "delims=" %%a in ('xcopy /W _ _ 2^>NUL') do if not defined key set "key=%%a"
rem If key is CR: terminate input
if "!key:~-1!" equ "!CR!" goto endRead
rem If key is BS: delete last char, if any
set "key=!key:~-1!"
if "!key!" equ "!BS!" (
if %i% gtr 0 (
set /P "=!BS! !BS!" < NUL
set "input=%input:~0,-1%"
set /A i-=1
)
goto nextKey
)
rem Insert here any filter on the key
if %i% equ %3 goto nextKey
rem Else: show and accept the key
set /P "=.!BS!%key%" < NUL
set "input=%input%%key%"
set /A i+=1
goto nextKey
:endRead
echo/
del _
endlocal & set "%~1=%input%"
exit /B
However, if you want to limit the length of a Batch variable in other cases, like SET /A or plain SET commands, then there is no way to do that. Of course, you may execute such commands and then cut the variable value to the maximum length, but that process is an entirely different thing.

Resources