batch files, variable expansion and substrings in loops - variables

I can't wrap my head around the variable substitution mechanism of DOS batch files, especially in for loops.
Simplified batch problem: imagine the following directory
01foo.txt
02foo.dir (this is a directory)
bar01 (this is a directory)
bar02 (this is a directory)
i want to move all files/directories in this directory that do NOT start with 'bar' to a subdirectory that is bar+_the_first_2_characters_of_the_filename_or_directory_name.
In this case, 01foo.txt would be moved into bar01 and 02foo.dir would be moved into bar02.
The following script is my first attempt:
setlocal EnableDelayedExpansion
for %%A in (*) do (
set _x=%%A
if (!_x:~0,2! NEQ "bar") move %%A bar!_x:~0,2!
)
endlocal
apart from the fact that this seems to loop only for files, it simply doesn't work at all :-).
I get an error in the if statement saying "3! was unexpected at this time"
Any idea on how to improve the situation/script?
Thanks

It's only a problem of the syntax...
The IF statement does not expect not accept surrounding brackets.
The the comma in !_x:~0,2! breaks the IF-statment, you could quote both parts or move it into an own set prefix=!_x:~0,2!" line.
If you quote "bar" you also need to quote "!prefix!".
That's all
setlocal EnableDelayedExpansion
for %%A in (*) do (
set "_x=%%A"
set "prefix=!_x:~0,2!"
if "!prefix!" NEQ "bar" move %%A bar!prefix!
)
endlocal

Related

Using a Batch file can you remove part of a file name from files in a folder and all sub folders?

Hi I am new at using batch files and I am struggling to find a way of removing part of a file name for multiple files in a folder and all sub-folder.
the files are all named like r1_c02_200111_145423_am.csv and I need to remove the _am from the files.
I have tried the following
FOR /R "C:\Users\bob\Documents\data\" %%G IN (*_am.csv) DO REN "%%G" *.csv
but this does not change anything.
can anybody point me in the right direction please?
With the help of such a script, you can perform the required operation.
#ECHO off
FOR %%i IN (*_am.csv) DO CALL :do_rename %%i
GOTO :eof
:do_rename
SET "_file=%1"
REN "%_file%" "%_file:~0,-7%.csv"
:eof
Now more.
The search in the question was correct. To perform a rename while passing through the FOR ... DO, you can use both the SETLOCAL EnableDelayedExpansion or a procedure call. I think it's easier to use a procedure call. you don’t have to puzzle over understanding (and misunderstanding) how the SETLOCAL EnableDelayedExpansion works.
For each iteration of the loop, the Wick procedure is called with the first argument of the file name passed to it:
CALL :do_rename %%i
In the procedure itself, the argument is converted to a variable. You can already use substring operations on the variable itself. More information about substring operations can be found here.
:do_rename
SET "_file=%1"
REN "%_file%" "%_file:~0,-7%.csv"

Batch - move files into alphabetical forders including unicode or 'strange' characters

I have some files and I want to move them inside alphabetical folder NOT previously created. With batch I want generate folders. Theses files must be moved inside these folders following files's first letter
I have a multi language file list inside my directory like this:
中文
alfa
35h
Ĕuid
لعربية
សេវិនខ្មែរ
I try this command to move files into alphabetic folders using first folder letter for ordering
#echo off
setlocal enabledelayedexpansion
for /d %%i in (*) do (
set first=%%i
set first=!first:~0,1!
md !first! 2>nul
if not "!first!" == "%%i" move "%%i" "!first!\%%i"
)
Nothing happens.
This part
for /d %%i in (*) do (
create folders and move folders and not files but I want move files inside generated folders (I don't want create folders previously)
You're currently using for /d which works on directories, not files. Since you have files, you need your for loop to work on them. If you just remove the /d, it will do so. However, note that this means it will also move the batch file itself. If you don't want that, then you'll need to put in logic to exclude it. Something like this:
#echo off
setlocal enabledelayedexpansion
for %%i in (*) do (
set first=%%i
if not "!first!" == "%0" (
set first=!first:~0,1!
md !first! 2>nul
if not "!first!" == "%%i" move "%%i" "!first!\%%i"
)
)
EDIT FOR ! SUPPORT
If the filename contains an exclamation mark, it won't work, because when delayed expansion is enabled, it sees that as part of a variable delimiter. The way to get around it is to assign the filename to a variable before you enable delayed expansion:
#echo off
setlocal
for %%i in (*) do (
set name=%%i
setlocal enabledelayedexpansion
if not "!name!" == "%0" (
set first=!name:~0,1!
md !first! 2>nul
if not "!first!" == "!name!" move "!name!" "!first!\!name!"
)
)

batch file passing variable to a text file

ECHO off
FOR /r %%a IN (*) DO (
IF NOT "%pth%"==%%~da%%~pa (
ECHO "%pth%">>Liste.txt
)
SET pth=%%~da%%~pa
)
I want to pass a path just one time; not for every file subdirectories includes. How %pth% string variable can be passed to a text file? Why does this code not work?
Is this code wrong?
FOR /r %%a IN (*) DO (
SET pth=%%~da%%~pa
ECHO %pth%>>Liste.txt
)
The code below works. It passes the paths for each file. If there is files more than one in a subdirectory it passes same path as many as the number o files. I don't want it. I want passing the same path once.
FOR /r %%a IN (*) DO ECHO %%~da%%~pa>>Liste.txt
You haven't told use very clearly (with an example) what you want to do.
Fundamentally, within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Hence
#ECHO off
setlocal enabledelayedexpansion
FOR /r %%a IN (*) DO (
IF NOT "!pth!"=="%%~dpa" (
ECHO "!pth!">>Liste.txt
)
SET "pth=%%~dpa"
)
should work (I haven't tried it)
OR
for /r /d %%a in (*) do >>liste.txt echo %%a
which is probably easier (produce a listing of all subdirectories)
Note that %%~da%%~pa is equivalent to %%~dpa
Edited to include required "
#Magoo: Thanks. You show me the right.
The code below is which I want; but "if conditional" does not work: ("!pth!"==%%~dpa) check is always return FALSE I think.
#ECHO off
setlocal enabledelayedexpansion
FOR /r %%a IN (*) DO (
IF NOT "!pth!"==%%~dpa (
ECHO %%~dpa>>Liste.txt
)
SET "pth=%%~dpa"
)
#Magoo: The question is how the code above works like your one line code below:
for /r /d %%a in (*) do >>liste.txt echo %%a
or like written by #MC ND:
dir /ad /s /b >> liste.txt
At last this works. Thanks everyone.
code:
#ECHO off
setlocal enabledelayedexpansion
FOR /r %%a IN (*) DO (
IF NOT "!pth!"=="%%~dpa" (
ECHO %%~dpa>>Liste.txt
)
SET "pth=%%~dpa"
)
dir /ad /s /b >> liste.txt
This should do the same your code trying to do: append all the subfolders into the target file.
The problem with the original code is how the parser handles variables. In batch files, when a line or a block of lines is reached, the parser removes all variable reads, replacing them with its value before executing the code. So, if inside a line/block a variable is changed, this changed value can not be retrieved inside the same line/block as the read operation was removed. The usual way to handle it is to enable delayed expansion. This allows to change where needed variable read syntax from %var% into !var!, indicating to the parser that the read operation must be delayed until the code is executed.
So, in your case, filtering the path of the files
setlocal enabledelayedexpansion
set "pth="
FOR /r %%a IN (*) DO (
IF NOT "!pth!"=="%%~dpa" (
ECHO "%%~dpa">>Liste.txt
SET "pth=%%~dpa"
)
)
Put the IF %pth% condition outside the FOR and it should work.
What exactly do you want to do ? Perhaps theres a simpler way.

Removing everything after specific character in batch

SETLOCAL ENABLEDELAYEDEXPANSION
set "s=DIR D:\MyFolder /S /Q ^|FIND /i "Owner" ^|findstr /m /i "\.mkv$""
for /f "Tokens=5,6*" %%a in ('%s%') do (
SET _endbit=%%aa:*STRING=%
CALL SET _result=%%aa:%_endbit%=%%
>>%tmp%\list.txt echo %_result% %%b %%c
)
wscript "C:\my.vbs"
I am listing my files that owned by Owner and has extension mkv from MyFolder. I want to remove everything after specific character/word. I wrote that code. But It seems to be not working.
First of all, is it possible to do that? If so what is wrong with my code?
You came up with the same solution I was going to post on your other question - but I think you're missing some things. The line ending STRING=% is missing another % at the end, and the line %%aa:%_endbit%=%%, I'm not sure it can work directly on the variable from the for loop, and you need to store it in another variable first, and you probably need some expansions using ! characters too.
Here's what I had that seems to work, just as a test in a folder with three files in it, removing the end of the filename using E0 as the cutoff string:
SETLOCAL ENABLEDELAYEDEXPANSION
#echo off
for %%f in (*.mkv) do (
set FULLNAME=%%f
set ENDTEXT=!FULLNAME:*E0=!
call set TRIMMEDNAME=%%FULLNAME:!ENDTEXT!=%%
echo !TRIMMEDNAME!
)

whats the difference between: %%a and %variable% variables?

for /f "tokens=*" %%a in ('find /v ":" "%appdata%\gamelauncher\options.txt" ^| find "menu=a"') do ( set usemenu=a )
for /f "tokens=*" %%a in ('find /v ":" "%appdata%\gamelauncher\options.txt" ^| find "menu=b"') do ( set usemenu=b )
for /f "tokens=*" %%a in ('find /v ":" "%appdata%\gamelauncher\options.txt" ^| find "menu=c"') do ( set usemenu=c )
Right, in this code (which may not work, that what i'm trying to find out) we have this "%%a" in that 'for' command.
First, whats the difference between %variable% and %%a?
Second, can someone explain the 'for' command to me? I have Google'd it way too much and all the explanations seem way to complicated...
What I am trying to do is pull a variable from options.txt, so i can change the menu style of my game launcher. there are 3 styles (a, b and c), so if the options.txt reads "menu=a" how can i get it to set a variable like %usemenu% to the value of a?
Thanks for any help in advance!
%variable% are environment variables. They are set with set and can be accessed with %foo% or !foo! (with delayed expansion if enabled). %%a are special variables created by the for command to represent the current loop item or a token of a current line.
for is probably about the most complicated and powerful part of batch files. If you need loop, then in most cases for has you covered. help for has a summary.
You can
iterate over files: for %x in (*.txt) do ...
repeat something n times: for /l %x in (1, 1, 15) do... (the arguments are start, step and end)
iterate over a set of values: for %x in (a, b, c) do ...
iterate over the lines of a file: for /f %x in (foo.txt) do ...
tokenize lines of a file: for /f "tokens=2,3* delims=," %x in (foo.txt) do ...
iterate over the output of a command: for /f %x in ('somecommand.exe') do ...
That's just a short overview. It gets more complex but please read the help for that.
Variables of the form %%a (or %a if for is used outside of batch files) are very similar to arguments to batch files and subroutines (%1, %2, ...). Some kinds of expansions can be applied to them, for example to get just the file name and extension if the variable represents a file name with path you can use %%~nxa. A complete overview of those is given in help for.
On the other hand, environment variables have other kinds of special things. You can perform replacements in them via %foo:a=b% would result in %foo% except that every a is replaced by a b. Also you can take substrings: %foo:~4,2%. Descriptions of those things can be found in help set.
As to why %variables% and %%a are different things that's a bit hard to answer and probably just a historical oddity. As outlined above there is a third kind of variable, %1, etc. which are very similar to those used in for and have existed for longer, I guess. Since environment variables are a bit unwieldy to use in for due to blocks and thus heavy reliance on delayed expansion the decision probably was made to use the same mechanisms as for arguments instead of environment variables.
Also environment variables could be more expensive, given that the process has a special “environment” block of memory where they are stored in variable=value␀ pairs, so updating environment variables involves potentially copying around a bit of memory while the other kind of variables could be more lightweight. This is speculation, though.
As for your problem, you don't really need for here:
find /v ":" "%appdata%\gamelauncher\options.txt" | find "menu=a" && set usemenu=a
This will only run the set if the preceding command was successful, i.e. menu=a was found. This should be considerably easier than for. From what I read you're trying to look whether menu=a exists in a line that does not contain a colon and in that case usemenu should be set to a, right? (And likewise for b and c. You could try coaxing for into doing that by looping over the lines of the file or output and tokenizing appropriately to figure out the value of menu but depending on the format of the lines this can be tricky. If what you have there works in theory then you should simply stick to that. You can however use a loop around it to avoid having to repeat the same line three times for a, b and c:
for %%X in (a b c) do (
find /v ":" "%appdata%\gamelauncher\options.txt" | find "menu=%%X" && set usemenu=%%X
)
If the file you are parsing is simple, however, with just name=value pairs in each line where : foo would be a comment, then you could use for as well:
for /f "tokens=1,* eol=: delims==" %%A in (%appdata%\gamelauncher\options.txt) do (
if "%%A"=="menu" set usemenu=%%B
)
But that depends a little on the exact format of the file. Above snippet would now read the file line by line and for each line would discard everything after a colon (the eol=: option), use the equals sign as a token delimiter and capture two tokens: The part before the first = and everything after it. The tokens are named starting with %%A so the second one is implicitly %%B (again, this is explained in help for). Now, for each line we examine the first token and look whether it's menu and if so, assign its value to the usemenu variable. If you have a lot of possible options to support this is certainly easier to maintain :-).