I am trying to create a BAT that automatically sorts the ZIPs in the input folder into the corresponding target folder.
All ZIPs begin with a serial number 001 File.zip, 002 File.zip, 003 File.zip, etc.
The target folders are named according to the same scheme: 001, 002, 003, ..., 999.
So if a ZIP comes into the input folder, it should automatically be assigned to the corresponding target folder with the same number.
But since the target folder is up to 999 I don't want to write all paths beforehand ...
So far my code looks like this:
SET input=C:\Daten\Input
SET target=C:\Daten\Target
for /r %input% %%a in (*.zip) do (
Set FILE=%%~nxa
Set FILEWITHOUTEXT=%%~na
Set SPLITABLE_NAME=!FILEWITHOUTEXT:_= !
for %%t in (!SPLITABLE_NAME!) do set FIRSTTOKEN=%%t
)
copy /Y %%a %target%\!FIRSTTOKEN!\!FILE!
Unfortunately, the whole thing just doesn't work at all.
Based on Stephan's comment and some additional code cleanup and best practice usage your code should look like this.
:sort
SET "input=C:\Daten\Input"
SET "target=C:\Daten\Target"
for /r "%input%" %%a in (*.zip) do (
For /f "tokens=1 delims=_ " %%t in ("%%~na") do (
copy /Y "%%~a" "%target%\%%t\"
)
)
I see that you have already selected and answer. If you wanted to push ahead into a modern day scripting language, you could consider using PowerShell. If you are on a supported Windows platform, PowerShell will be available.
Once you are confident that the files will be correctly copied, remove the -WhatIf from the Copy-Item command.
powershell -NoLogo -NoProfile -Command ^
"Get-ChildItem -File -Path 'C:\Daten\Input' -Filter '???_*.zip' |" ^
'ForEach-Object {" ^
"if ($_.Name -match '^(\d{3})_.*') {" ^
"Copy-Item $_.FullName "C:\Daten\Target\$($Matches[1])" -Whatif" ^
"}" ^
"}"
Related
I don't know if its age or lack of practice. Either way I cannot wrap my head around this issue. I am trying to set a subroutine but the string that I am passing along are being split because of the space in string 3 and 4.
SET SubA=String1,String2,String 3,String 4
FOR %%A IN (%SubA%) DO MD "%%A"
I've tried parenthesis around the string
The <> brackets like Microsoft says to use. I have also tried this line below without success.
setlocal enableDelayedExpansion
For /F "tokens=* delims=" %%A IN (%Var%) DO MD "%%A"
Also I would love if possible could I make an list, possibly with an array. Like in Power shell I could do this. I really need to keep in the same batch file so the user could edit the list. I am aware that I could use caret but the easier I can make it for my client the better.
$Folders (
String1
String2
String 3
String 4
)
Edit: My desired result is to have this script create a set of folders like those pictured.
The simplest pure batch-file solution that doesn't require trickery is to use for's ability to enumerate space-separated tokens.
For this to work as intended, tokens that themselves contain spaces must be double-quoted:
#echo off & setlocal
:: Space-separated list of folder names, with names that contains
:: spaces themselves double-quoted.
SET SubA=String1 String2 "String 3" "String 4"
:: Loop over the list elements and create a directory for each.
FOR %%A IN (%SubA%) DO MD %%A
As Compo's helpful answer implies, you could actually pass this list to a single invocation of
MD: MD %SubA%
Unfortunately, as far as I know, batch files do not offer a convenient way to define lists in a one-item-per-line format.
However, you could provide the list of names via an external file, with each name on its own line (no double-quoting needed), which can then be parsed with for /f; e.g.:
#echo off & setlocal
:: Determine the full path of the file "names.txt"
:: located in the same folder as this batch file.
set "nameList=%~dp0names.txt"
:: Loop over all names in the file and call `md` with each.
for /f "usebackq delims=" %%n in ("%nameList%") do md "%%n"
And input file names.txt would then contain something like:
String1
String2
String 3
String 4
The simplest way to 'make' your directories is probably like this:
Set "SubA=String1,String2,String 3,String 4"
MD "%SubA:,=" "%" 2>NUL
As for working with the initial variable, and using it array like, you could do it like this:
#Echo Off & SetLocal EnableExtensions EnableDelayedExpansion
Set "SubA=String1,String2,String 3,String 4"
For /F "Delims==" %%G In ('"(Set Index[) 2>NUL"') Do Set "%%G="
Set "i=0"
Set "Index[!i!]=%SubA:,=" & Set /A i += 1 & Set "Index[!i!]=%"
(Set Index[) 2>NUL && Pause
Or you could do it like this:
#Echo Off & SetLocal EnableExtensions EnableDelayedExpansion
Set "SubA=String1,String2,String 3,String 4"
For /F "Delims==" %%G In ('"(Set Index[) 2>NUL"') Do Set "%%G="
Set "i=-1"
For %%G In ("%SubA:,=","%") Do (Set /A i += 1
Set "Index[!i!]=%%~G")
(Set Index[) 2>NUL && Pause
Here is another way to create the directories using the PowerShell that is already on your system if it is still supported by Microsoft. When you are satisfied that the correct directories will be created, remove the -WhatIf from the mkdir command.
SET "SubA=String1,String2,String 3,String 4"
powershell -NoLogo -NoProfile -Command ^
"'%SubA%'.split(',') | ForEach-Object { mkdir $_ -WhatIf | Out-Null }
A better way would be to test to see if the directory already exists before trying to create it.
SET "SubA=String1,String2,String 3,String 4"
powershell -NoLogo -NoProfile -Command ^
"'%SubA%'.split(',') | ForEach-Object {" ^
"if (-not (Test-Path -Path $_)) { mkdir $_ | Out-Null }" ^
"}"
I'm looking for some commands to add to my script.
The script goes through a folder of text log files and looks for a string passed as input.
Unfortunately, the log files overflow at a set size and don't have their date in the file name, just a number suffix and a string prefix. In the log file lines there is only the time of the event, not the date. The date is added as a single line at 12 o'clock.
Example of the file folder where the search is done:
Logfile_001.txt
Logfile_002.txt
Reboot-01_Logfile_001.txt
Reboot-01_Logfile_002.txt
Reboot-01_Logfile_003.txt
Reboot-02_Logfile_001.txt
Reboot-02_Logfile_002.txt
I want to add the "date modified" attribute of the searched file in each line of the FINDSTR result.
My current code:
#ECHO off
DEL /F _Result.searchresult
SET /P searchterm=Enter search term:
#ECHO Searching for %searchterm% >_Result.searchresult
#ECHO --------------------------------------------- >>_Result.searchresult
FINDSTR /S /C:"%searchterm%" *.txt >>_Result.searchresult
_Result.searchresult
Current result:
Reboot-79_Logfile_001.txt:08:29:05.586 loginfo
Reboot-79_Logfile_001.txt:08:30:05.586 loginfo
Wanted result:
2019/08/13 Reboot-79_Logfile_001.txt:08:29:05.586 loginfo
2019/08/13 Reboot-79_Logfile_001.txt:08:30:05.586 loginfo
Or:
Reboot-79_Logfile_001.txt:2019/08/13 08:29:05.586 loginfo
Reboot-79_Logfile_001.txt:2019/08/13 08:30:05.586 loginfo
Where "2019/08/13" is the date of the "date modified" attribute of file Reboot-79_Logfile_001.txt
#OP I had to make a minor Change as I wrote this on my phone on the train originally and I made a couple typos etc. that were present when you tested
Specifically these two single character changes:
"Tokens=1* NOT "Token=1*
and
SET "_DateMatched=1" NOT SET "_DateMatched="
To do this split out the file name from the results of find string using a loop and then get the date from that to use in the echo.
Script
#(
SETLOCAL
ECHO OFF
SET "_ResultFile=_Result.searchresult"
SET "_DateMatched="
)
DEL /F /Q "%_ResultFile%"
SET /P "_SearchTerm=Enter search term: "
ECHO Searching for %_SearchTerm% >"%_ResultFile%"
ECHO --------------------------------------------- >>"%_ResultFile%"
FOR /F "Tokens=1* delims=:" %%A IN ('
FINDSTR /S /C:"%_SearchTerm%" *.txt
') DO (
For %%a in ( %%~tA ) Do (
IF NOT DEFINED _DateMatched (
ECHO.%%a %%A:%%B>>"%_ResultFile%"
SET "_DateMatched=1"
)
)
SET "_DateMatched="
)
Results
Searching for ggg
---------------------------------------------
08/13/2019 test.txt:ggg
08/13/2019 test2.txt:ggg
In addition, Yes you could use a FOR /F instead it was a 50/50 coin flip II didd to use the defined/not defined with a FOR loop instead of a FOR /F loop.
However the other answer provided chose this path, but for some reason used my code as a base and added in a 3rd loop so that their middle loop is redundant.
I'll post that alternative here as well as I had thought about posting both ways, it's six one way half a dozen the other. (but I won't waste your time with an extra loop, I am still wrapping my brain around them adding that!)
Alternative version
#(
SETLOCAL
ECHO OFF
SET "_ResultFile=_Result.searchresult"
)
DEL /F /Q "%_ResultFile%"
SET /P "_SearchTerm=Enter search term: "
ECHO Searching for %_SearchTerm% >"%_ResultFile%"
ECHO --------------------------------------------- >>"%_ResultFile%"
FOR /F "Tokens=1* delims=:" %%A IN ('
FINDSTR /S /C:"%_SearchTerm%" *.txt
') DO (
For /F %%a in ("%%~tA") Do (
ECHO.%%a %%A:%%B>>"%_ResultFile%"
)
)
Although there really isn't a functional difference between each method, I waffle on whether the 1st or 2nd is more aesthetically pleasing, so it's a coin flip to me on which to write first or whether to write both at all.
That said trying to create a rational for some of the odd comments I received on poking another for posting my code with minor essentially non functional changes, but the core of the code is still the stuff I wrote, and just chose to show the FOR /F method, but then did so in a way that makes an unnecessary 3rd loop.
I think there are some minor benefits to the FOR /F, it saves at about 50 characters of code, and while in this case it's really 50/50, there are scenarios where I would definitely choose it as being a bit faster as the next step, although in this case we only have 3 iterations on a match, so I suspect for the current scenario we'd need to have 10s of thousands of matches before we might noticeably affect the outcome.
That said, by adding a 3rd unneeded loop as in the other comment, I suspect any speed benefit was nullified, so.. I'm not sure, at least in the above code I provided there isn't the unneeded loop, so if there is a speed benefit concert try use that instead. :)
To not insert the file time, you'll need another for to strip it.
:: Q:\Test\2019\08\13\SO_57475301.cmd
#ECHO OFF
SET /P "_searchterm=Enter search term: "
(ECHO Searching for %_searchterm%
ECHO ---------------------------------------------
FOR /F "tokens=1* delims=:" %%A IN ('
FINDSTR /C:"%_searchterm%" *.txt
') DO For %%a in ("%%A") Do For /F %%D in ("%%~ta") do ECHO:%%D %%A:%%B
) >"_Result.searchresult"
Sample output:
> SO_57475301.cmd
Enter search term: log
> type _Result.searchresult
Searching for log
---------------------------------------------
08/13/2019 Reboot-79_Logfile_001.txt:08:29:05.586 loginfo
08/13/2019 Reboot-79_Logfile_001.txt:08:30:05.586 loginfo
To have the file name first reorder ECHO:%%A:%%D %%B
Here is a way to get what you want.
=== grepplus.ps1
Get-ChildItem -File -Path 'C:/src/t' -Filter '*.ps1' |
Select-String '^S' |
ForEach-Object {
'{0} {1}:{2}' -f (
(((Get-ChildItem $_.Path).LastWriteTime).ToString('yyyy/MM/dd') -replace '-','/'),
$_.Filename,
$_.Line
)
}
=== grepplus.bat
#ECHO OFF
powershell -NoLogo -NoProfile -Command ^
"Get-ChildItem -File -Path 'C:/src/t' -Filter '*.ps1' |" ^
"Select-String '^S' |" ^
"ForEach-Object {" ^
"'{0} {1}:{2}' -f (" ^
"(((Get-ChildItem $_.Path).LastWriteTime).ToString('yyyy/MM/dd') -replace '-','/')," ^
"$_.Filename," ^
"$_.Line" ^
")" ^
"}"
Here is my comment converted to an answer since I hope it might be useful…
Instead of a simple findstr /S command line you could try something with a for loop that iterates over the files. To get the file date/time stamps (last modification) and the pure file names you could make use of ~-modifiers like ~t and ~nx, respectively. To capture the output of findstr use a for /F loop; then you will be able to concatenate it with other strings.
Something like this could work for you (I hope):
(
for %%I in ("*.txt") do (
for /F "delims=" %%S in ('findstr /C:"%searchterm%" "%%~I"') do (
for /F %%T in ("%%~tI") do (
echo %%T:%%~nxI:%%S
)
)
)
) >> "_Result.searchresult"
The second for /F loop tries to split off the pure date from the date/time value returned by the %%~tI portion, but regard that this value is locale-dependent!
The above approach does not recurse sub-directories to find matching files. Though if you really want to do so, replace the line for %%I in ("*.txt") do ( by for /R %%I in ("*.txt") do (.
I found an answer to find all empty directories. I want to do the opposite to find and list non empty directories in a folder. The folders just go one level deep.
#echo off
for /d /r %1 %%A in (.) do (
dir /a /b "%%~fA" 2>nul | findstr "^" >nul || echo %%~fA
)
How to modify the above line to find non empty folders ?
In your particular situation, instead of using Dir you could use Where.exe, (Vista+), which has a handy /Q option, (enter Where /? at the Command Prompt for usage information).
From cmd.exe:
For /D %A In (*) Do #Where/Q "%A":*>Nul&&Echo %A
From a batch file:
#For /D %%A In (*) Do #Where/Q "%%A":*>Nul&&Echo %%A
Conversely with a small change you can report those without files:
From cmd.exe:
For /D %A In (*) Do #Where/Q "%A":*>Nul||Echo %A
From a batch file:
#For /D %%A In (*) Do #Where/Q "%%A":*>Nul||Echo %%A
In the above examples, if the string between the parentheses contains a path, e.g. MyFolder\*, "C:\Some Location\* or ..\RelPath\* you can optionally use Echo %~nxA / Echo %%~nxA if you prefer the output to be just the directory name.
Simply identify the directories with a non-zero Count. Using the -Directory switch will require PowerShell 3.0 or higher.
#ECHO OFF
powershell -NoProfile -Command ^
"(Get-ChildItem -Directory |" ^
"ForEach-Object { if ((Get-ChildItem $_).Count -ne 0) { $_.Name } })"
Usage:
C:>CALL get-nonemptydirs.bat
d2
junk
others
t t t
xxx
Here is a simple PowerShell script that will find all directories (within a directory) that have contents within them:
((Get-ChildItem -Directory -Path "C:\PATH-TO-FOLDER" | Where {$_.PSIsContainer -eq $True}) | Where {$_.GetFiles().Count -ne 0} | select FullName).FullName
If you want to write the results to a file, just append the following to the end:
| out-file -FilePath "C:\PATH-TO-OUTFILE\FILE.txt"
Full script:
((Get-ChildItem -Directory -Path "C:\PATH-TO-FOLDER" | Where {$_.PSIsContainer -eq $True}) | Where {$_.GetFiles().Count -ne 0} | select FullName).FullName | Out-File -FilePath "C:\PATH-TO-OUTFILE\FILE.txt"
my workdir looks like:
C:\Workdir\Dir1
C:\Workdir\Dir2
C:\Workdir\Dir3
i am starting a batchfile with this loop:
for /D %%a in (*) do (
and then my actual command progresses each directory one after another.
but i need to start the progressing reversed. by starting with the last directory and then working upwards. starting with Dir3 and then Dir2 and then Dir1
how do i do that?
You should loop using "for /f" and then list all folders in the reverse order via the "dir" command.
#echo off
for /f "tokens=*" %%G in ('dir /s /b /A:D /O:-N "%CD%"') do (
echo %%G
)
Where:
%CD% - is current directory - same if you call "in (*)" from the same directory
/O:-N is reverse order by name
https://ss64.com/nt/dir.html
https://ss64.com/nt/for_d.html
As you have indicated a reversed Windows shell natural sort order you need to be made aware that numbers beyond single digit will cause you issues without additional work.
It would order like this:
Dir9
Dir8
Dir7
Dir6
Dir5
Dir4
Dir3
Dir2
Dir11
Dir10
Dir1
To fix this craziness, my suggested additional work utilises powershell:
#Echo Off
For /F "Delims=" %%A In ('
PowerShell -C "Get-ChildItem | ?{ $_.PSIsContainer } | Select-Object -Expand Name | Sort-Object { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) } -Descending"
') Do Echo %%A
Pause
I have 12 Files in a folder each names a_01.jpg a_02.jpg (...) etc
I want to copy those files in the same folder using a new name like e.g. daniela_01.jpg etc
So I tried using a batchfile with:
copy C:\Users\name\temp\a_*.jpg C:\Users\name\temp\daniela_*.jpg
this didn't work out as well as
copy C:\Users\name\temp\a_??.jpg C:\Users\name\temp\daniela_??.jpg
I dont get the 12 Files... It only copys one single piece...
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "oldpfx=a_"
SET "newpfx=daniela_"
SET "delimchar=_"
FOR /f "tokens=1*delims=%delimchar%" %%a IN (
'dir /b /a-d "%sourcedir%\%oldpfx%*.jpg" '
) DO (
ECHO COPY "%sourcedir%\%%a%delimchar%%%b" "%sourcedir%\%newpfx%%%b"
)
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
You'd also need to specify your old and new prefixes. It would be possible to calculate delimchar by using
set "delimchar=%oldpfx:~-1%"
but the delimiter can be only one character, not a string.
Note that since your old prefix is the same as the end of the new, re-running the procedure would attempt to do the copy again and would try to copy daniela_*.jpg to daniela_daniela_*.jpg.
Best to tell us the entire problem from the get-go.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "namesfile=U:\q25053916.txt"
SET "oldpfx=a"
SET "delimchar=_"
FOR /f "tokens=1*delims=%delimchar%" %%a IN (
'dir /b /a-d "%sourcedir%\%oldpfx%%delimchar%*.jpg" '
) DO (
FOR /f "delims=" %%p IN ('type "%namesfile%"') DO (
ECHO COPY "%sourcedir%\%%a%delimchar%%%b" "%destdir%\%%p%delimchar%%%b"
)
)
GOTO :EOF
By preference, make the source and destination different. That way, the process can be re-run easily.
All you need to do above changing the variables is to set up your names in a simple textfile (I used a file named q25053916.txt containing your data for my testing.)
q25053916.txt
daniela
maximilian
Fabian
etc
let say your files are on the c:\temp dir :
ls c:\temp\*.jpg | %{ copy $_.fullname c:\temp\daniel$($_.name)}
update after your comment (remove the 1st letter)
ls c:\temp\*.jpg | %{copy $_.fullname c:\temp\daniel$(($_.name).substring(1))}
in batch you could do :
copy *.jpg daniel*.jpg
I'd do it like this:
Get-Item 'C:\source\folder\a_*.jpg' | % {
$target = Join-Path 'C:\target\folder' ($_.Name -replace 'a_','daniela_')
Copy-Item $_ $target
}
I would use a regular expression:
ls C:\Users\name\temp\a_*.jpg | % {
if ($_ -match 'a_(.*?).jpg') {
Copy-Item $_ ('C:\Users\name\temp\daniela_' + $Matches[1] + '.jpg')
}
}