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) .
Related
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
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, ", %, !, =;
A user defined input file has to be integrated into the entries of pre-set output file,
changing the data at a given block [A-Z] and index [1-75] position.
The input file structure with wildcards for variable parts:
* 00asr[1-75] 00asr*
b -v -o 00asr34 00asr34.hkx (example)
b -v 00asr35 00asr35.hkx (example)
Output file as above but with multiple blocks [A-Z]asr[1-75]:
* Aasr[1-75] Aasr*
* Basr[1-75] Basr*
...
* Zasr[1-75] Zasr*
The user has to pick a given letter A-Z to determine parts of the string and the block to replace information in.
Then we can replace the %input% substrings 00asr with %block%asr; they will then match given substrings in the %output%:
* 00asr[1-75] 00asr* >> * Zasr[1-75] Zasr* in the case of Z.
#ECHO OFF
ECHO INPUT FILE:
ECHO %1
SET INTEXT=%1
ECHO.
SET /P "LETTER=Letter? "
SET "REPLACE=%LETTER%asr"
SET TMP=tmp.txt
setlocal enabledelayedexpansion
FOR /F "tokens=1,* delims=¶" %%A IN ( '"TYPE %INTEXT%"') DO (
SET string1=%%A
SET modified=!string1:00asr=%REPLACE%!
echo !modified! >> %TMP%
)
PAUSE
I adapted this code. Looks like good code to me, but I'm like a blind man.
Now either the %TMP%s first-line [1-75] is required to determine whether there is any offset in the block
or the strings have to be compared, overwriting the line containing substring * Zasr[1-75] Zasr* with the same one from %TMP%;
or in the case of offset skipping a given amount of lines in %output% before overwriting the lines.
I'm not sure which option would be easier to implement or quicker to execute,
since I'm entirely failing at doing this.
The whole method should work something like this:
Input File
o <parameters> 00asr3 00asr3.hkx
o <parameters> 00asr4 00asr4.hkx
o <parameters> 00asr5 00asr5.hkx
User Input
Q
OUTPUT BEFORE
...
p Oasr75 Oasr75.hkx
p Qasr1 Qasr1.hkx
p Qasr2 Qasr2.hkx
p Qasr3 Qasr3.hkx
p Qasr4 Qasr4.hkx
p Qasr5 Qasr5.hkx
p Qasr6 Qasr6.hkx
....
OUTPUT AFTER
...
p Oasr75 Oasr75.hkx
p Qasr1 Qasr1.hkx
p Qasr2 Qasr2.hkx
o <parameters> Qasr3 Qasr3.hkx
o <parameters> Qasr4 Qasr4.hkx
o <parameters> Qasr5 Qasr5.hkx
p Qasr6 Qasr6.hkx
....
I managed to write the file renaming parts just fine; but this... I don't even.
I have been looking at various articles but due to not actually knowing any batch I'm not getting ahead with this.
1 Was very helpful, but I outright fail to compare strings correctly and can't seem to manage to assign them to variables.
Any help is appreciated. I might even understand what I'm doing wrong (which currently is most things).
I've solved it by myself.
Here's a shortened version of the script.
Cleaning input somewhat, changing the token in tmp, getting target parameters,
removing old target strings from file, merging files, deleting temps.
I'm confident this code isn't very good either way so further explanation seems unnecessary.
#ECHO OFF
SET _PACK=%1
SET "PATH=%~dp0"
SET "_LIST=in.txt"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "_ERROR=0"
:USERINPUT
IF %_ERROR%==1 (
ECHO INVALID INPUT: string too long.
SET "_ERROR=0"
)
IF %_ERROR%==2 (
ECHO INVALID INPUT: non-letter.
SET "_ERROR=0"
)
SET /P "LETTER=ENTER NEW BLOCK LETTER A-Z: "
ECHO "%LETTER%" SELECTED
IF NOT "%LETTER:~1,1%"=="" (
SET "_ERROR=1"
GOTO USERINPUT
)
if "%LETTER%" lss "A" (
SET "_ERROR=2"
GOTO USERINPUT
)
if "%LETTER%" gtr "Z" (
SET "_ERROR=2"
GOTO USERINPUT
)
SET "_STRING=%LETTER%asr"
' renaming files here
SET TMP=asr.tmp
DEL %TMP%
SET "_COUNT=0"
FOR /F "tokens=1,* delims=¶" %%A IN ( '"TYPE %_PACK%"') DO (
SET _LINE=%%A
SET _NEWLINE=!_LINE:0asr=%_STRING%!
ECHO !_NEWLINE!>> %TMP%
CALL SET /A _COUNT=_COUNT+1
)
ECHO NUMBER OF INPUT LINES: !_COUNT!
SET /P _OFFSET=< %TMP%
CALL SET _OFFSET=%%_OFFSET:*asr=%%
CALL SET _OFFSET=%%_OFFSET:~0, 2%%
CALL SET _OFFSET=%_OFFSET: =%
ECHO OFFSET: %_OFFSET%.
SET /A _END=_COUNT+_OFFSET-1
COPY /Y in.txt in.tmp
FOR /L %%X IN (%_OFFSET%, 1, %_END%) DO (
SET "_SEARCH=%_STRING%%%X "
ECHO DELETING OLD STRING : !_SEARCH!
FINDSTR /V /C:"!_SEARCH!" %_PACK% > in.tmp
del in.txt
ren "in.tmp" "in.txt"
)
TYPE %TMP%>> in.txt
DEL %TMP%
PAUSE
DEL %1
EXIT
I have a text file that groups different subsystems using [ ] and then contains item flags in each subgroup. Here is a snippet of the file such that you can get an understanding for what it looks like (notice each subgroup can have the same items):
[EV]
Verbosity=0
Alignment=123
[FluidLevelControl]
BufferTypeLastUsed=TWEEN
Enable Dip Tube=no
Alignment=456,efg
[PressureLevelControl]
Enabled=yes
Alignment=789,abc
Calibration Date=1280919634
[BufferFrontValve]
Log=yes
Alignment=987
Note, the above file is in excess of 2000 lines. I imagine the script is going to take a little while to execute. I also know that there is a better framework to do this but in our application we need it to run from a flash drive and be able to be plugged into our instrument which run WinXP without a .NET frameworks etc.
What I would like to do is use a .bat file to search the document for a specific subsystem (ie. [DesiredSubsystem]) and desired item within the subsystem then modify the item data. For example, in the above text I may want to change the Alignment from 789 to 12345 in the PressureLevelControl subgroup.
I understand there is no way to effective replace / update a text file using a bat file. I've created a function to read in a file and write it to a new file, now I'm trying to develop a clean way to identify the line items and what subgroup they are in as to replace the desired text as needed.
Here is what I have commented with my plan:
Update: I spent the afternoon writing some code that seems to work as shown below, there is most def better methods.
::SET VARS
set "varDebugFP=\\svchafile\Teams\Test Engineering\Productivity Tools\MFG BAT Files\SpecificTest\"
set varSource=%varDebugFP%Debug\
set varDestination=%varDebugFP%Debug\
set varFileName=specific.ini
::Do Text File Editing
setlocal enableDelayedExpansion
set "LastGroup=NONE"
::preserve blank lines using FINDSTR, even if a line may start with :
for /f "usebackq delims=*" %%A in (`type "%srcFile%" ^| findstr /n "^"`) do (
set "strLine=%%A"
set "strLine=!strLine:*:=!"
::Check to see if the is defined and greater than 2 characters inidicating a good line
if defined strLine if NOT "!strLine:~2,1!"=="" if "!strLine:~0,1!!strLine:~-1!"=="[]" (set "LastGroup=!strLine!")
::Set the paramaters looking to match
set "DesiredGroup=[TestGroup]"
set "DesiredItem=TestItem"
set "ReplaceLineWith=NewTestItemLine=NewData"
::Look for match on current line
if defined strLine if "!LastGroup!"=="!DesiredGroup!" if NOT "!strLine!"=="!strLine:TestItem=Mod!" (set "strLine=!ReplaceLineWith!")
::Note, in the above line I would like 'TestItem' to be the 'DesiredItem' variable but I can't get it working due to the DelayedExpansion
::Set the additonal paramaters looking to match
::Note, there are multiple items I want to change at once without having to reitterate through the org long (2000+lines) file
set "DesiredGroup=[TestGroup2]"
set "DesiredItem=TestItem2"
set "ReplaceLineWith=NewTestItemLine2=NewData2"
if defined strLine if "!LastGroup!"=="!DesiredGroup!" if NOT "!strLine!"=="!strLine:TestItem=Mod!" (set "strLine=!ReplaceLineWith!")
::I plan to copy and paste the above section as many times as needed to capture all the lines I need to edit (at this point about ~10)
::I don't really understand why the "(" in the below line, I found it in an example on stackoverflow and it seems to work.
echo(!strLine!>>"%newFile%"
)
endlocal
::Replace org file with new file, delete org file (this part I have figured out)
Is there a better way of doing this? Can anyone help complete the code as I'm having a lot of trouble parsing this correctly.
Update: Thanks for the two methods proposed in the answers below. They are very long and I learned a lot from them. Not entirely sure how to implement the functions however and my biggest concern is that using the function will make repetitive reads from the file slowing it down dramatically. I'm very new to this bat file thing but I know its very powerful if you know the commands and are creative.
Thanks in advance for any and all help. -Dan
So many people want to use batch to edit text files - There are many SO questions dealing with the subject. But it is quite difficult (and relatively slow) to do so robustly using only native batch commands.
You are better off using some other tool. One good option is to use something like a free Windows port of sed or awk. But those require downloading non-native executables onto your machine, something that is forbidden in many offices.
I have written REPL.BAT - a hybrid JScript/batch utility that performs a regular expression search and replace on stdin and writes the result to stdout.. The script only uses native scripting available to all modern Windows machines from XP onward. Full documentation is embedded within the script, including a link to a MicroSoft page that describes all available JScript regex metacharacters.
Assuming that REPL.BAT is in your current directory, or better yet, somewhere within your PATH, then the following simple batch script can be used to modify the value of any item within a specific subsystem.
::MODIFY_CONFIG.BAT File SubSystem Item NewValue
::
:: Any argument that contains spaces or special characters should be quoted.
::
:: File = File to modify, may include full path
:: SubSystem = The section containing the item to modify (without brackets)
:: Item = The Item within the SubSystem that is to be modified
:: NewValue = The new value for the item
#echo off
type "%~1"|repl "(^ *\[%~2] *\r?\n(?: *[^[].*\n)*? *%~3=)[^\r\n]*" "$1%~4" m >"%~1.new"
move /y "%~1.new" "%~1" >nul
Here is a call to the script that changes Alignment within PressureLevelControl to 12345
MODIFY_CONFIG yourFile.ini PressureLevelControl Alignment 12345
#ECHO OFF
SETLOCAL
:: Read parameters
:: %1 is subgroup
:: %2 is item
:: %3 is new value
:: %3 missing = report value
SET "subgroup=%~1"
SET "item=%~2"
SET "newval=%~3"
IF NOT DEFINED subgroup ECHO syntax:%~nx0 "subgroup" "item" "newvalue"&GOTO :EOF
IF NOT DEFINED item ECHO syntax:%~nx0 "subgroup" "item" "newvalue"&GOTO :EOF
ECHO %*
:: state=0 (looking for subgroup) 1 (found subgroup)
SET /a state=0
:: result=0 (did nothing) 2 (found subgroup, not data line) 3 (found subgroup more than once)
:: 4 (found and replaced data line) 5 (found subgroup more than once, replaced data once)
:: 6 (detected dataline more than once, replaced once) 9 (reporting only - value found)
SET /a result=0
(
FOR /f "tokens=1*delims=:" %%a IN ('findstr /n /r "^" q21263073.txt') DO (
SET "line=%%b"
CALL :process
IF DEFINED repro ECHO(%%b
REM pause
)
)>newfile.txt
SET "replacefile="
CALL :report%result%
IF DEFINED replacefile ECHO A NEW FILE HAS BEEN CREATED
GOTO :EOF
:report0
ECHO [%subgroup%] NOT found
GOTO :eof
:report2
ECHO [%subgroup%] %item% NOT found
GOTO :eof
:report3
ECHO [%subgroup%] found repeatedly - %item% NOT found
GOTO :eof
:report4
ECHO [%subfound%] %olditem% found replaced %oldvalue% with %newval%
SET replacefile=Y
GOTO :eof
:report5
ECHO [%subgroup%] found repeatedly - %olditem% found replaced %oldvalue% with %newval%
GOTO :eof
:report6
ECHO [%subgroup%] %item% found repeatedly - %olditem% found replaced %oldvalue% with %newval% ONCE
GOTO :eof
:report9
ECHO [%subgroup%] %olditem% found with value %oldvalue%
GOTO :eof
:process
:: blank line ?
SET repro=Y
IF NOT DEFINED line GOTO :EOF
IF "%line:~0,1%%line:~-1%"=="[]" GOTO fsubsys
:: only process data lines if state=1
IF NOT %state%==1 GOTO :EOF
IF %result% gtr 5 GOTO :EOF
SET "fvalue="
FOR /f "tokens=1*delims==" %%p IN ("%line%") DO SET "fitem=%%p"&SET "fvalue=%%q"
:: Did we have an item=value line?
IF NOT DEFINED fvalue GOTO :EOF
CALL :matchit "%fitem%" "%item%"
IF NOT DEFINED matched GOTO :eof
:: we found a matching item within a subgroup.
:: result must be 2,3,4 or 5
FOR %%z IN (2.4 3.5 4.6 5.6) DO FOR /f "tokens=1,2delims=." %%c IN ("%%z") DO IF %result%==%%c SET result=%%d
IF %result%==6 GOTO :EOF
:: Haven't yet replaced value
:: Do we have a replacement?
SET "olditem=%fitem%"&SET "oldvalue=%fvalue%"
IF NOT DEFINED newval SET result=9&GOTO :eof
SET "repro="
ECHO(%fitem%=%newval%
GOTO :eof
:: found a subgroup name
:fsubsys
SET /a state=0
:: Is it the one we're looking for?
CALL :matchit "%line:~1,-1%" "%subgroup%"
IF NOT DEFINED matched GOTO :eof
SET /a state=1
FOR %%z IN (0.2 2.3 4.5) DO FOR /f "tokens=1,2delims=." %%c IN ("%%z") DO IF %result%==%%c SET result=%%d
IF %result%==2 SET "subfound=%line:~1,-1%"
GOTO :eof
:: match %1 to %2. If matches, set matched to not empty. if not, set matched to empty
:: here is where we can have some fun.
:matchit
SET "matched="
SET "string1=%~1"
SET "string2=%~2"
:: Case-insensitive exact match?
IF /i "%string1%"=="%string2%" SET matched=Y&GOTO :EOF
:: partial-string match. If specified item begins "+" then match rest against item found
:: so +ali matches "Alignment"
IF NOT %string2:~0,1%==+ GOTO npsm
CALL SET string3=%%string1:*%string2:~1%=%%
IF /i "%string3%"=="%string1%" GOTO :eof
IF /i "%string2:~1%%string3%"=="%string1%" SET matched=Y
GOTO :EOF
:: initials - so "Enable Dip Tube" is matched by "edt"
:npsm
CALL :inits %string1%
IF /i "%string3%"=="%string2%" SET matched=Y
GOTO :eof
:inits
SET "string3=%2"
IF NOT DEFINED string3 GOTO :EOF
SET "string3="
:initsl
SET string1=%1
IF NOT DEFINED string1 GOTO :EOF
SET string3=%string3%%string1:~0,1%
SHIFT
GOTO initsl
OK - I got carried away...
I used a file named q21263073.txt with your sample data for my testing. The file newfile.txt will be produced and may be made to replace the original if desired (advisable only if replacefile is defined.)
Required syntax is thisbatch subgroup item newvalue
Any parameter that contains spaces should be "quoted".
Features:
name-matching is case-insensitive.
You can use a leading + to abbreviate to a unique start-of-string, so +pr
would match PressureLevelControl.
Error report produced if abbreviation is not unique within a section.
You can abbreviate Space Separated Names to the initials SSN.
if the newvalue is omitted, a report of an existing value is shown.
It should be fairly obvious that you could easily make modifications to allow values to be added or deleted rather than just changed.
Playing with the filenames and other matters are now in the hands of those that show an interest.
ok basicly i want a simple batch program to change X characters to Y characters in a way like this
a-b
b-c
c-d
d-e
e-f
etc etc etc
ive looks up strings and other variable tricks but it doesnt work. heres what i tried and you can see on "codeb" where i tried an alternate method
set /p code=[paste your code here]
set code=%codea:~1%
set /a %code:~2% %codeb%
this was basically my way of attempting to split all the input characters into seperate variables.
if your feeling....bored below is the exact conversion for the translation
a='
b=v
c=x
d=s
e=w
f=d
g=f
h=g
i=u
j=h
k=j
l=k
m=n
n=b
o=i
p=o
q=\
r=e
s=a
t=r
u=y
v=c
w=q
x=z
y=t
z=/
essentially i should be able to "paste" this "v'rxg" into the batch and hit enter and it then displays "batch"
I modified Andriy's code for a faster version that use Batch variables instead of FIND text file. Here it is:
#echo off
setlocal EnableDelayedExpansion
for /f "delims=" %%a in (codechart.txt) do set %%a
:loop
set /P encoded=[paste your code here]
if "%encoded%" == "" goto :eof
set decoded=
call :decode
echo Result:
echo\%decoded%
goto loop
:decode
if "%encoded%" == "" goto :eof
set ec=%encoded:~0,1%
set encoded=%encoded:~1%
set dc=?
if defined %ec% set dc=!%ec%!
set decoded=%decoded%%dc%
goto decode
However, my version does not work when special characters (non-valid variable names) are encoded.
EDIT The only differences of my code vs. Andriy's are these:
setlocal EnableDelayedExpansion: This command is required to use !var! expansion besides %var%, allowing to use both expansions in the same command.
for /f "delims=" %%a in (codechart.txt) do set %%a: This command takes the lines of your conversions and execute a set command with each one, that is, set a=' set b=v etc. This way, the conversions are stored in Batch variables with the name of the original character and the value of the new character.
if defined %ec% set dc=!%ec%!: This line convert the character in ec variable via a direct replacement. For instance, if ec is "b": if defined b set dc=!b!; because b variable exists and contains a "v", then execute set dc=v.
Well, here's a working script that you can play with. It uses your chart in the form of a text file (named codechart.txt in my setup):
#ECHO OFF
:loop
SET /P encoded=[paste your code here]
IF [%encoded%]==[] GOTO :EOF
SET decoded=
CALL :decode
ECHO Result:
ECHO(%decoded%
GOTO loop
:decode
IF [%encoded%]==[] GOTO :EOF
SET ec=%encoded:~0,1%
SET encoded=%encoded:~1%
SET dc=?
FOR /F "delims==" %%C IN ('FIND "=%ec%" ^<codechart.txt') DO SET dc=%%C
SET decoded=%decoded%%dc%
GOTO decode
If you enter a non-empty string, it will produce a result. Hitting Enter without actually entering anything will terminate the batch. If a character is not found in the chart, it will be represented as a ? in the output.