Remove possible trailing spaces from a substring result - string

I am inexperienced in batch scripting and have just have written one where all files in the source directory are enumerated, and each filename string is trimmed up to its first - character. The target path is then checked for the existence of a child directory named using the resulting substring, and if a match is not found, that directory is created. The source file is then moved into that directory.
My issue is that often the filename string contains whitespace immediately preceding the - which results in substrings with trailing space characters.
So my question is, how can I remove the trailing space characters before using the substring containing them.
The code so far:
REM CODE BY ZF
#Echo off
CLS
setlocal
SETLOCAL ENABLEDELAYEDEXPANSION
set /p Input=ENTER DIRECTORY:
REM set mypath=%Input%
REM *******************
REM IFIXED PATH FOR TESTING
set mypath=C:\Admin\cmd_tests
REM echo !mypath!
REM pause
IF "%mypath%" NEQ "" (
REM set mypath=C:\Admin\cmd_tests
REM pause
REM *******************
REM GET SUBFOLDERNAME FROM FILENAME - WORKS
set Counter=0
for %%f in (%mypath%\*.*) do (
echo %%f
echo %%~nxf
for /f "delims=-" %%i in ("%%~nxf") do (
REM echo %%i
set myfolder=%%i
set %%myfolder=myfolder
)
echo !myfolder!
REM set "MYSTRING=%%myfolder"
REM *******************
REM ITERATE FOLDER NAME - myfolder is not transfered into this routine, so the result = "" and ""
(echo %myfolder%& echo.) | findstr /O . | more +1 | (set /P RESULT= & call exit /B %%RESULT%%)
set /A STRLENGTH=%ERRORLEVEL%-5
echo string !myfolder! length = %STRLENGTH%
REM *******************
REM DEFINE TARGET SUBFOLDER PATH
set searchfolder="C:\Admin\cmd_tests\!myfolder!"
echo !searchfolder!
REM if exist !searchfolder! echo "Folder already exists"
REM if not exist !searchfolder! echo "Folder does not exist"
REM *******************
REM CREATE SUBFOLDER
if not exist !searchfolder! mkdir !searchfolder!
REM *******************
REM DEFINE FILE
set targetfile="C:\Admin\cmd_tests\!myfolder!\%%~nxf"
echo !targetfile!
REM *******************
REM MOVE FILE TO SUBFOLDER
IF NOT EXIST "%searchfolder%\%%~NXF" (
echo "FILE DOES NOT EXIST"
REM move "%%f" "%targetfile%"
) ELSE (
echo "FILE EXISTS"
)
echo "---------------------"
)
pause
) ELSE (
REM *******************
REM IF MYPATH DIESN EXIST (user input later)
echo "exit"
pause
)

The following batch file could be used to move all non-hidden files in the specified folder into subfolders with name being either entire file name or the string up to first hyphen character working also for unusual file names starting with a dot and do not have a file extension (= one more dot and at least one other character after last dot).
The Windows file management removes trailing spaces and dots from file/folder names as documented by Microsoft on page about Naming Files, Paths, and Namespaces. For that reason the batch file makes use of the Windows file management functions to get the folder name without trailing spaces and dots instead of using own code to remove trailing spaces from the folder name on file name being something like album name - song title.mp4 to move the file to the subfolder with the name album name.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
rem Assign the first argument string passed to the batch file
rem on calling it to the environment variable FolderPath.
set "FolderPath=%~1"
rem Is the batch file not called with an argument string?
if not defined FolderPath goto FolderPrompt
rem Replace all slashes by backslashes in the passed argument string.
set "FolderPath=%FolderPath:/=\%"
rem Append a backslash if the passed argument string does not end
rem with a backslash and check next if the passed argument string
rem references an existing folder at all.
if not "%FolderPath:~-1%" == "\" set "FolderPath=%FolderPath%\"
if not exist "%FolderPath%" goto FolderPrompt
rem Get the full folder path with a backslash at end
rem if the passed folder path is a relative path.
for %%I in ("%FolderPath%") do set "FolderPath=%%~fI"
goto ProcessFiles
:FolderPrompt
rem Undefine the environment variable FolderPath.
set "FolderPath="
rem Prompt the user for the folder path.
set /P "FolderPath=Enter directory: "
rem Has the user not entered a string?
if not defined FolderPath goto FolderPrompt
rem Remove all double quotes from user input string.
set "FolderPath=%FolderPath:"=%"
rem Has the user not entered anything else than one or more double quotes?
if not defined FolderPath goto FolderPrompt
rem Replace all slashes by backslashes in user input string.
set "FolderPath=%FolderPath:/=\%"
rem Append a backslash if the passed argument string does not end
rem with a backslash and check next if the passed argument string
rem references an existing folder at all.
if not "%FolderPath:~-1%" == "\" set "FolderPath=%FolderPath%\"
if not exist "%FolderPath%" goto FolderPrompt
rem Get the full folder path with a backslash at end
rem if the entered folder path is a relative path.
for %%I in ("%FolderPath%") do set "FolderPath=%%~fI"
goto ProcessFiles
rem Process all non-hidden files in the folder with first loading into
rem memory the list of file names of the files to move to subfolders to
rem make sure that no file is skipped as it could happen on processing
rem the files just a simple FOR loop on FAT32 or exFAT drives.
rem For each file name the string up to first hyphen characters is
rem interpreted as folder name into which the file should be moved.
rem The entire file name is interpreted as folder name if the file name
rem does not contain a hyphen character at all. The extra IF condition
rem is for file names starting with a dot and not having a file extension
rem like ".Unsual File Name - Test!".
rem The Windows file management removes by default all trailing spaces and
rem dots before passing a file/folder name to the file system. Therefore
rem the Windows file management functions are used implicit to get the
rem folder name as either already existing for the current file or being
rem next created without trailing spaces and dots with using the third
rem FOR loop with loop variable K. The usage of "%%~nxK" results in passing
rem the folder name via Windows file management API to the file system and
rem so this reference expands to folder name as returned by the file system
rem (folder exists) respectively the used Windows file managment function
rem (folder does not exist).
:ProcessFiles
echo Process files in: "%FolderPath:~0,-1%"
for /F "eol=| delims=" %%I in ('dir "%FolderPath%*" /A-D-H /B 2^>nul') do (
if not "%%~nI" == "" (
for /F "eol=| delims=-" %%J in ("%%~nI") do for %%K in ("%%J") do (
md "%FolderPath%%%~nxK" 2>nul
if exist "%FolderPath%%%~nxK\" (
echo Move "%%I" to "%%~nxK" ...
move /Y "%FolderPath%%%I" "%FolderPath%%%~nxK\"
) else echo ERROR: Failed to create directory: "%%~nxK"
)
) else (
for /F "eol=| delims=-" %%J in ("%%~xI") do for %%K in ("%%J") do (
md "%FolderPath%%%~nxK" 2>nul
if exist "%FolderPath%%%~nxK\" (
echo Move "%%I" to "%%~nxK" ...
move /Y "%FolderPath%%%I" "%FolderPath%%%~nxK\"
) else echo ERROR: Failed to create directory: "%%~nxK"
)
)
)
endlocal
Well, this batch file should be obviously used for moving song or video files, but is designed nevertheless for moving any type of files.
The path of the folder with the files to move can be passed to the batch file as first argument on calling it, for example for using this batch file via the Send to context menu in Windows File Explorer on right clicking on a folder, or can be entered on execution of the batch file. Please read the comments how the batch file makes sure that the passed or entered argument string is the name of a folder with full or relative path. The passed or entered folder path can be also something like \MySongs (folder MySongs in root of current drive) or ..\MyVideos (folder MyVideos in parent directory of current directory).
The batch file does not use delayed environment variable expansion and works therefore also with file names containing one or more !. It is designed also for file names beginning unusually with one or more spaces or with a semicolon.
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 /?
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
move /?
rem /?
set /?
setlocal /?
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background.

i managed it - files are moved now correctly:
CODE V2 / WIP :
REM CODE BY Zdenek Fajfrlik & adapted info from the forum
#Echo off
CLS
SETLOCAL ENABLEDELAYEDEXPANSION
set /p Input=ENTER DIRECTORY:
REM set mypath=%Input%
REM *******************
REM FIXED PATH FOR TESTING
set mypath=C:\Admin\cmd_tests
IF NOT "%mypath%" == "" (
REM *******************
REM GET SUBFOLDERNAME FROM FILENAME - WORKS
set Counter=0
for %%f in (%mypath%\*.*) do (
REM echo %%f
REM echo %%~nxf
for /f "delims=-" %%i in ("%%~nxf") do (
set MYSTRING=%%i
)
REM *******************
REM TRIM STRING - TRAILING SPACES
REM ::trim right whitespace (up to 100 spaces at the end)
for /f "tokens=* delims= " %%a in ("%MYSTRING%") do set MYSTRING=%%a
for /l %%a in (1,1,100) do if "!MYSTRING:~-1!"==" " set MYSTRING=!MYSTRING:~0,-1!
REM echo !MYSTRING!
REM *******************
REM DEFINE TARGET SUBFOLDER PATH
set searchfolder="C:\Admin\cmd_tests\!MYSTRING!"
echo !searchfolder!
REM if exist !searchfolder! echo "Folder already exists"
REM if not exist !searchfolder! echo "Folder does not exist"
REM *******************
REM CREATE SUBFOLDER
if not exist !searchfolder! mkdir !searchfolder!
REM *******************
REM DEFINE FILE
REM set targetfile=C:\Admin\cmd_tests\!MYSTRING!\%%~nxf
set targetfile="C:\Admin\cmd_tests\!MYSTRING!\%%~nxf"
REM echo !targetfile!
REM *******************
REM MOVE FILE TO SUBFOLDER
IF NOT EXIST "%searchfolder%\%%~NXF" (
echo "FILE DOES NOT EXIST"
echo %%f
echo !targetfile!
move "%%f" !targetfile!
) ELSE (
echo "FILE EXISTS"
)
echo "---------------------"
)
pause
) ELSE (
REM *******************
REM IF MYPATH DOES NOT EXIST (user input later)
echo "exit"
pause
)
Thanx for helping :-)
stan

Related

Unexpected results while replacing single characters in a text file

My batch file:
#ECHO off
(FOR /f "delims=" %%i in (source.txt) DO (
SET "line=%%i"
setlocal enabledelayedexpansion
SET "line=!line:Ć=F!"
SET "line=!line:Ç=G!"
SET "line=!line:Ň=R!"
SET "line=!line:Ô=T!"
ECHO.!line!
endlocal
))>"output.txt"
My source.txt file:
ĆÇŇÔ
Expected output.txt file:
FGRT
Current output.txt file:
FFRR
My question is: what's wrong here?
If source.txt is not saved as Unicode, your issue may be related to the codepage at the time you run your loop.
The following example switches to codepage 1252, West European Latin, (as also suggested in the comments by Gerhard), if not that already. Although I'd assume codepage 850, Multilingual (Latin I) should work equally well. (Just change to the codepage required by replacing 1252 on lines 7, and 8, as necessary).
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
If Not Exist "source.txt" GoTo :EOF
For /F "Delims==" %%G In ('2^> NUL Set _cp') Do Set "%%G="
For /F Tokens^=* %%G In ('"%SystemRoot%\System32\chcp.com"'
) Do For %%H In (%%G) Do Set "_cp=%%~nH"
If Not %_cp% Equ 1252 (Set "_cpc=TRUE"
"%SystemRoot%\System32\chcp.com" 1252 1> NUL)
(For /F UseBackQ^ Delims^=^ EOL^= %%G In ("source.txt") Do (
Set "line=%%G"
SetLocal EnableDelayedExpansion
Set "line=!line:Ć=F!"
Set "line=!line:Ç=G!"
Set "line=!line:Ň=R!"
Set "line=!line:Ô=T!"
Echo=!line!
EndLocal)) 1> "output.txt"
If Defined _cpc "%SystemRoot%\System32\chcp.com" %_cp% 1> NUL
Please note that using a For loop like this, will remove any blank lines from the output
The answer (as suggested by #Gerhard and #Compo): it was the wrong code page.
Below is my current working batch code if someone else will ever be in the same need (to convert inverted ATASCII characters in ATARI BASIC code).
It converts defined set of characters (you can add more / delete some - just modify strings and the total number of characters) and make comments more visible by adding lines at the start and at the end of each one.
#ECHO off
rem --------------------------------------------------
rem CHECK FOR THE SOURCE FILE
rem --------------------------------------------------
IF "%~1"=="" GOTO :End
rem --------------------------------------------------
rem SET THE CODE PAGE
rem --------------------------------------------------
CHCP 1252 > NUL
rem --------------------------------------------------
rem DEFINE THE SET OF CHARACTERS TO CONVERT
rem --------------------------------------------------
SET "input_set_of_chars= Ł¤§¨cŞ«¬­R°+˛ł´u¸ą»Ľ˝ľÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚř"
SET "output_set_of_chars= #$'()*+,-.0123456789;<=>ABCDEFGHIJKLMNOPQRSTUVWXYZx"
SET "number_of_chars=52"
rem --------------------------------------------------
rem CONVERT EACH LINE OF THE SOURCE FILE
rem --------------------------------------------------
(FOR /f "delims=" %%i in (%~1) DO (
SET "line=%%i"
CALL :ConvertASCII
))> "%~n1-converted%~x1"
GOTO :End
rem --------------------------------------------------
rem START OF THE CONVERT SUBROUTINE
rem --------------------------------------------------
:ConvertASCII
SETLOCAL enableDelayedExpansion
rem --------------------------------------------------
rem IF THE LINE IS NOT A COMMENT THEN DO NOT CONVERT
rem --------------------------------------------------
IF "!line!"=="!line: REM =!" GOTO :LoopEnd
rem --------------------------------------------------
rem MAKE COMMENT LINE A LITTLE MORE VISIBLE
rem --------------------------------------------------
SET "line=!line: REM = REM ----------!----------"
rem --------------------------------------------------
rem CONVERT ALL DEFINED CHARACTERS
rem --------------------------------------------------
SET "counter=0"
:LoopStart
SET "input_char=!input_set_of_chars:~%counter%,1!"
SET "output_char=!output_set_of_chars:~%counter%,1!"
SET "line=!line:%input_char%=%output_char%!"
SET /a counter+=1
IF "!counter!"=="!number_of_chars!" GOTO :LoopEnd
GOTO :LoopStart
rem --------------------------------------------------
rem ECHO THE CURRENT LINE AND EXIT SUBROUTINE
rem --------------------------------------------------
:LoopEnd
ECHO.!line!
ENDLOCAL
EXIT /b 0
:End
As per your comment, it is actually not really a code page issue, because you have got ATASCII encoding in your *.bas files. For converting such files to avoid inverted glyphs, I would use a language that can easily read the file in binary mode and subtract 0x80 from every byte whose value is greater than or equal to 0x80.
Anyway, if you do want to replace the characters left over from your already performed conversion process (Ć, Ç, Ň, Ô, with codes 0x8F, 0x80, 0xD5, 0xE2, resp., as per your active code page 852), I would do it the following way, applying code page 437 during any conversion activities, because this defines the character set of the original IBM PC, also known as OEM font, where there should not occur any unwanted character conversion in the background:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=%~dp0." & rem // (full path to target directory)
set "_SOURCE=source.txt" & rem // (name of source file)
set "_RETURN=return.txt" & rem // (name of return file)
set "_FILTER=^[0-9][0-9]* *REM " & rem /* (`findstr` search expression to filter
rem for specific lines; `^` means all) */
rem // Store current code page:
for /F "tokens=2 delims=:" %%P in ('chcp') do for /F %%C in ("%%P") do set "$CP=%%C"
rem // Set code page to OEM in order to avoid unwanted character conversions:
> nul chcp 437
rem /* Specify character replacements; the `forfiles` command supports substitution
rem of hex codes like `0xHH`, so you can specify special characters by their code
rem in order to avoid having to embed them into this script, which might in turn
rem lead to problems due to dependencies on the current code page; each `0x22`
rem represents a `"` to enclose each replacement expression within quotes; each
rem of the following replacement expression is `"` + char. + `=` + char. +`"`: */
for /F "delims=" %%R in ('
forfiles /P "%~dp0." /M "%~nx0" /C ^
"cmd /C echo 0x220x8F=F0x22 0x220x80=G0x22 0x220xD5=R0x22 0x220xE2=T0x22"
') do set "RPL=%%R"
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Write into return file:
> "%_RETURN%" (
rem /* Read from source file, temporarily precede each line with line number
rem followed by a `:` in order to not lose blank lines: */
for /F "delims=" %%L in ('findstr /N "^" "%_SOURCE%"') do (
rem // Store current line:
set "LINE=%%L"
rem // Toggle delayed expansion to avoid troubles with `!`:
setlocal EnableDelayedExpansion
rem // Remove temporary line number prefix:
set "LINE=!LINE:*:=!"
rem // Filter for lines that are subject to the replacements:
cmd /V /C echo(^^!LINE^^!| > nul findstr /R /I /C:"!_FILTER!" && (
rem // Perform replacements one after another:
for %%R in (%RPL%) do if defined LINE set "LINE=!LINE:%%~R!"
)
rem // Return resulting line:
echo(!LINE!
endlocal
)
)
rem // Return from target directory:
popd
)
rem // Restore former code page:
if defined $CP > nul chcp %$CP%
endlocal
exit /B
This approach performs the character replacements only in lines that begin with: a decimal number, followed by one or more SPACEs, followed by REM in a case-insensitive manner, followed by a SPACE.
Here is now a script that truly converts ATASCII characters in REM comments in your Atari Basic (*.bas) file, using certutil for converting the binary character codes:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_TARGET=%~dp0." & rem // (full path to target directory)
set "_SOURCE=source.txt" & rem // (name of source file)
set "_RETURN=return.txt" & rem // (name of return file)
set "_FILTER=^[0-9][0-9]* *REM " & rem /* (`findstr` search expression to filter
rem for specific lines; `^` means all) */
set "_TEMPFN=%TEMP%\%~n0_%RANDOM%" & rem // (path and base name of temporary file)
rem // Change into target directory:
pushd "%_TARGET%" && (
rem // Write into return file:
> "%_RETURN%" (
rem /* Read from source file, temporarily precede each line with line number
rem followed by a `:` in order to not lose blank lines: */
for /F "delims=" %%L in ('findstr /N "^" "%_SOURCE%"') do (
rem // Store current line:
set "LINE=%%L"
rem // Toggle delayed expansion to avoid troubles with `!`:
setlocal EnableDelayedExpansion
rem // Remove temporary line number prefix:
set "LINE=!LINE:*:=!"
rem // Filter for lines that are subject to the replacements:
cmd /V /C echo(^^!LINE^^!| > nul findstr /R /I /C:"!_FILTER!" && (
rem // Found a line, hence write it to temporary file:
(> "!_TEMPFN!.tmp" echo(!LINE!) && (
rem // Convert temporary file to hex dump file:
> nul certutil -f -encodehex "!_TEMPFN!.tmp" "!_TEMPFN!.cnv" 4 && (
rem // Write to temporary file:
(> "!_TEMPFN!.tmp" (
rem // Read hex dump file line by line:
for /F "usebackq tokens=*" %%T in ("!_TEMPFN!.cnv") do (
rem // Reset buffer, loop through hex values:
set "BUFF= " & for %%H in (%%T) do (
rem // Determine new hex value, append it to buffer:
set "HEX=%%H" & set /A "FIG=0x!HEX:~,1!-0x8"
if !FIG! lss 0 (
rem // Value was < 0x80, hence keep it:
set "BUFF=!BUFF! !HEX!"
) else (
rem // Value was >= 0x80, hence subtract 0x80:
set "BUFF=!BUFF! !FIG!!HEX:~1!"
)
)
echo(!BUFF:~2!
)
)) && (
> nul certutil -f -decodehex "!_TEMPFN!.tmp" "!_TEMPFN!.cnv" 4 && (
type "!_TEMPFN!.cnv"
) || echo(!LINE!
) || echo(!LINE!
) || echo(!LINE!
) || echo(!LINE!
) || (
rem // Return resulting line:
echo(!LINE!
)
endlocal
)
)
rem // Clean up temporary files:
del "%_TEMPFN%.tmp" "%_TEMPFN%.cnv"
rem // Return from target directory:
popd
)
endlocal
exit /B

Batch File Help: Sorting files into specifically-named folders based on filename with exceptions

My operating system is Windows 7 and I have files with names such as:
123.txt
abcd_123.txt
abcd_1234.txt
bcde_123_456.txt
bcde_123_4567.txt
cde_fgh_123_456.txt
cde_fgh_123_4567.txt
I would like for folders to be generated based on the starting parts of these filenames (or without the trailing numbers) and prefaced with a specific character, and for the files to then be sorted accordingly into them. Example result:
#abcd\abcd_123.txt
#abcd\abcd_1234.txt
#bcde\bcde_123_456.txt
#bcde\bcde_123_4567.txt
#cde_fgh\cde_fgh_123_456.txt
#cde_fgh\cde_fgh_123_4567.txt
*123.txt is skipped / not sorted.
This is the code I've come up with so far:
#echo OFF
setlocal enabledelayedexpansion
set var_dir="#Sorted"
for /f "delims=_" %%i in ('dir /b /a-d *_*.txt') do (
mkdir "#Sorted\#%%i" 2>nul
move "%%i_*.txt" "%var_dir%\#%%i" >NUL 2>nul
)
echo Sorting Complete!
#pause
GOTO :EOF
It works, but I am not sure how to:
Ignore filenames that start with a number (0-9).
Obtain strings beyond the first delim (_).
As for the second point, I think the filenames can sometimes be too complex to correctly differentiate which part to use as the name for the folder. Example, it sorts:
cde_fgh_123_4567.txt
Into:
#cde\cde_fgh_123_4567.txt
As such, I was thinking for the algorithm to be something like:
Set Folder Name to
(1) string before (first) "_" if string is greater than 3 characters
OR
(2) entire string before second "_" if first string is less than or equal to 3 characters
Thus, the example above should be changed to:
#cde_fgh\cde_fgh_123_4567.txt
How do I improve my batch code to obtain the desired outcome?
Your specifications are not clear enough. I assumed that the folder name is comprised of the first parts of filename that are not numbers. This method works with your filename examples:
#echo off
setlocal EnableDelayedExpansion
for %%a in (*.txt) do (
call :getFolder "%%~Na"
if defined folder (
if not exist "!folder!\" mkdir "!folder!"
move "%%a" "!folder!"
)
)
goto :EOF
:getFolder filename
set "file=%~1"
rem Separate filename in parts at undescores and
rem collect them while part is not a number (greater than zero)
set "folder="
set "part=%file:_=" & set /A "num=part" & (if !num! equ 0 set "folder=!folder!_!part!") & set "part=%"
if defined folder set "folder=#%folder:~1%"
exit /B
For example, this is the result obtained with your data:
File "123.txt" folder ""
File "abcd_123.txt" folder "#abcd"
File "abcd_1234.txt" folder "#abcd"
File "bcde_123_456.txt" folder "#bcde"
File "bcde_123_4567.txt" folder "#bcde"
File "cde_fgh_123_456.txt" folder "#cde_fgh"
File "cde_fgh_123_4567.txt" folder "#cde_fgh"
You could do the following:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\#Sorted"
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop through matching files:
for /F "eol=| tokens=1,2,* delims=_" %%A in ('dir /B /A:-D-H-S "*_*.txt"') do (
rem // Skip if file name consists of less than two `_`-separated parts:
if not "%%B"=="" (
rem // Check if file name consists of more than two parts:
if "%%C"=="" (
rem // Two parts, so part one becomes directory name:
2> nul md "#%%A"
move "%%A_%%B" "#%%A\%%A_%%B"
) else (
rem // More than two parts:
set "ONE=%%A"
setlocal EnableDelayedExpansion
rem // Check length of part one:
if "!ONE:~3!"=="" (
rem /* Part one contains not more than 3 characters,
rem hence parts one and two become directory name: */
endlocal
2> nul md "#%%A_%%B"
move "%%A_%%B_%%C" "#%%A_%%B\%%A_%%B_%%C"
) else (
rem /* Part one contains more than 3 characters,
rem hence only part one becomes directory name: */
endlocal
2> nul md "#%%A"
move "%%A_%%B_%%C" "#%%A\%%A_%%B_%%C"
)
)
)
)
rem // Return from target directory:
popd
)
endlocal
exit /B
You could quickly do this using this code:
#(SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET "_SrcFolder=C:\Admin\CMD\s-e\tmp"
REM Use for DIR to pre-filter the list as much as possible
SET "_FileGlob=*_*.txt"
REM Used for FindStr Matches a Value that Begins with non numeric characters, followed by an underscore multiple times, followed by any number of numeric characters and underscore and ending in .txt
SET "_FileRegex=!_SrcFolder:\=\\!\\[a-Z][a-Z_]*_[0-9][0-9_]*.txt$"
)
CALL :Main
( ENDLOCAL
EXIT /B
)
:Main
REM Loop through the file sin the directory filtering non-matches and then perform actions based on matches
ECHO."%_FileRegex%"
FOR /F "Tokens=*" %%A IN ('
DIR /S/B/A-D "%_SrcFolder%\%_FileGlob%" ^|
FINDSTR /r "%_FileRegex%"
') DO (
SET "_FileName=%%~nA"
REM Break File Name after the Characters needed for the directory
FOR /F "TOKENS=* delims=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" %%a IN (
"%%~nA"
) DO (
REM Create Folder
IF NOT EXIST "%_SrcFolder%\#!_FileName:_%%a=!\" (
ECHO.&ECHO.== Creating Folder "#!_FileName:_%%a=!"
MD "%_SrcFolder%\#!_FileName:_%%a=!\"
)
REM Move Original File to the New Directory
ECHO. + Moving "%%~nxA" TO "#!_FileName:_%%a=!"
MOVE /Y "%%A" "%_SrcFolder%\#!_FileName:_%%a=!\%%~nxA" >NUL
)
)
GOTO :EOF

Append a string in a file after another string through a windows batch file

I'm trying to write a batch file to append a certain string "str2 => bbb" to a file if it is not yet present in the file. "str2" will go after the string "str1 => aaa" (that always exists in the file). For example:
file.txt
...
str1 => aaa
...
end of file.txt
it will become:
file.txt
...
...
...
str1 => aaa
str2 => bbb
...
end of file.txt
and the batch file must be not destructive, i.e. if "str2" already exists in the file, the batch will do nothing.
I know how to find a string in the file:
FINDSTR "str2 => bbb" "file.txt"
IF %errorlevel%==0 (
ECHO FOUND
)
but I don't know what else to do to write the other string in the next line.
Since it is not clear to me whether str2 must occur immediately after str1 in the file or just anywhere, I wrote the following script which is capable of covering both criterias. It directly modifies the input file in case, so be careful. The input file must be speficied as a command line argument:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "FILE=%~1" & rem // (input file; `%~1` takes first command line argument)
set "WORD1=str1" & rem // (word after which `%WORD2%` must be inserted)
set "WORD2=str2" & rem // (word that must be present in the file)
set "STRING2=%WORD2% => bbb" & rem // (full string to insert if `%WORD2%` is missing)
set "SEPARATORS= = " & rem // (characters that separate the words from the rest)
set "FIXEDPOS=#" & rem // (if not empty, defines that `%WORD2%` must be after `%WORD1%`)
rem // Create line-break (carriage-return and line-feed):
(for /F %%# in ('copy /Z "%~f0" nul') do set ^"CR+LF=%%#^
%= empty line =%
^")
rem // Ensure list of separators contains (ends) with space:
if defined SEPARATORS (
if not "%SEPARATORS:~-1%"==" " set "SEPARATORS=%SEPARATORS: =% "
) else set "SEPARATORS= "
setlocal EnableDelayedExpansion
rem // Set up regular expression:
if defined FIXEDPOS (
rem /* `%WORD2%` must be in the line following `%WORD1%`, so define a dual-line
rem regular expression (both words must be present at the beginnings of lines): */
set "REGEX=^%WORD1%[%SEPARATORS%].*!CR+LF!%WORD2%[%SEPARATORS%]"
) else (
rem /* Position of `%WORD2%` does not matter with respect to `%WORD1%`,
rem hence it merely must be present at the beginning of a line: */
set "REGEX=^%WORD2%[%SEPARATORS%]"
)
rem // Search for regular expression in file:
> nul findstr /I /R /C:"!REGEX!" "%FILE%" || (
rem // No match encountered, so read entire file and deplete it afterwards:
for /F "delims=" %%L in ('findstr /N /R "^" "%FILE%" ^& ^> "%FILE%" break') do (
endlocal
rem // Read a line, reset flag that defines whether or not to insert a string:
set "FLAG=" & set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Split off first word and compare with `%WORD1%`:
for /F "eol= tokens=1 delims=%SEPARATORS%" %%K in ("!LINE:*:=!") do (
endlocal
rem // First word matches `%WORD1%`, so set flag:
if /I "%%K"=="%WORD1%" set "FLAG=#"
setlocal EnableDelayedExpansion
)
rem // Append to file:
>> "%FILE%" (
rem // Write original line:
echo(!LINE:*:=!
rem // Write string to insert in case flag is defined:
if defined FLAG echo(!STRING2!
)
)
)
endlocal
endlocal
exit /B
Note that this script does not check whether str1 occurs multiple times.
Use powershell in your batch file to simplify things
FINDSTR "str2 => bbb" "file.txt"
IF %errorlevel%==0 (
ECHO FOUND
Goto END
)
powershell -Command "(get-content File.txt) -replace "str1 => aaa", "$&`n str2 => bbb" | set-content File.txt"
:end
The powershell command will get the content of your file and replace your string with search string ($&) + new line + str2...)

Find string with special character in text file and add line break before each occurrence

I have a text file that is one long string like this:
ISA*00*GARBAGE~ST*TEST*TEST~CLP*TEST~ST*TEST*TEST~CLP*TEST~ST*TEST*TEST~CLP*TEST~GE*GARBAGE*~
And I need it to look like this:
~ST*TEST*TEST~CLP*TEST
~ST*TEST*TEST~CLP*TEST
~ST*TEST*TEST~CLP*TEST
I first tried to add a line at every ~ST to split the string up, but I can't for the life of me make this happen. I have tried various scripts, but I thought a find/replace script would work best.
#echo off
setlocal enabledelayedexpansion
set INTEXTFILE=test.txt
set OUTTEXTFILE=test_out.txt
set SEARCHTEXT=~ST
set REPLACETEXT=~ST
for /f "tokens=1,* delims=~" %%A in ( '"type %INTEXTFILE%"') do (
SET string=%%A
SET modified=!string:%SEARCHTEXT%=%REPLACETEXT%!
echo !modified! >> %OUTTEXTFILE%
)
del %INTEXTFILE%
rename %OUTTEXTFILE% %INTEXTFILE%
Found here How to replace substrings in windows batch file
But I'm stuck because (1) the special character ~ makes the code not work at all. It gives me this result:
string:~ST=~ST
The code does nothing at all if using quotes around "~ST". And (2) I can't figure out how to add a line break before ~ST.
The final task for this would be to delete the ISA*00*blahblahblah and ~GE*blahblahblah lines after all splits have been performed. But I am stuck on the splitting at ~ST part.
Any suggestions?
#echo off
setlocal EnableDelayedExpansion
rem Set next variable to the number of "~" chars that delimit the wanted fields, or more
set "maxTokens=7"
rem Define the delimiters that starts a new field
set "delims=/ST/GE/"
for /F "delims=" %%a in (test.txt) do (
set "line=%%a"
set "field="
rem Process up to maxTokens per line;
rem this is a trick to avoid a call to a subroutine that have a goto loop
for /L %%i in (0,1,%maxTokens%) do if defined line (
for /F "tokens=1* delims=~" %%b in ("!line!") do (
rem Get the first token in the line separated by "~" delimiter
set "token=%%b"
rem ... and update the rest of the line
set "line=%%c"
rem Get the first two chars after "~" token like "ST", "CL" or "GE";
rem if they are "ST" or "GE":
for %%d in ("!token:~0,2!") do if "!delims:/%%~d/=!" neq "%delims%" (
rem Start a new field: show previous one, if any
if defined field echo !field!
if "%%~d" equ "ST" (
set "field=~%%b"
) else (
rem It is "GE": cancel rest of line
set "line="
)
) else (
rem It is "CL" token: join it to current field, if any
if defined field set "field=!field!~%%b"
)
)
)
)
Input:
ISA*00*GARBAGE~ST*TEST1*TEST1~CLP*TEST1~ST*TEST2*TEST2~CLP*TEST2~ST*TEST3*TEST3~CLP*TEST3~GE*GARBAGE*~CLP~TESTX
Output:
~ST*TEST1*TEST1~CLP*TEST1
~ST*TEST2*TEST2~CLP*TEST2
~ST*TEST3*TEST3~CLP*TEST3
Don't reinvent the wheel, use a regexp replace tool such as sed or JREPL.BAT:
call jrepl "^.*?~ST(.+?)~GE.*$" "'~ST'+$1.replace(/~ST/g,'\r\n$&')" /jmatch <in.txt >out.txt
The ~ cannot be used as the first character of a search string in the substring substitution syntax %VARIABLE:SEARCH_STRING=REPLACE_STRING%, because it is used to mark the substring expansion %VARIABLE:~POSITION,LENGTH% (type set/? for more information).
Supposing your text file contains a single line of text only and it does not exceed a size of about 8 kBytes, I see the following option for accomplishing your task. This script makes use of the substring substitution syntax %VARIABLE:*SEARCH_STRING=REPLACE_STRING%; the * defines to match everything up to the first occurrence of SEARCH_STRING:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem initialise constants:
set "INFILE=test_in.txt"
set "OUTFILE=test_out.txt"
set "SEARCH=ST"
set "TAIL=GE"
rem read single-line file content into variable:
< "%INFILE%" set /P "DATA="
rem remove everything before first `~%SEARCH%`:
set "DATA=~%SEARCH%!DATA:*~%SEARCH%=!"
rem call sub-routine, redirect its output:
> "%OUTFILE%" call :LOOP
endlocal
goto :EOF
:LOOP
rem extract portion right to first `~%SEARCH%`:
set "RIGHT=!DATA:*~%SEARCH%=!"
rem skip rest if no match found:
if "!RIGHT!"=="!DATA!" goto :TAIL
rem extract portion left to first `~%SEARCH%`, including `~`:
set "LEFT=!DATA:%SEARCH%%RIGHT%=!"
rem the last character must be a `~`;
rem so remove it; `echo` outputs a trailing line-break;
rem the `if` avoids an empty line at the beginning;
rem the unwanted part at the beginning is removed implicitly:
if not "!LEFT:~,-1!"=="" echo(!LEFT:~,-1!
rem output `~%SEARCH%` without trailing line-break:
< nul set /P "DUMMY=~%SEARCH%"
rem store remainder for next iteration:
set "DATA=!RIGHT!"
rem loop back if remainder is not empty:
if not "!DATA!"=="" goto :LOOP
:TAIL
rem this section removes the part starting at `~%TAIL%`:
set "RIGHT=!DATA:*~%TAIL%=!"
if "!RIGHT!"=="!DATA!" goto :EOF
set "LEFT=!DATA:%TAIL%%RIGHT%=!"
rem output part before `~%TAIL%` without trailing line-break:
< nul set /P "DUMMY=!LEFT:~,-1!"
goto :EOF
The following restrictions apply to this approach:
the input file contains a single line;
the size of the input file does not exceed about 8 kBytes;
there is exactly one instance of ~GE, that occurs after all instances of ~ST;
there is always at least one character in between two adjacent ~ST instances;
no special characters occur in the file, like: SPACE, TAB, ", %, !, =;

Batch call another batch and use output as variable

I'm trying to compare a bunch of files with names like
"BD12-CD9.txt"
to folders named
"BD12-CD9 - somefoldername"
I'm removing the .txt extension to get the ID code "BD12-CD9" and want to check if the folder name contains the code.
I can't figure out how to do this without an if statement that compares strings exactly. (might be an easy solution here like jQuery's indexOf())
So I need to strip the ID code from the file name and do a direct comparison.
The problem is the ids and names are not always exactly the same length e.g. "BD12-CD10.txt" so I can't just do "set folderName2=!folderName:~0,8!"
I found a strlength function and put it in another batch file called strlen.bat and it works great, echoing the length of my stripped ID codes. How do I use the echoed value though?
move.bat
#echo off
#setlocal enabledelayedexpansion
set "errfolderpath=C:\Testing\Moving txt files automatically\"
echo.
for %%x in (*.*) do (
set fileName=%%x
set fileName2=!fileName!
set fileName2=!fileName2:~0,-4!
call strlen !fileName2!
FOR /D %%K in ("%errfolderpath%*") DO (
SET folderName=%%~nK
set folderName2=!folderName!
set folderName2=!folderName:~0,11!
echo folder: !folderName2!, file: !fileName2!
if !folderName2! == !fileName2! echo MOVE '!fileName!' to '!folderName!'
)
echo %_len%
echo.
)
PAUSE
strlen.bat
#echo off
:: strlen.bat
:: http://acm.zhihua-lai.com
if [%1] EQU [] goto end
:loop
if [%1] EQU [] goto end
set _len=0
set _str=%1
set _subs=%_str%
:getlen
if not defined _subs goto result
:: remove first letter until empty
set _subs=%_subs:~1%
set /a _len+=1
goto getlen
:result
echo %_len%
shift
goto loop
:end
For the batch file variable usage, if you look to the code, a variable called _len is defined. This variable is then echoed to console, but is not removed (no reasignation, no endlocal, ...) , so just use it.
Anyway, it is easier than it seems.
#echo off
setlocal enableextensions disabledelayedexpansion
set "errfolderpath=C:\Testing\Moving txt files automatically"
for %%a in (*.txt) do (
set "notMoved=1"
for %%b in ("%errorfolderpath%\%%~na*") do if defined notMoved (
set "notMoved="
move "%%~fa" "%%~fb"
)
)
For each file, search a folder with the same prefix (the name of the file referenced in %%a, without extension, is %%~na). If found, move the file (the full path to the file is %%~fa) to the target folder and use a switch variable to "mark" the file as moved to avoid problems in the case of two folders with the same prefix (that will be retrieved in the inner for %%b) .

Resources