How do I make a workaround for the FOR Loop? The problem is that DelayedExpansion fails for filenames with "!" in them
The code (this is part of a bigger script)
:FileScan
::Sets the filenames(in alphabetical order) to variables
SETLOCAL EnableDelayedExpansion
SET "EpCount=0"
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul') DO (
SET /a "EpCount+=1"
SET Ep!EpCount!=%%x
)
IF %EpCount%==0 ( Echo. & Echo: No Episodes Found & GOTO :Err )
:FolderScan
::Sets the foldernames(in alphabetical order) to variables
SET "FolderCount=0"
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n /a:d "%~dp0Put_Your_Files_Here" 2^>nul') DO (
SET /a "FolderCount+=1"
SET Folder!FolderCount!=%%x
)
If not possible in batch how do I do it in PowerShell to something that can be called by the original batch like:
:FileScan
Call %~dp0FileScanScript.ps1
IF %EpCount%==0 ( Echo. & Echo: No Episodes Found & GOTO :Err )
:FolderScan
Call %~dp0FolderScanScript.ps1
EDIT: CALL SET fixed the original code avoiding DelayedExpansion altogether
SET "EpCount=0"
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul') DO (
SET /a "EpCount+=1"
CALL SET "Ep%%EpCount%%=%%x"
)
The easiest solution is indeed to use call
set
to introduce another parsing phase:
setlocal DisableDelayedExpansion
pushd "%~dp0."
set /A "EpCount=0"
for /F "delims=" %%x in ('
dir /B /A:-D /O:N "*.mkv*" "*.mp4*" "*.avi*" "*.flv*" 2^> nul
') do (
set /A "EpCount+=1"
call set "Ep%%EpCount%%=%%x"
)
popd
endlocal
However, this causes problems as soon as carets ^
appear in the strings or file names, because call
doubles them when they appear quoted.
To solve this, you need to make sure that the strings are expanded in the second parsing phase too, which can be achieved by using an interim variable, like this:
setlocal DisableDelayedExpansion
pushd "%~dp0."
set /A "EpCount=0"
for /F "delims=" %%x in ('
dir /B /A:-D /O:N "*.mkv*" "*.mp4*" "*.avi*" "*.flv*" 2^> nul
') do (
set "Episode=%%x"
set /A "EpCount+=1"
call set "Ep%%EpCount%%=%%Episode%%"
)
popd
set Ep
endlocal
In addition, I added the filter option /A:-D
to dir
to ensure that no directories are returned. Furthermore, I used pushd
and popd
to change to the parent directory of the script temporarily. The dir
command line as you wrote it, searched files *.mkv*
in the parent directory of the script, but all the other ones in the current working directory, which is probably not what you wanted.
Another option is to toggle delayed expansion, so that the for
variable reference %%x
becomes expanded when delayed expansion is disabled, and the assignment to the Ep
array-style variables is done when it is enabled. But you need to implement measures to transport variable values beyond the endlocal
barrier then, like in the following example:
setlocal DisableDelayedExpansion
pushd "%~dp0."
set /A "EpCount=0"
for /F "delims=" %%x in ('
dir /B /A:-D /O:N "*.mkv*" "*.mp4*" "*.avi*" "*.flv*" 2^> nul
') do (
rem /* Delayed expansion is disabled at this point, so expanding
rem the `for` variable reference `%%x` is safe here: */
set "Episode=%%x"
set /A "EpCount+=1"
rem // Enable delayed expansion here:
setlocal EnableDelayedExpansion
rem /* Use a `for /F` loop that iterates once only over the assignment string,
rem which cannot be empty, so the loop always iterates; the expanded value
rem is stored in `%%z`, which must be assigned after `endlocal` in order
rem to have it available afterwards and not to lose exclamation marks: */
for /F "delims=" %%z in ("Ep!EpCount!=!Episode!") do (
endlocal
set "%%z"
)
)
popd
set "Ep"
endlocal
CALL SET Ep%%EpCount%%=%%x
would set your variables appropriately, as would
FOR /f "tokens=1*delims=[]" %%x IN (
'Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul ^|find /v /n ""'
) DO (
SET /a epcount=%%x
CALL SET ep%%x=%%y
)
(prefix each name with [num]
then use for
to extract num
to %%x
and name
to %%y
(assuming there are no names that start [
or ]
))
Note: The OP had originally tagged the question powershell in addition to batch-file , but later removed that tag (since restored).
The equivalent of the following batch-file ( cmd
) loop:
SETLOCAL EnableDelayedExpansion
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul') DO (
SET /a "EpCount+=1"
SET Ep!EpCount!=%%x
)
in PowerShell (PSv3+) is:
# Collect all episode paths in array $Eps
$Eps = (Get-ChildItem -Recurse $PSScriptRoot -Include *.mkv, *.mp4, *.avi, *.flv).FullName
# Count the number of paths collected.
$EpCount = $Eps.Count
The above will work with paths that have embedded !
instances too.
$Eps
will be an array of the full filenames.
To then process the matching files one by one :
foreach ($ep in $Eps) { # Loop over all files (episodes)
# Work with $ep
}
Using a single array rather than distinct $Ep1
, $Ep2
, ... variables makes for much simpler and more efficient processing.
The above is the fastest way to process the already-collected-in-memory filenames one by one.
A more memory-efficient (though slightly slower), pipeline-based approach would be:
Get-ChildItem -Recurse $PSScriptRoot -Include *.mkv,*.mp4,*.avi,*.flv | ForEach-Object {
# Work with $_.FullName - automatic variable $_ represents the input object at hand.
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.