Why variables set in for loops do not exist - batch-file

I write two for loops to do data automation. While variables echoed well in each loop, the last step (data process using a well-written batch) keeps giving errors that variables set previous do not exist.
The code loops through the subfolders (q1, q2, etc.) under the directory. For each subfolder, there is another for loop to set several variables. I echoed three variables fine in loops.
However, when using a batch called abc.rb, the error is COM_M does not exist.
Actually, the error is all three variables do not exist.
setlocal ENABLEDELAYEDEXPANSION
for /f %%f in ('dir /ad /b ') do (
echo %%f
pause
pushd %%f
for %%a in (*.a*.dat) do (
set COM_DATA=%%a
echo !COM_DATA!
set COM_V=%%f\com-v.dat
echo !COM_V!
set COM_M=%%f\com-M.dat
echo !COM_M!
)
chdir
set fig=someA
set matrix=someB
rem use a written batch (called abc.rb) to process data
abc.rb -a !COM_DATA! -b !COM_V! -c !COM_M! -d !fig! -e !matrix!
popd
)
endlocal
Can anyone find any bugs? Thank you!

I am not sure why the need to pushd into the dir, but as far as I can see, there is only a need for a single for loop:
#echo off
setlocal enabledelayedexpansion
set "fig=someA"
set "matrix=someB"
for /R %%a in (*.a*.dat) do (
set "COM_DATA=%%a"
echo !COM_DATA!
set "COM_V=%%~dpacom-v.dat
echo !COM_V!
set COM_M=%%~dpacom-M.dat
echo !COM_M!
rem If abc.rb is is NOT a windows batch file, remove call below
call abc.rb -a "!COM_DATA!" -b "!COM_V!" -c "!COM_M!" -d !fig! -e !matrix!
)
If you require the pushd (which I doubt)
#echo off
setlocal enabledelayedexpansion
set "fig=someA"
set "matrix=someB"
for /R %%a in (*.a*.dat) do (
pushd "%%~dpa"
set "COM_DATA=%%a"
echo !COM_DATA!
set "COM_V=%%~dpacom-v.dat"
echo !COM_V!
set "COM_M=%%~dpacom-M.dat"
echo !COM_M!
rem If abc.rb is is NOT a windows batch file, remove call below
call abc.rb -a "!COM_DATA!" -b "!COM_V!" -c "!COM_M!" -d !fig! -e !matrix!
popd
)
The double quotes will help if the paths have whitespace, if your program has an issue with them, then you can remove them: abc.rb -a !COM_DATA! -b !COM_V! -c !COM_M! -d !fig! -e !matrix!

#echo off
setlocal ENABLEDELAYEDEXPANSION
set "fig=someA"
set "matrix=someB"
set "COM_V=com-v.dat"
set "COM_M=com-M.dat"
for /f %%f in ('dir /ad /b') do (
echo %%f
pause
if exist "%%~f\*.a*.dat" (
pushd "%%~f" && (
for %%a in (*.a*.dat) do (
set "COM_DATA=%%~a"
echo !COM_DATA!
)
chdir
rem use a written batch called abc.rb to process data
call abc.rb -a "!COM_DATA!" -b "!COM_V!" -c "!COM_M!" -d "!fig!" -e "!matrix!"
popd
)
)
)
endlocal
Issues:
If the nested for loop finds no files with the matched pattern of *.a*.dat, then the variables COM_DATA, COM_V and COM_M may not be defined or updated with a newer value.
Value of COM_DATA is a filename. Values of COM_V and COM_M is the parent folder name and filename, which is inconsistent. Based on the current directory, I would consider filenames as correct. This means that COM_V and COM_M never need to change.
If abc.rb is a batch-file, then you need to use call for the interpreter to return control back to the main script.
Changes:
Test if the file pattern exists, and run the code within the code block if true.
COM_V and COM_M moved out of the for loop as values never change.
Calling abc.rb as being a batch-file.
fig and matrix moved out of the for loop as values never change.
Double quote setting of variables and use of variables to avoid issues with spaces, special characters etc.
pushd && ( ensures the code within the parentheses is run only on success of changing directory.
Removed parentheses in the rem line. They may not cause a problem, though rem lines are parsed and can cause a syntax error. Suggest avoiding special characters in rem lines unless you intend to debug.

Related

In Subversion, get full URL of branch for most recent change

Briefly - is there a simple svn command to tell us the "most recently changed branch"?
In more depth, as part of our build process, we're trying to detect the most recently-changed branch in Subversion, in order to perform further processing on that branch.
For example, if someone added the file:
http://server/svn/project/branches/Integration1/newfile.txt
we'd want to be able to determine the string:
http://server/svn/project/branches/Integration1
that is, the parent folder of the most recent change.
The closest we can find is to execute the following command:
svn log --verbose --limit 1 http://server/svn/project/
but it then gives a 4-line output that we'd need to parse, using batch.
We have a Jenkins build server with the Subversion plugin installed, which can detect the change to the overall subversion repository, but it only gives us the environment variables SVN_URL and SVN_REVISION, neither of which address our goal.
I would use:
svn log --verbose --quiet --limit 1 "http://server/svn/project/"
So you get rid of any commit log messages and have just a list of modified paths and a header. Then use a for /F loop loop to capture the paths:
rem // Capture output of `svn log`, skip header and hyphen-only lines:
for /F "skip=3 eol=- tokens=1*" %%I in ('
svn log --verbose --quiet --limit 1 "http://server/svn/project/"
') do echo(%%J
The tricky part is to derive the common root path of multiple items, which might be done like in the following script, for instance (see all the explanatory rem remarks in the code; provide the repository path http://server/svn/project/ as the first command line argument):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_REPOPATH=%~1" & rem // (take the first argument as the repository path)
rem // Clear buffer for common root path:
set "COMM="
rem // Clear all variables beginning with `$`:
for /F "delims=" %%K in ('set $ 2^> nul') do set "%%K="
rem // Capture output of `svn log`, skip header and hyphen-only lines:
for /F "skip=3 eol=- tokens=1*" %%I in ('
svn log --verbose --quiet --limit 1 "%_REPOPATH%"
') do (
rem // Store currently iterated path:
set "ITEM=%%J"
rem // Check whether buffer for common root path is empty:
if not defined COMM (
rem // Buffer is empty, hence this is the first iteration:
set "COMM=%%J"
) else (
rem // Otherwise, store each `/`-separated path element in array `$ITEM[]`:
set /A "IDX=0"
setlocal EnableDelayedExpansion
for %%K in ("!ITEM:/=" "!") do (
for %%L in (!IDX!) do (
endlocal
set "$ITEM[%%L]=%%~K"
set /A "IDX+=1"
setlocal EnableDelayedExpansion
)
)
endlocal
rem // Store each element of buffered common root path in array `$COMM[]`:
set /A "CNT=IDX-1, IDX=0"
setlocal EnableDelayedExpansion
for %%K in ("!COMM:/=" "!") do (
for %%L in (!IDX!) do (
endlocal
set "$COMM[%%L]=%%~K"
set /A "IDX+=1"
setlocal EnableDelayedExpansion
)
)
rem // Determine the lower number of elements of `$ITEM[]` and `$COMM[]`:
if !CNT! geq !IDX! set /A "CNT=IDX-1"
rem // Loop through both arrays and write all common elements to buffer:
set "COMM=" & set "FLAG=#"
for /L %%L in (0,1,!CNT!) do (
if /I "!$ITEM[%%L]!" == "!$COMM[%%L]!" (
rem // The flag is just necessary to stop at the first difference:
if defined FLAG set "COMM=!COMM!!$COMM[%%L]!/"
) else set "FLAG="
)
rem // Transport the built common root path buffer beyond `endlocal`:
for /F "delims=" %%K in (""!COMM!"") do (
endlocal & set "COMM=%%~K"
)
)
)
setlocal EnableDelayedExpansion
if defined COMM (
rem // Check if there was only one path and just get get its parent in case:
if not "!COMM:~-1!" == "/" (
for %%K in ("!COMM:/=\!") do (
endlocal & set "COMM=%%~pK"
setlocal EnableDelayedExpansion
set "COMM=!COMM:\=/!"
)
)
rem // Return the finally retrieved common root path of all paths:
echo(!COMM:~1,-1!
)
endlocal
endlocal
exit /B
I don't know if that will work in your Jenkins environment, but here is what I would do in a standard (eg bash) shell. I use a terse svn log:
svn log ^/branches -l1 -q --incremental
Adding a pipe to parse it, the full command would be:
svn log ^/branches -l1 -q --incremental | sed -n "s/.*| \(.*\) |.*/\1/p"
Replace the caret (^) with http://server/svn/project/.
EDIT: to get the branch name, increase the verbosity of svn log with -v to print the modified paths, and adapt the sed command to extract the branch name. The following works for me:
svn log ^/branches -l1 -q -v | sed -n '4 s|.*/branches/||p'
or more aggressive:
svn log ^/branches -l1 -q -v | sed -nr '4 s|.*/branches/([^/]+).*|\1|p'

Batch File: syntax is incorrect while setting variables

I am getting syntax error while seting variables . Can some one please tell me where i am doing wrong.
#echo off
setlocal EnableDelayedExpansion
cd C:\data
for %%i in (*.pgp)
do
(
set encrypted=%%i
set decrypted=!encrypted:.gpg=!
gpg --batch --yes --passphrase "xyz" -o !decrypted! --decrypt !encrypted!
)
endlocal
if i do the same logic with out seting any variables it works
for %%i in (*.pgp)
do
(
must be coded as
for %%i in (*.pgp) do (
ie. the do and the ) and the ( after the do must all be on the same physical line.
Also, in your replace set, have you specified .gpg in place of .pgp?? (in which case, %%~ni could be used in place of the substitution.
in the gpg line, perhaps you need to quote the decrypted and encrypted strings, or you could possibly use "%%~ni" and "%%i" respectively.

Rename Files using wildcard paths

Recently I started working and my first task is to write a batch file that automatically changes filenames to filename_date with the original file-ending.
For that you should be able to write paths into a textfile (e.g. paths.txt) and when you start the program, it should take any line (=path->file) from there and rename it.
I got it to work on my PC quiet well but as I gave it to testing they asked to make the use of wildcards Z:\Path\*.* possible.
My current code looks as follows:
#echo off
setlocal EnableDelayedExpansion
cd %~dp0
For /F "tokens=*" %%m in (paths.txt) do (
set path=%%~dpm
set name=%%~nxm
pushd "!path!"
dir
For /r !path! %%f in (!name!) do (
set path=%%~dpf
set name=%%~nf
set ending=%%~xf
set datsave=%%~nxf
set "name=!name!_"
set "name=!name!!date:~6,4!"
set "name=!name!!date:~3,2!"
set "name=!name!!date:~0,2!"
set "name=!name!!ending!"
copy "!datsave!" "!name!"
del "!datsave!"
cls
popd
)
)
I know that a lot of it is probably easier and more efficient to do, but this is my first batch project and I am quiet happy except for the wildcard problem.
So an example would be:
C:\Some\Path\*.*
This line would be in paths.txt.
With the splitting
set path=%%~dpf
set name=%%~nf
set ending=%%~xf
set datsave=%%~nxf
I get the following:
path: C:\Some\Path
name: C:\Some\Path
ending: -empty-
datsave: C:\Some\Path
because name is set to the Path at the start of the first FOR-Loop. But that seems to be working if I do not use wildcards.
Now the question: Why does this happen and how do I get rid of it? Or do I just use the wrong type of wildcards?
Again: This is my first time I work with batch, so it might be something simple ;)
Ok, I figured out 2 problems and now it works
set name=%%~nxm evaluates the wildcard. Even if name is *.txt it will return bar.txt.
I replaced that by a basename computation instead: set name=!name:*\=! done enough times (not very subtle but hey batch files forces us to do such things) which preserves the wildcard
The other problem is the for /R loop: after pushd, the argument needs to be . or it won't be scanned.
Last minor one: use rename instead of copy plus delete. It preserves file time and is very fast. Copying then deleting a large file can take a long time.
#echo off
set DEPTH=20
setlocal EnableDelayedExpansion
cd %~dp0
For /F %%m in (paths.txt) do (
set pth=%%~dpm
set z=%%m
set name=!z!
rem brutal basename. We cannot break the inner loop or
rem it would break the upper loop too
for /L %%I in (1,1,%DEPTH%) do set name=!name:*\=!
rem but we can check if it is really a basename
set chkname=!name:*\=!
if not !chkname!==!name! ( echo please increase DEPTH value
pause
exit /B)
rem set name=%%~nxm
pushd "!pth!"
For /r . %%f in (!name!) do (
set pth=%%~dpf
set name=%%~nf
set ending=%%~xf
set datsave=%%~nxf
set "name=!name!_!date:~6,4!!date:~3,2!!date:~0,2!!ending!
echo renaming "!datsave!" to "!name!"
rem ren "!datsave!" "!name!"
popd
)
)
paths.txt contains just a line C:\full\path\to\test\*.txt
my test directory contains 2 text files and 1 other file
output:
renaming "bar.txt" to "bar_20160812.txt"
renaming "foo.txt" to "foo_20160812.txt"
(just uncomment the ren line to get the job done)
Weeeeell First of all thanks again to #Jean-François Fabre and #aschipfl for their patience with me :)
After the hint with the second batch file I had to test a few things as not everything worked as fine, but now everything works great!
Code of the Main file:
#echo off
setlocal EnableDelayedExpansion
cd %~dp0
set DEPTH=20
For /F %%m in (paths.txt) do (
pause
set pth=%%~dpm
REM pushd !pth!
REM set origpth=!cd!
REM popd
set z=%%m
set name=!z!
For /L %%i in (1,1,%DEPTH%) do set
name=!name:*\=!
set chkname=!name:*\=!
if not !chkname!==!name! ( echo depth to small
pause
exit /B)
rem set name=%%~nxm
pushd "!pth!"
For /r . %%f in (!name!) do (
pushd %~dp0
call renamefiles.bat %%f REM "!origpth!"
popd
)
)
And the code of the sub-file:
#echo off
REM set pth=%~dp1
REM set origpth=%2
REM set origpth=%origpath:"=%\
REM If !pth!==%origpth% (
set path=%~dp1
set name=%~n1
set ending=%~x1
set datsave=%~nx1
pushd !path!
set "name=!name!_!date:~6,4!!date:~3,2!!date:~0,2!!ending!"
pause
echo renaming "!datsave!" to "!name!"
rem "!datsave!" "!name!"
cls
popd
REM )
EDIT: After testing around a bit I figured, that subfolders are included as well! I put extra code to both codes marked with REM and two extra spaces. Take out those REM's and the programm will not longer include subfolders when renaming :)

zip using batch file

I need to make a condition for unzipping a file using batch.
The method is as follows:
If dummy=1, the batch will unzip an xlsx file to a particular folder, else , it will copy another xlsx file directly to that folder.
my code is as follows:
set dummy=1
If %dummy% EQU 1 (
SET CHEMIN=C:\file_to_unzip
cd %CHEMIN%
for /f %%j in ('dir /b %CHEMIN%\*.zip') do (
C:\PROGRA~1\WinZip\Winzip32 -min -e -o -j %CHEMIN%\%%j %CHEMIN%
)
COPY "C:\file_to_unzip\*.xlsx" "C:\destination"
) else (
COPY "C:\file_to_copy\*.xlsx" "C:\destination"
)
The problem is that without the IF condition, the unzip command will work properly. But once the IF condition is included, the cmd will say that the zip file cannot be found. I am not sure what causes this issue and how to solve this.
Any help appreciated.
Do you have delayed expansion enabled? setlocal enabledelayedexpansion
With your example code above, the failure will occur due to the CHEMIN variable being set within the if statement. If a variable is set within a parentheses scope, the new value set for that variable will not be available until the all the sub scopes have ended. Run the following script to see what I mean:
#echo off
set "xValue=1"
echo.%xValue%
if 1 EQU 1 (
set "xValue=2"
echo.%xValue%
echo.!xValue!
)
If delayed expansion is disabled (which it is by default), the results will be
1
1
1
if enabled, add setlocal enabledelayedexpansion at the beginning
1
1
2
Try your script as follows. To use
setlocal enabledelayedexpansion
set dummy=1
If %dummy% EQU 1 (
SET CHEMIN=C:\file_to_unzip
cd !CHEMIN!
for /f %%j in ('dir /b !CHEMIN!\*.zip') do (
C:\PROGRA~1\WinZip\Winzip32 -min -e -o -j !CHEMIN!\%%j !CHEMIN!
)
COPY "C:\file_to_unzip\*.xlsx" "C:\destination"
) else (
COPY "C:\file_to_copy\*.xlsx" "C:\destination"
)
Another solution would be to just move the set CHEMIN=C:\file_to_unzip line outside of the if statement.

Trying to use a batch file to work with filenames that have a forbidden character

Okay, a brief explanation of what I am doing: I use Windows Media Center (Windows 7) to record Jeopardy every evening. I then use Handbrake to convert the .wtv files to .mkv files and then transfer them to my NAS so I can watch them later using Plex Media Server/Center. Rather than doing this "by hand", I'm trying to automate the process using a batch file as a scheduled task. Initially, I had set up a script so that I could right-click > Send To > convert.bat and it would initiate the command-line interface for Handbrake and convert the file, move the output to my NAS, and delete the original file (worked great).
Now, what I'm doing is initiating the batch script as a scheduled task and looping through the contents of my "recorded tv" directory and looping through any .wtv files to convert/move/delete them.
The problem lies in the fact that Windows Media Center correctly names the Jeopardy files with the "!" in them (Eg: Jeopardy!_KHQ_2012_12_04_21_12_12.wtv), which completely bricks my script. The "Send To" batch file worked great, but when I loop through the *.wtv files in the directory, it returns all the filenames with the "!" stripped out which means I can't do squat with them. Files without "!" do process without a hitch.
Thanks in advance to anyone who can get me pointed in the right direction! (and if you happen to see any other areas where this script could be improved, that's fine too...)
Here is the basic code that I am attempting to use:
#echo off
setlocal ENABLEDELAYEDEXPANSION
SET count=0
SET getFolder=C:\Users\Public\Recorded TV\
SET ripFolder=C:\Rips\
SET putFolder=Z:\Videos\Recorded TV\
FOR %%F IN ("%getFolder%*.wtv") DO (
SET /A count=!count!+1
REM DETERMINE OUTPUT FILENAME
for /f "tokens=5,6,7,8,9,10 delims=\_" %%a in ("%%F") do (
set show=%%a
set station=%%b
set year=%%c
set month=%%d
set day=%%e
set hour=%%f
REM GENERATE OUTPUT NAMING CONVENTION
set output=!show! s!year!e!month!!day! !hour!
)
REM PROCESS WITH HANDBRAKE CLI
"C:\Program Files\Handbrake\HandBrakeCLI.exe" -i "%%F" -t 1 -c 1 -o %ripFolder%!OUTPUT!.mkv -f mkv --deinterlace="fast" --crop 58:60:2:2 --strict-anamorphic -e x264 -q 20 --vfr -a 1 -E faac -B 160 -6 dpl2 -R Auto -D 0 --gain=0 --audio-copy-mask none --audio-fallback ffac3 -x ref=1:weightp=1:subq=2:rc-lookahead=10:trellis=0:8x8dct=0 --verbose=1
REM MOVE CONVERTED FILE TO NAS
copy "%ripFolder%!OUTPUT!.mkv" "%putFolder%"
REM DELETE ORIGINAL
del "%%F"
REM DELETE LOCAL RIP
del "%ripFolder%!output!.mkv"
)
echo %count% files processed
pause
ENDLOCAL
As you have recognized, the exclamation mark is stripped before you can escape it.
That's because you expand the FOR-loop variable %%F while delayed expansion is enabled, and the exclamation mark tries to expand a variable.
You need to toggle the delayed expansion here, as the variable contents are safe when using with delayed expansion, but to get the value you need the disabled mode.
#echo off
setlocal DisableDelayedExpansion
SET count=0
FOR %%F IN ("%getFolder%*.wtv") DO (
set "orgFile=%%F"
SET /A count+=1
REM DETERMINE OUTPUT FILENAME
for /f "tokens=5,6,7,8,9,10 delims=\_" %%a in ("%%F") do (
set show=%%a
set station=%%b
set year=%%c
set month=%%d
set day=%%e
set hour=%%f
setlocal EnableDelayedExpansion
REM GENERATE OUTPUT NAMING CONVENTION
set output=!show! s!year!e!month!!day! !hour!
)
REM PROCESS WITH HANDBRAKE CLI
"C:\Program Files\Handbrake\HandBrakeCLI.exe" -i "%%F" -t 1 -c 1 -o %ripFolder%!OUTPUT!.mkv -f mkv --deinterlace="fast" --crop 58:60:2:2 --strict-anamorphic -e x264 -q 20 --vfr -a 1 -E faac -B 160 -6 dpl2 -R Auto -D 0 --gain=0 --audio-copy-mask none --audio-fallback ffac3 -x ref=1:weightp=1:subq=2:rc-lookahead=10:trellis=0:8x8dct=0 --verbose=1
REM MOVE CONVERTED FILE TO NAS
copy "%ripFolder%!OUTPUT!.mkv" "%putFolder%"
REM DELETE ORIGINAL
del "!orgFile!"
REM DELETE LOCAL RIP
del "%ripFolder%!output!.mkv"
endlocal
)

Resources