I have a folder containing a bunch of subfolders and files, but the structure is a bit inefficient. For example:
Root Folder
----EmptyFolder1
--------Folder1
------------SubFolder1
------------File1
------------File2
----EmptyFolder2
--------Folder2
------------SubFolder2
------------File3
------------File4
How can I move all of the Folders/SubFolders/Files up in the tree and eliminate all of the EmptyFolders so that it looks more like this:
Root Folder
----Folder1
--------SubFolder1
--------File1
--------File2
----Folder2
--------SubFolder2
--------File3
--------File4
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir\t h r e e"
FOR /f "delims=" %%a IN (
'dir/s/b/a-d "%sourcedir%"'
) DO (
CALL :movefile %%a
)
:loop
SET "zapped="
FOR /d /r "%sourcedir%" %%a IN (.) DO (
RD "%%a" >NUL 2>NUL
IF NOT EXIST "%%a" SET zapped=Y
)
IF DEFINED zapped GOTO loop
DIR /s/b/ad "%sourcedir%
GOTO :EOF
:movefile
SET "oldfn=%*"
SET "newfn=!oldfn:%sourcedir%\=!"
SET "newfn=%sourcedir%\%newfn:*\=%"
FOR %%r IN ("%newfn%") DO (
ECHO MD "%%~dpr"
ECHO MOVE "%oldfn%" "%newfn%"
)
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances.
caution test on a representative subtree first!
The required MD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MD to MD to actually create the directories. Append 2>nul to suppress error messages (eg. when the directory already exists)
The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)
The section after the :loop label deletes any empty directories in the subtree. Of necessity, the commands are executed not merely displayed.
Be very, very careful about file/directorynames that contain symbols with special meaning to cmd.
Related
I have a folder named G:\Project\Projects
This folder has some(about 20) unknown subfolders. Some of the subfolders have a folder named SSS and a file called deleteme.doc
I want to delete all these SSS folders(with contents) and deleteme.doc files using a batch file or powershell file. I do not want to delete these files or folder deeply nested into the subfolders, just those who are directly under subfolder.
For example I want to delete this folder
G:\Project\Projects\proj 1\sss
but not these 3-
G:\Project\Projects\proj 1\other\sss
G:\Project\Projects\sss
G:\Project\Projects\proj 1\sss (it is a file)
The thing is I want to simply double click-run the batch/powershell file from anywhere, possibly from another partition or drive (or even network computer).
I tried RMDIR G:\Project\Projects\*\SSS it didn't seem to be working.
Please explain your answer if possible, also show me commands for both recycle bin delete and parma delete. Though I think sending to recycle bin is not possible without powershell.
Administrator privilege is not necessary to delete folders in non-system partition right?
Windows 10. powershell version 5
#ECHO Off
SETLOCAL
:: remove variables starting $
FOR /F "delims==" %%a In ('set $ 2^>Nul') DO SET "%%a="
:: set defaults
SET "$dirname=sss"
SET "$filename=deleteme.doc"
SET "$from=c:\wherever\you\like"
:: analyse command line
:: syntax file="filename" " dir=dirname" from="dirname" (/switches)
:: each is optional. default is as established above
:: /d - delete directory only if it is completely empty
:: /dn - delete directory only if it is not completely empty
:: /ds - delete directory only if it is completely empty or if no files in its subdirectory tree
:: /fe - delete file if empty
:: /fn - delete file if not empty
:: /b - both file and directory must exist to make deletions
::
FOR %%a IN (%*) DO IF DEFINED $twopart (CALL :parttwo %%a) ELSE (
SET "$processed="
IF /i "%%a"=="dir" SET "$processed=Y"&SET "$twopart=$dirname"
IF /i "%%a"=="file" SET "$processed=Y"&SET "$twopart=$filename"
IF /i "%%a"=="from" SET "$processed=Y"&SET "$twopart=$from"
IF /i "%%a"=="/b" CALL :setswitch b
IF /i "%%a"=="/d" CALL :setswitch d
IF /i "%%a"=="/dn" CALL :setswitch dn
IF /i "%%a"=="/ds" CALL :setswitch ds
IF /i "%%a"=="/fe" CALL :setswitch fe
IF /i "%%a"=="/fn" CALL :setswitch fn
IF NOT DEFINED $processed CALL :extras %%a
)
IF DEFINED $extras ECHO(%$extras% NOT understood)&pause&GOTO :EOF
:: resolve switch compatibility
IF DEFINED $d IF DEFINED $dn SET "$incompatible=/d /dn"
IF DEFINED $fe IF DEFINED $fn SET "$incompatible=%$incompatible% /f /fn"
IF DEFINED $incompatible ECHO(%$incompatible% incompatible)&pause&GOTO :EOF
:: if $from is set, make it quoted.
IF DEFINED $from SET "$from="%$from:"=%""
:: Now search for the directory
FOR /d /r %$from% %%a IN ("%$dirname%") DO IF EXIST "%%a\." (
SET "$victim=%%a"
CALL :deldir
)
IF NOT DEFINED $b FOR /r %$from% %%a IN ("%$filename%") DO IF EXIST "%%a" (
SET "$victim=%%~dpa%$filename%"
CALL :delfile
)
GOTO :eof
:: delete the victim directory if all other conditions are met
:: take care of file as well if required.
:deldir
SET "$victim=%$victim:"=%"
IF DEFINED $b IF NOT EXIST "%$victim%\..\%$filename%" GOTO :eof
:: if the directory has any contents and "/d" was specified, skip deletion
IF DEFINED $d (
FOR /f %%z IN ('dir /b "%$victim%" 2^>nul') DO GOTO :eof
)
:: if the directory has files in its subtree and "/ds" was specified, skip deletion
IF DEFINED $ds (
FOR /f %%z IN ('dir /s /b /a-d "%$victim%" 2^>nul') DO GOTO :eof
)
:: if the directory has no files and none in its subtree and "/dn" was specified, don't skip deletion
IF DEFINED $dn (
FOR /f %%z IN ('dir /b "%$victim%" 2^>nul') DO GOTO deldir1
GOTO :eof
)
:: delete the directory
:deldir1
ECHO(DEL /s /f /q "%$victim%"
SET "$victim=%$victim%\..\%$filename%"
IF EXIST "%$victim%" GOTO delfile
GOTO :eof
:delfile
FOR %%z IN ("%$victim%") DO (
IF DEFINED $fn IF "%%~zz"=="0" GOTO :EOF
IF DEFINED $fe IF "%%~zz" neq "0" GOTO :EOF
)
ECHO(DEL /f /q "%$victim%"
GOTO :eof
:parttwo
SET "%$twopart%=%~1"
SET "$twopart="
GOTO :eof
:extras
SET "$extras=%$extras% %~1"
GOTO :eof
:setswitch
SET "$processed=Y"
SET "$%1=Y"
SHIFT
IF "%1" neq "" GOTO setswitch
GOTO :EOF
Here's a general solution which you'd need to test before unleashing.
The required RD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(RD to RD to actually delete the directories.
The required DEL commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(DEL to DEL to actually delete the files.
The first section analyses the parameters ans switches supplied to the program.
The defaults are shown and need to be adjusted to suit your circumstances.
The command may be run as
*thisname* from=c:\new\location dir=xxx file=hello.txt /d
where from specifies where the tree-walk begins, fie the filename to be located and dir the directoryname to delete.
The switches are also listed.
The program simply examines the command line for elements of interest and sets values as required.
We then traverse the tree, looking for the directoryname and then make decisions depending on the switch-settings.
Then traverse the tree again, looking for the filename.
Something like
for /f %A in ('dir "c:\somewhere\deleteme.doc"') Do echo rd "%~dpA"
Administrator privilege is not necessary to delete folders in non-system partition right?
How would we know what permissions you have. Type icacls c:\somewhere to see.
You need to know your problem.
I created a short powershell solution using this-
$targetName='ss s';
foreach ($file in Get-Childitem "G:\Project\Projects\" )
{
$fname=$file.name;
if (Test-Path "G:\Project\Projects\$fname\$targetName\")
{
$shell = new-object -comobject "Shell.Application"
$item = $shell.Namespace(0).ParseName("G:\Project\Projects\$fname\$targetName")
$item.InvokeVerb("delete")
}
}
This powershell script sends that folder to recycle bin after confirmation popup. (this won't send any file named 'ss s')
this seems to be working for batch file script-
set "b=ss s"
for /d %%a in (*) do IF EXIST "%%a\%b%\*" (rmdir /s "%%a\%b%")
if the targetfolder was named "$ss s" then you have to set variables as "b=/$ss s"
I want to create a .bat script to copy only one random file from each folder (also subfolders, so recursively) whilst also keeping the folder structure. I've tried the following code which comes close to what I want but doesn't copy the folder structure and one file per folder.
#ECHO OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
SET Destination=H:\Temp
SET FileFilter=.ape
SET SubDirectories=/S
SET Source=%~dp1
SET FileList1Name=FileList1.%RANDOM%.txt
SET FileList1="%TEMP%\%FileList1Name%"
SET FileList2="%TEMP%\FileList2.%RANDOM%.txt"
ECHO Source: %Source%
IF /I {%SubDirectories%}=={/S} ECHO + Sub-Directories
IF NOT {"%FileFilter%"}=={""} ECHO File Filter: %FileFilter%
ECHO.
ECHO Destination: %Destination%
ECHO.
ECHO.
ECHO Building file list...
CD /D "%Source%"
DIR %FileFilter% /A:-D-H-S /B %SubDirectories% > %FileList1%
FOR /F "tokens=1,2,3 delims=:" %%A IN ('FIND /C ":" %FileList1%') DO SET TotalFiles=%%C
SET TotalFiles=%TotalFiles:~1%
ECHO The source has %TotalFiles% total files.
ECHO Enter the number of random files to copy to the destination.
SET /P FilesToCopy=
ECHO.
IF /I %TotalFiles% LSS %FilesToCopy% SET %FilesToCopy%=%TotalFiles%
SET Destination="%Destination%"
IF NOT EXIST %Destination% MKDIR %Destination%
SET ProgressTitle=Copying Random Files...
FOR /L %%A IN (1,1,%FilesToCopy%) DO (
TITLE %ProgressTitle% %%A / %FilesToCopy%
REM Pick a random file.
SET /A RandomLine=!RANDOM! %% !TotalFiles!
REM Go to the random file's line.
SET Line=0
FOR /F "usebackq tokens=*" %%F IN (%FileList1%) DO (
IF !Line!==!RandomLine! (
REM Found the line. Copy the file to the destination.
XCOPY /V /Y "%%F" %Destination%
) ELSE (
REM Not the random file, build the new list without this file included.
ECHO %%F>> %FileList2%
)
SET /A Line=!Line! + 1
)
SET /A TotalFiles=!TotalFiles! - 1
REM Update the master file list with the new list without the last file.
DEL /F /Q %FileList1%
RENAME %FileList2% %FileList1Name%
)
IF EXIST %FileList1% DEL /F /Q %FileList1%
IF EXIST %FileList2% DEL /F /Q %FileList2%
ENDLOCAL
The destination should be set in the .bat code like the code above. Can anybody please help me with this? Thanks in advance!
Copying a directory tree structure (folders only) is trivial with XCOPY.
Selecting a random file from a given folder is not too difficult. First you need the count of files, using DIR /B to list them and FIND /C to count them. Then use the modulo operator to select a random number in the range. Finally use DIR /B to list them again, FINDSTR /N to number them, and another FINDSTR to select the Nth file.
Perhaps the trickiest bit is dealing with relative paths. FOR /R can walk a directory tree, but it provides a full absolute path, which is great for the source, but doesn't do any good when trying to specify the destination.
There are a few things you could do. You can get the string length of the root source path, and then use substring operations to derive the relative path. See How do you get the string length in a batch file? for methods to compute string length.
Another option is to use FORFILES to walk the source tree and get relative paths directly, but it is extremely slow.
But perhaps the simplest solution is to map unused drive letters to the root of your source and destination folders. This enables you to use the absolute paths directly (after removing the drive letter). This is the option I chose. The only negative aspect of this solution is you must know two unused drive letters for your system, so the script cannot be simply copied from one system to another. I suppose you could programatically
discover unused drive letters, but I didn't bother.
Note: It is critical that the source tree does not contain the destination
#echo off
setlocal
:: Define source and destination
set "source=c:\mySource"
set "destination=c:\test2\myDestination"
:: Replicate empty directory structure
xcopy /s /t /e /i "%source%" "%destination%"
:: Map unused drive letters to source and destination. Change letters as needed
subst y: "%source%"
subst z: "%destination%"
:: Walk the source tree, calling :processFolder for each directory.
for /r y:\ %%D in (.) do call :processFolder "%%~fD"
:: Cleanup and exit
subst y: /d
subst z: /d
exit /b
:processFolder
:: Count the files
for /f %%N in ('dir /a-d /b %1 2^>nul^|find /c /v ""') do set "cnt=%%N"
:: Nothing to do if folder is empty
if %cnt% equ 0 exit /b
:: Select a random number within the range
set /a N=%random% %% cnt + 1
:: copy the Nth file
for /f "delims=: tokens=2" %%F in (
'dir /a-d /b %1^|findstr /n .^|findstr "^%N%:"'
) do copy "%%D\%%F" "z:%%~pnxD" >nul
exit /b
EDIT
I fixed an obscure bug in the above code. The original COPY line read as follows:
copy "%%~1\%%F" "z:%%~pnx1" >nul
That version fails if any of the folders within the source tree contain %D or %F in their name. This type of problem always exists within a FOR loop if you expand a variable with %var% or expand a :subroutine parameter with %1.
The problem is easily fixed by using %%D instead of %1. It is counter-intuitive, but FOR variables are global in scope as long as any FOR loop is currently active. The %%D is inaccessible throughout most of the :processFolder routine, but it is available within the FOR loops.
The "natural" way to process a directory tree is via a recursive subroutine; this method minimize the problems inherent to this process. As I said at this post: "You may write a recursive algorithm in Batch that gives you exact control of what you do in every nested subdirectory". I taken the code at this answer, that duplicate a tree, and slightly modified it in order to solve this problem.
#echo off
setlocal
set "Destination=H:\Temp"
set "FileFilter=*.ape"
rem Enter to source folder and process it
cd /D "%~dp1"
call :processFolder
goto :EOF
:processFolder
setlocal EnableDelayedExpansion
rem For each folder in this level
for /D %%a in (*) do (
rem Enter into it, process it and go back to original
cd "%%a"
set "Destination=%Destination%\%%a"
if not exist "!Destination!" md "!Destination!"
rem Get the files in this folder and copy a random one
set "n=0"
for %%b in (%FileFilter%) do (
set /A n+=1
set "file[!n!]=%%b"
)
if !n! gtr 0 (
set /A "rnd=!random! %% n + 1"
for %%i in (!rnd!) do copy "!file[%%i]!" "!Destination!"
)
call :processFolder
cd ..
)
exit /B
Here is anther approach using xcopy /L to walk through all files in the source directory, which does not actually copy anything due to /L but returns paths relative to the source directory. For explanation of the code see all the remarks:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define source and destination directories here:
set "SOURCE=%dp~1"
set "DESTIN=H:\Temp"
rem Change to source directory:
cd /D "%SOURCE%"
rem Reset index number:
set /A "INDEX=0"
rem Walk through output of `xcopy /L`, which returns
rem all files in source directory as relative paths;
rem `find` filters out the summary line; `echo` appends one more line
rem with invalid path, just to process the last item as well:
for /F "delims=" %%F in ('
2^> nul xcopy /L /S /I /Y "." "%TEMP%" ^
^| find ".\" ^
^& echo^(C:\^^^|\^^^|
') do (
rem Store path to parent directory of current item:
set "CURRPATH=%%~dpF"
setlocal EnableDelayedExpansion
if !INDEX! EQU 0 (
rem First item, so build empty directory tree:
xcopy /T /E /Y "." "%DESTIN%"
endlocal
rem Set index and first array element, holding
rem all files present in the current directory:
set /A "INDEX=1"
set "ITEMS_1=%%F"
) else if "!CURRPATH!"=="!PREVPATH!" (
rem Previous parent directory equals current one,
rem so increment index and store current file:
set /A "INDEX+=1"
for %%I in (!INDEX!) do (
endlocal
set /A "INDEX=%%I"
set "ITEMS_%%I=%%F"
)
) else (
rem Current parent directory is not the previous one,
rem so generate random number from 1 to recent index
rem to select a file in the previous parent directory,
rem perform copying task, then reset index and store
rem the parent directory of the current (next) item:
set /A "INDEX=!RANDOM!%%!INDEX!+1"
for %%I in (!INDEX!) do (
xcopy /Y "!ITEMS_%%I!" "%DESTIN%\!ITEMS_%%I!"
endlocal
set /A "INDEX=1"
set "ITEMS_1=%%F"
)
)
rem Store path to parent directory of previous item:
set "PREVPATH=%%~dpF"
)
endlocal
exit /B
For this approach the destination directory can also be located within the source directory tree.
I have a folder structure with a bunch of *.jpg files scattered across the folders.
Now I want to find some files listed in a CSV file (one column only) or a text file line by line like
one.jpg
ten.jpg
five.jpg
and copy all those *.jpg files to another folder keeping the folder structure like:
outputfolder\somefolder\somefolder\one.jpg
outputfolder\somefolder\ten.jpg
outputfolder\somefolder\somefolder\somefolder\five.jpg
How can I achieve this?
Here is what I've tried
#echo off
CLS
REM CHECK FOR ADMIN RIGHTS
COPY /b/y NUL %WINDIR%\06CF2EB6-94E6-4a60-91D8-AB945AE8CF38 >NUL 2>&1
IF ERRORLEVEL 1 GOTO:NONADMIN
DEL %WINDIR%\06CF2EB6-94E6-4a60-91D8-AB945AE8CF38 >NUL 2>&1
:ADMIN
REM GOT ADMIN RIGHTS
COLOR
1F ECHO Please wait...
FOR /R "%~dp0" %%I IN (.) DO
for /f "usebackq delims=" %%a in ("%~dp0list.txt") do
echo d |xcopy "%%I\%%a" "C:\B2B_output_files" /e /i
COLOR 2F
PAUSE
GOTO:EOF
:NONADMIN
REM NO ADMIN RIGHTS
COLOR 4F
pause
GOTO:EOF
Stack Overflow is not a free code writing service, see help topic What topics can I ask about here?
However, I have nevertheless written the entire batch code for this task. Learn from this commented code and next time try to write the batch code by yourself and ask only if you stick on a problem you can't solve by yourself after several trials and not finding a solution on Stack Overflow or any other website.
The paths of source and target base folder must be defined at top of the batch script below.
The text file containing the name of the files to copy line by line must be named FileNames.txt and must be stored in source base folder with using batch code below.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem Define source and target base folders.
rem Note:
rem The paths should not contain an equal sign as then the
rem string substitutions below would not work as coded. The
rem target base folder can be even a subfolder of the source
rem base folder.
set "SourceBaseFolder=C:\Temp"
set "TargetBaseFolder=C:\Temp\OutputFolder"
rem Set source base folder as current directory. The previous
rem current directory is restored by command endlocal at end.
if not exist "%SourceBaseFolder%\*" (
echo %~nx0: There is no folder %SourceBaseFolder%
set "ErrorCount=1"
goto HaltOnError
)
cd /D "%SourceBaseFolder%"
if not exist "FileNames.txt" (
echo %~nx0: There is no file %SourceBaseFolder%\FileNames.txt
set "ErrorCount=1"
goto HaltOnError
)
rem For each file name in text file FileNames.txt in
rem source base folder the loops below do following:
rem 1. Search recursively for a file with current file name
rem in entire directory tree of source base folder.
rem 2. If a file could be found, check its path. Skip the
rem file if the path of found file contains the target
rem folder path to avoid copying files to itself. This
rem IF condition makes it possible that target base
rem folder is a subfolder of source base folder.
rem 3. Create the folders of found file relative to source
rem base path in target base folder. Then check if this
rem was successful by verifying if the target folder
rem really exists and copy the file on existing folder or
rem output an error message on failure creating the folder.
set "ErrorCount=0"
for /F "usebackq delims=" %%N in ("FileNames.txt") do (
for /R %%J in ("%%N*") do (
set "FilePath=%%~dpJ"
if "!FilePath:%TargetBaseFolder%=!" == "!FilePath!" (
set "TargetPath=%TargetBaseFolder%\!FilePath:%SourceBaseFolder%\=!"
md "!TargetPath!" 2>nul
if exist "!TargetPath!\*" (
echo Copying file %%~fJ
copy /Y "%%~fJ" "!TargetPath!" >nul
) else (
set /A ErrorCount+=1
echo Failed to create directory !TargetPath!
)
)
)
)
:HaltOnError
if %ErrorCount% NEQ 0 (
echo.
pause
)
endlocal
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.
call /? ... for an explanation of %~nx0
copy /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
pause /?
rem /?
set /?
setlocal /?
And read also the Microsoft article about Using command redirection operators to understand 2>nul for suppressing error messages written to STDERR.
The following code should do what you asked for:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LISTFILE=copylist.txt"
set "SOURCE=.\source"
set "DESTIN=.\destin"
for /F "usebackq eol=| delims=" %%L in ("%LISTFILE%") do (
for /F "eol=| delims=" %%F in ('
cd /D "%SOURCE%" ^
^& xcopy /L /S /I /Y ".\%%~L*" "%TEMP%" ^
^| findstr /I /R "^\..*\\%%~L$"
') do (
2> nul md "%DESTIN%\%%F\.."
copy /B /-Y "%SOURCE%\%%F" "%DESTIN%\%%F"
)
)
endlocal
exit /B
It relies on the fact that xcopy outputs relative paths to the console if a relative source path is given, and that it features a switch /L that tells it not to copy anything but list what would be copied without the switch. There is also the switch /S which defines to search for the source item recursively also within subdirectories.
There is a small problem though which requires to be worked around: xcopy /S only walks through subdirectories if source contains a wildcard * or ?, but not if a dedicated file name is given. That is why * is appended to the file name. Since this could also match some unintended items of course, findstr is used to filter them out.
So basically there is a for /F loop that iterates through the items listed in the text file copylist.txt. Within this loop another for /F is nested that enumerates the output of the aforementioned findstr-filtered xcopy /L /S output, which receives the items of the outer loop one after another. The embedded cd command ensures to be in the source directory. The destination of xcopy is just an existing directory to avoid error messages (remember nothing is actually copied due to /L).
The inner loop bopy contains an md command that creates the destination directory (tree), if not existing (2> nul avoids error messages if has already been created earlier), and a copy command which actually performs the copying activity; the switch /-Y defines to prompt the user in case an item already exists at the destination location, you can change it to /Y to overwrite without asking.
I want to make a script that will iterate over all the folders in the current folder, then if a folder (that we iterate over) has only a single file or directory, to move that file or directory up one level. (And if possible to delete the now empty folder)
This is what I got so far:
for /d %s in (.\*) do (
#echo %s
set cnt=0
for %A in (%s) do set /a cnt+=1
echo File count = %cnt%
#echo %cnt //don't do anything
#echo %a //don't do anything
if (cnt leq 1) (
move .\*.* ..
)
)
But it doesn't work and I have no idea why...
I took code from:
count script from: https://stackoverflow.com/a/11005300/4279201
Iterate over subdirs of current dir: Iterate all files in a directory using a 'for' loop
move: https://superuser.com/a/180578/451485
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
:: First, create a list of subdirectorynames; iterate using `%%a`
FOR /f "delims=" %%a IN (
'dir /b /ad "%sourcedir%\*" '
) DO (
REM clear initial flags for each dir
SET "flag1="
SET "flag2="
REM Now read the subdirectory. set flag1 for first file, flag2 for second or later
FOR /f %%g IN (
'dir /b /a-d "%sourcedir%\%%a\*" 2^>nul'
) DO IF DEFINED flag1 (SET flag2=y) ELSE (SET flag1=y)
REM if neither flag set, directory is empty
REM if both are set, dir has 2 or more files
REM so - if flag1 but not flag2 is set then exactly 1 file.
IF DEFINED flag1 IF NOT DEFINED flag2 ECHO(MOVE "%sourcedir%\%%a\*" "%sourcedir%\"
REM if flag2 is not set, subdirectory is now empty
IF NOT DEFINED flag2 ECHO(rd "%sourcedir%\%%a"
)
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)
The required RD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(RD to RD to actually create the directories.
I'd suggest you target the batch on a tree that contains three subdirectories containing 0,1 and 2 files for testing.
Yur batch fails for a number of reasons.
The metavariable (loop-control variable) in a batch file requires a double % in every reference
environment variables are referenced by %var%, not %var.
within a block statement (a parenthesised series of statements), %var% will refer to the value of var at the time the block is initially encountered, not as the variable changes within the loop. This is the delayed expansion problem - well-documented here.
Note that this batch does not check whether a candidate to be moved already exists in the destination directory. If it does, then the move and rd statements will generate an error report and the file and directory will remain as-is.
NB: do not change any rem line to the :: form as :: is in fact a broken label which terminates a block (ie. will cause problems)
Edit - revision
#ECHO Off
SETLOCAL
SET "sourcedir=U:\sourcedir"
:: First, create a list of subdirectorynames; iterate using `%%a`
FOR /f "delims=" %%a IN (
'dir /b /ad "%sourcedir%\*" '
) DO (
REM clear initial flags for each dir
ECHO %%a
SET "subdir=%%a"
SET "flag1="
SET "flag2="
REM Now read the subdirectory. set flag1 for first file, flag2 for second or later
FOR /f "delims=" %%g IN (
'dir /b "%sourcedir%\%%a\*" 2^>nul'
) DO SET "name=%%g"&IF DEFINED flag1 (SET flag2=y) ELSE (SET flag1=y)
REM if neither flag set, directory is empty
REM if both are set, dir has 2 or more files/dirs
REM so - if flag1 but not flag2 is set then exactly 1 file/dir
IF DEFINED flag1 IF NOT DEFINED flag2 CALL :moveit
REM if flag2 is not set, subdirectory is now empty
IF NOT DEFINED flag2 rd "%sourcedir%\%%a"
)
GOTO :EOF
:moveit
REM NAME may be a file or a directory - "%sourcedir%\%%a\%NAME%\.." exists if directory
IF NOT EXIST "%sourcedir%\%subdir%\%NAME%\.." MOVE "%sourcedir%\%subdir%\*" "%sourcedir%\"
IF EXIST "%sourcedir%\%subdir%\%NAME%\.." IF NOT EXIST "%sourcedir%\%name%" MOVE "%sourcedir%\%subdir%\%NAME%" "%sourcedir%\"
GOTO :EOF
Caution! The above batch does NOT use ECHO( hence it will attempt to
move or delete files or directories. Use only on test subtree!
This minor revision moves single-directories as well as single-files up within the directories on the level below the target.
It displays sufficient information to infer the source of error messages if it can't move or delete as directed.
Essentially, it's the same as the original, exceot that a subroutine is called to make the changes. The subroutine uses name for the directoryname it hopes to move and subdir for the subdirectory being processed.
I have a hard drive with files and folders ordered in this similar manner:
F:\folder1\folder\folder\file.rar
F:\folder1\folder\folder\file1.rar
F:\folder1\folder\folder\file2.rar
F:\folder2\folder\file.rar
F:\folder2\folder\file1.rar
F:\folder3\folder\file.rar
F:\folder3\folder\folder\folder\file.rar
I'd like to move all files in this drive to F:\\*\ , Rename if a duplicate filename is found, and recursively delete empty folders afterwards. There's just too many of these folders to find out how deep each parent directory is. After executing the batch script the folders should look like:
F:\folder1\file.rar
F:\folder1\file1.rar
F:\folder1\file2.rar
F:\folder2\file.rar
F:\folder2\file1.rar
F:\folder3\file.rar
F:\folder3\file (1).rar
There might be folders with files already inside the F:\\*\ level. I want them to stay where they are.
try this:
#ECHO OFF &SETLOCAL
FOR /r "F:\" %%a IN (*.rar) DO (
SET "fname=%%~nxa"
SET "fpath=%%~fa"
FOR /f "tokens=1,2 delims=\" %%b IN ("%%~fa") DO SET "targetfolder=%%~b\%%~c"
SETLOCAL ENABLEDELAYEDEXPANSION
CALL :moveit "!fpath!" "!targetfolder!" "!fname!"
ENDLOCAL
)
GOTO :eof
:moveit
SETLOCAL
SET "nname=%~3"
:loop
SET /a fcount+=1
IF EXIST "%~2\%nname%" (
SET "nname=%~n3 (%fcount%)%~x3"
GOTO :loop
)
ECHO MOVE "%~1" "%~2\%nname%"
MOVE "%~1" "%~2\%nname%"
ENDLOCAL
EXIT /b