jackdaw
jackdaw

Reputation: 15

Move files to a subdirectory

Using some great help (Create subdirectory under each directory containing a file) I added a \pre subdirectory in any directory that contained a .jpg photo.

I want to move any .jpg files from their current directory into the \pre subdirectory. The script I tried is:

FOR /R c:\temp %G IN (*.JPG) DO pushd %~dpG && if exist *.jpg move *.jpg pre\ && popd

The script moved the .jpg files. The problem is that the script moves the files then follows to the \pre directory and tries to do the move again.

The pre directories have been created using the script linked to in the first paragraph.

For example, directory A\B\C was processed to give A\B\C\pre. This script scans A/B/C and moves the .jpgs to \A\B\C\pre. It then follows the directory tree into A\B\C\pre and tries to move the .jpg files again

Upvotes: 0

Views: 184

Answers (3)

Mofi
Mofi

Reputation: 49216

I suggest to use this batch file for the file moving task.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%I in ('dir "C:\Temp\*.jpg" /A-D /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /L /V /C:"\\pre\\" /C:"\\post\\"') do (
    if not exist "%%~dpIpre\" md "%%~dpIpre"
    move /Y "%%I" "%%~dpIpre\"
)
endlocal

FOR starts in background one more command process with %ComSpec% /c and the specified command line appended as additional arguments. So there is executed with Windows installed into C:\Windows in background:

C:\Windows\System32\cmd.exe /c dir "C:\Temp\*.jpg" /A-D /B /S 2>nul | %SystemRoot%\System32\findstr.exe /I /L /V /C:"\\pre\\" /C:"\\post\\"

DIR executed by the background command process searches

  • in directory C:\Temp and all its subdirectories because of option /S
  • just for files because of option /A-D (attribute not directory)
  • matching the wildcard pattern *.jpg
  • and outputs in bare format because of option /B
  • just the file names with full path because of option /S.

This list of file names is redirected from STDOUT (standard output) of background command process with redirection operator | to STDIN (standard input) of FINDSTR which searches

  • for lines containing case-insensitive because of option /I
  • either the literal string \pre\ or the literal string \post\
  • and outputs the inverted result because of option /V, i.e. the lines not containing \pre\ or \post\.

So FINDSTR is used here as filter to get from the list of *.jpg file names output by DIR with full path just those file names which do not have \pre\ or \post\ in their path to exclude the JPEG files which are already in one of the two subdirectories with name pre or post.

2>nul after the arguments of command DIR suppresses the error message output by DIR if it cannot find any *.jpg file name in C:\Temp and its subdirectories by redirecting the error message written to STDERR (standard error) to the device NUL.

Read the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operators > and | must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with findstr with using a separate command process started in background.

FOR with option /F captures everything written to handle STDOUT of started command process by FINDSTR and processes this output line by line after started cmd.exe terminated itself. It is very important here to process a captured list of file names and do not iterate over one file name after the other returned by the file system because of the files matched by wildcard pattern *.jpg are moved during each loop iteration within the directory structure. So the directory entries matching *.jpg changes on each loop iteration and therefore it is required that a list of file names is processed loaded into memory before moving the files.

FOR with option /F ignores empty lines which do not occur here.

FOR with option /F would split up each line into substrings using normal space and horizontal tab as string delimiters and would assign just first space/tab delimited string to specified loop variable I if not starting with default end of line character ; in which case the line would be completely ignored like an empty line.

A file name with full path cannot start with ;. So default eol=; must not be modified here. But the line splitting behavior is counterproductive because of a full qualified file name can contain one or more spaces. For that reason the option delims= is used to define an empty list of string delimiters which disables completely the line splitting behavior.

Therefore each full file name output by DIR not containing \pre\ or \post\ in path as filtered out by FINDSTR is assigned to loop variable I one after the other.

It is checked next if there is for the current JPEG file a subfolder pre and this folder is created if not already existing. Then the current JPEG file is moved into the subdirectory pre with overwriting the file in pre with exactly the same file name.

So this batch file can be executed multiple times on C:\Temp as it ignores all *.jpg files in all subdirectories pre and post

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.

  • dir /?
  • echo /?
  • endlocal /?
  • findstr /?
  • for /?
  • if /?
  • md /?
  • move /?
  • setlocal /?

Upvotes: 1

aschipfl
aschipfl

Reputation: 34989

What about the following script:

@echo off
rem // Enumerate the directory tree:
for /D /R "C:\TEMP" %%G in ("*") do (
    rem // Check whether current directory is not named `pre`:
    if /I not "%%~nxG" == "pre" (
        rem // Check whether there are files:
        if exist "%%~G\*.jpg" (
            rem // Create sub-directory called `pre`:
            md "%%~G\pre" 2> nul
            rem // Move files into the sub-directory:
            move "%%~G\*.jpg" "%%~G\pre"
        )
    )
)

Or directly in command prompt:

@for /D /R "C:\TEMP" %G in ("*") do @if /I not "%~nxG" == "pre" if exist "%~G\*.jpg" md "%~G\pre" 2> nul & move "%~G\*.jpg" "%~G\pre"

Upvotes: 2

lit
lit

Reputation: 16266

The problem of doing the operation in the new "pre" directory is solved by getting the list of directories before enumerating over them.

It might be nice to have a one-liner command to do it, but this is getting a bit more complex than what is easy to do in a one-liner.

Here is a PowerShell script that will do it. If you are on a supported Windows platform, PowerShell will be available. This script requires PowerShell 5.1 or higher. If you cannot get to the current PowerShell, the code can be changed to make it work. When you are satisfied that the correct files will be moved, remove the -WhatIf from the mkdir and Move-Item commands.

=== Move-JpegToPre.ps1

$dirs = Get-ChildItem -Directory -Path "C:/src/t"
$extent = 'jpg'

foreach ($dir in $dirs) {
    if (Test-Path -Path "$($dir.FullName)/*.$($extent)") {
        if (-not (Test-Path -Path "$($dir.FullName)/pre")) {
            mkdir "$($dir.FullName)/pre"
        }
        Move-Item -Path "$($dir.FullName)/*.$($extent)" -Destination "$($dir.FullName)/pre" -WhatIf
    }
}

In a cmd.exe shell it can be invoked by:

powershell -NoLogo -NoProfile -File Move-JpegToPre.ps1

Upvotes: 1

Related Questions