0

I have some files

afjj.txt
agk.png
beta.tt
Ritj.rar

I use this script to move files in alphabetically order into autogenerated folders

a
|
|----> afjj.txt
|----> agk.png

b
|----> beta.tt

R
|----> Ritj.rar

To do I use this code

@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!" 
  )
)

What is problem? If I double-click on this batch, batch doesn't work, not move.
But this batch works from command line.
Why?

NOTE: I use Windows Server 2016

P.S: from command line I use this command and works but not if I double click directly on .bat

move.bat "Z:\# 2020\Alfa\test"
aschipfl
  • 33,626
  • 12
  • 54
  • 99
Jack
  • 3
  • 4
  • 1
    do not name a batch file using the same name as an existing cmdlet. Additionally, when using Setlocal within a for loop, you run the risk of exceeding the Setlocal stack unless you endlocal prior to the next iteration of the loop. Lastly Turn Echo on to verify each step is performing the intended assessment - and see what's happening when it's not. – T3RR0R Nov 07 '20 at 08:08
  • @Gerhard I edit so `@echo off 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!" ) ) ` it works but also mymove.cmd is moved. I don't understand how to edit this code adding your suggestions `pushd "%~1"` and then `popd` after last – Jack Nov 07 '20 at 10:23
  • 1
    The problem is: when you start the script by double-clicking, the working folder is not what you'd expect (it's probably `C:\Windows\System32`). Thank Microsoft that they made this folder ReadOnly (for non-priviledged users), else you would have ruined your Windows. – Stephan Nov 07 '20 at 10:30

4 Answers4

2

Here is a slightly amended script. Notice I removed the !first! variable as it is not required. I also built in a safety measure, if it is unable to pushd to the given directory you passed to %~1 it will not continue the move. Else it might move files in a path you do not want it to move files. i.e the working dir you started the script in.

@echo off
setlocal enabledelayedexpansion
pushd "%~1" && echo( || goto :end
for %%i in (*.test) do (
  set "name=%%~ni"
  if not "%%~nxi" == "%~nx0" (md !name:~0,1!)2>nul
    if not "!name:~0,1!" == "%%~i" move "%%~i" "!name:~0,1!\%%~i"
  )
popd
goto :eof
:end
echo The directory you entered does not exist. Exited script..
pause

Note, with the above you can also drag a directory to the batch file, which will process that directory.

Or if you plan on double clicking, without parameters from cmd.

@echo off
setlocal enabledelayedexpansion
for %%i in (*.test) do (
  set "name=%%~ni"
  if not "%%~nxi" == "%~nx0" (md !name:~0,1!)2>nul
    if not "!name:~0,1!" == "%%~i" move "%%~i" "!name:~0,1!\%%~i"
  )
pause
Gerhard
  • 22,678
  • 7
  • 27
  • 43
  • I double click on your version of script but doesn't work. It move nothing – Jack Nov 07 '20 at 11:25
  • Yes. Because of the fact that you did not provide `%~1` parameter. So if you are not planning on using `%1` then remove the ``pushd` and `popd` lines. Will edit. – Gerhard Nov 07 '20 at 11:27
2

The first mistake is naming the batch file move.bat. It is never a good idea to give a batch file the name of a Windows command because hat cause usually troubles. See also: SS64.com - A-Z index of Windows CMD commands.

The second mistake is using setlocal enabledelayedexpansion inside the loop without a corresponding endlocal also within same loop executed as often as setlocal. Please read this answer for details about the commands SETLOCAL and ENDLOCAL. The command SETLOCAL pushes several data on stack on every iteration of the loop and the data are never popped from stack in same loop on each loop iteration. The result is sooner or later a stack overflow depending on the number of files to process as more and more data are pushed on stack.

The third mistake is the expectation that the current directory is always the directory of the batch file. This expectation is quite often not fulfilled.

The fourth mistake is using a loop to iterate over a list of files which permanently changes on each execution of the commands in body of FOR loop. The loop in code in question works usually on storage media with NTFS as file system, but does not work on storage media using FAT32 or exFAT as file system.

The fifth mistake is the expectation that %0 expands always to name of the currently executed batch file with file extension, but without file path which is not true if the batch file is executed with full qualified file name (drive + path + name + extension), or with just file name without file extension, or using a relative path.

The sixth mistake is not enclosing the folder name on creation of the subfolder in double quotes which is problematic on file name starting unusually with an ampersand.

The seventh mistake is not taking into account to handle correct file names starting with a dot like .htaccess in which case the second character must be used as name for the subfolder, except the second character is also a dot. It is very uncommon, but also possible that file name starts with one or more spaces. In this case also the first none space character of file name should be used as Windows by default prevents the creation of a folder of which name is just a space character.

The solution is using following commented batch file with name MoveToFolders.cmd or MyMove.bat.

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem Get folder path of batch file assigned to an environment
rem variable. This folder path ends always with a backslash.
set "FilesFolder=%~dp0"

rem Optionally support calling of this batch file with another folder
rem path without checking if the passed string is really a folder path.
if not "%~1" == "" set "FilesFolder=%~1"

rem Replace all / by \ as very often people use / as directory separator
rem which is wrong because the directory separator is \ on Windows.
set "FilesFolder=%FilesFolder:/=\%"

rem The folder path should end always with a backslash even on folder
rem path is passed as an argument to the batch file on calling it.
if not "%FilesFolder:~-1%" == "\" set "FilesFolder=%FilesFolder%\"

rem Get a list of files in specified folder including hidden files loaded
rem into the memory of running command process which does not change on
rem the iterations of the loop below. Then iterate over the files list and
rem move the files to a subfolder with first none dot and none space character
rem of file name as folder name with the exception of the running batch file.
for /F "eol=| delims=" %%i in ('dir "%FilesFolder%" /A-D /B 2^>nul') do if /I not "%FilesFolder%%%i" == "%~f0" (
    set "FileName=%%i"
    set "FirstPart="
    for /F "eol=| delims=. " %%j in ("%%i") do set "FirstPart=%%j"
    if defined FirstPart (
        setlocal EnableDelayedExpansion
        set "TargetFolderName=%FilesFolder%!FirstPart:~0,1!"
        md "!TargetFolderName!" 2>nul
        if exist "!TargetFolderName!\" move "%FilesFolder%!FileName!" "!TargetFolderName!\"
        endlocal
    )
)

rem Restore the previous execution environment.
endlocal

The batch file can be started also with a folder path as argument to process the files in this folder without checking if the passed argument string is really referencing an existing folder.

Please read very carefully the answers on How to replace a string with a substring when there are parentheses in the string if there is interest on how to verify if a passed argument string really references an existing folder.

To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.

  • call /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • md /?
  • move /?
  • rem /?
  • set /?
  • setlocal /?
Mofi
  • 46,139
  • 17
  • 80
  • 143
0

A slightly different take.

This script will work on the current directory if double clicked, or run at the command line without a path specified.

But it will also allow you to provide a path to it as well.

@( SETLOCAL ENABLEDELAYEDEXPANSION
  ECHO OFF
  SET "_PATH=%~1"
  IF NOT DEFINED _PATH SET "_PATH=%CD%"
)

CALL :Main

( Endlocal
  Exit /B )

:Main
  For /F "Tokens=*" %%_ in ('
    DIR /B/A-D-S-H-L "%path%"
  ') DO (
    IF /I "%%_" NEQ "%~nx0" (
      SET "_TmpName=%%_"
      IF NOT EXIST "%_Path%\!_TmpName:~0:1!\"  MD "%_Path%\!_TmpName:~0:1!\"
      MOVE /Y "%_Path%\!_TmpName!" "%_Path%\!_TmpName:~0:1!\!!_TmpName!"
    )
  )
GOTO :EOF
Ben Personick
  • 3,074
  • 1
  • 22
  • 29
  • I try to run your script but sorry, still doesn't work with double click. I try to use Mofi scipt and works but your doesn't work. I use windows server 2016, i don't know if this is relevant or not. – Jack Nov 08 '20 at 00:17
0

Based upon only filenames beginning with alphabetic characters, here's a somewhat simpler methodology:

@Echo Off
SetLocal EnableExtensions
PushD "%~1" 2> NUL
If /I "%~dp0" == "%CD%\" (Set "Exclusion=%~nx0") Else Set "Exclusion="
For %%G In (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) Do (
    %SystemRoot%\System32\Robocopy.exe . %%G "%%G*" /Mov /XF "%Exclusion%" 1> NUL 2>&1
    RD %%G 2> NUL
)
PopD
Compo
  • 36,585
  • 5
  • 27
  • 39