batch pick random file that contains utf-8 characters in its name - batch-file

I want to open a random file in a directory and its subdirectorys with batch. And I know there are enough questions on stackoverflow who give the code for that but none of which I found were with utf-8 character support.
I use the following code which I found in stackoverflow.
#echo off
setlocal
:: Create numbered list of files in a temporary file
set "tempFile=%temp%\%~nx0_fileList_%time::=.%.txt"
dir /b /s /a-d %1 | findstr /n "^" >"%tempFile%"
:: Count the files
for /f %%N in ('type "%tempFile%" ^| find /c /v ""') do set cnt=%%N
call :openRandomFile
:: Delete the temp file
del "%tempFile%"
exit /b
:openRandomFile
set /a "randomNum=(%random% %% cnt) + 1"
for /f "tokens=1* delims=:" %%A in (
'findstr "^%randomNum%:" "%tempFile%"'
) do start "" "%%B"
cmd /k
It works fine that way, until it picks a file like "blablabla_空色デイズ.mp3", in that case it gives an error like "file blablabla_?????.mp3 could not be found." and I have dozens of these files.
I have tried using chcp 65001 on the start of the file for using utf-8 and if I did so, the teporary created .txt list shows the correct names of japanese files, but the pick up itself does not work anymore after that, so I took away #echo off and cmd prints an error on set /a "randomNum=(%random% %% cnt) + 1": Error: division by zero.
And at this point, I dont understand anymore whats going on, because the file is working great without chcp 65001.
I don't know batch, please, does anyone have an idea how to make it run?
I would be really happy!

You will have a lot of problems with findstr and command output when utf8/unicode characters are involved. In this type of scenarios it is safer (but slower) to avoid them
#echo off
setlocal enableextensions disabledelayedexpansion
rem %1 = folder where to start to list files
rem if empty, current active directory is used
rem %2 = file mask
rem if empty * is used
rem What to search
set "fileMask=%~2" & if not defined fileMask set "fileMask=*"
rem Retrieve the number of matching files
set "nFiles=0" & for /r "%~f1." %%a in ("%fileMask%") do set /a "nFiles+=1"
if %nFiles% lss 1 exit /b
rem Select a random file
set /a "n=(%random% %% nFiles) + 1"
echo Selected file = %n% / %nFiles%
rem Count up to the selected file and start it
2>nul (
for /r "%~f1." %%a in ("%fileMask%") do (
set /a "n-=1", "1/n" || ( start "" "%%~fa" & goto :done )
)
)
:done
That is, count the number of files, select one of them and then start iterating over the list of files decrementing the number of the selected file. When it is 0 (we have reached the selected file), the 1/n division will fail and the conditional execution operator will execute the start command.

I'd try
) do start "" "%%~sB"
in the :openRandomFile procedure, which should open the file using its short name.
Sorry - can't test it as I don't have any UTF-8 filenames (that I know about)

Related

Batch File - When copying files sometimes it shows The system cannot find the path specified for all the file or for majority of file

i am making a file selector which would randomly copy files from one folder to another code works quite fine but sometimes it shows The system cannot find the path specified for all or majority of files i don't know what went wrong can please someone help
my code
#echo off
setlocal enabledelayedexpansion
set num=0
cls
set /p input= enter the number of files you want:
set /p address= enter the address of your files:
md SelectedFiles
pushd "%address%" || goto :EOF
set /a num=%num%+1
for /f "tokens=1,* delims=[]" %%i in ('dir /b /s /a-d ^| findstr /RV "[.]jpg [.]png" ^| find /v /n ""') do (
set "file%%i=%%~j"
set "cnt=%%i"
)
for /l %%c in (1,1,%input%) do (
set /a rand=!random! %% !cnt!
for %%r in (!rand!) do copy "!file%%r!" "%address%\SelectedFiles" | clip
)
echo your files have been copied
pause
popd
Try, as a replacement for your for /l loop
for /l %%c in (1,1,%input%) do (
set /a rand=1 + !random! %% !cnt!
for %%r in (!rand!) do (
copy "!file%%r!" "%address%\SelectedFiles" | clip
for %%s in (!cnt!) do set "file%%r=!file%%s!"
set /a cnt-=1
)
)
Your filenames are currently being assigned to file1..file!cnt!.
You are then generating rand as 0..cnt-1, so there is a probability that you will choose file0 which does not exist and no possibility of choosing file!cnt!
There is also a possibility of re-choosing a file.
You should make sure that input is not greater than cnt.
My suggested code simply makes the range 1..cnt, then when a file has been processed, moves the very last filename (file!cnt!) over the chosen name and reduces cnt since there is one fewer filename in the list.
NOTE: Since the |clip is piping from a copy statement for one file only, it should only ever generate 1 file(s) copied. on the clipboard

Remove last 4 characters of a file in a folder with a batch file

this should be pretty simple, i just want a bat file that when placed in a folder will rename all files within the folder and delete the final 4 characters, but not the extension
ex.
img (1).jpg
becomes
img.jpg
ive tried
#echo off
setlocal enabledelayedexpansion
if not exist %1 goto :eof
for /f %%A in ('find /V /C "" ^<%1') do set lines=%%A
for /f "tokens=1,* delims=[]" %%A in ('find /V /N "" ^<%1') do (
if %%A LSS %lines% (
echo %%B
) else (
set rec=%%B
echo !rec:~0,-4!
)
)
but it doesnt work. theres no error, it runs, it just doesnt do anything
I modified your code as follows:
1/ switched to a test directory (PUSHD testdirectoryname) and back (POPD)
2/ changed the %1 to a fixed filename
3/ Inserted some debug lines
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following setting for the source directory includes spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
PUSHD "%sourcedir%"
if not exist q71992787.txt goto :eof
TYPE "%sourcedir%\q71992787.txt"
for /f %%A in ('find /V /C "" ^<q71992787.txt') do set lines=%%A
SET li
find /V /N "" <q71992787.txt
for /f "tokens=1,* delims=[]" %%A in ('find /V /N "" ^<q71992787.txt') do (
if %%A LSS %lines% (
echo %%B
) else (
set rec=%%B
echo !rec:~0,-4!
)
)
POPD
GOTO :EOF
The result, other than the debug lines, was a list of the filenames in the file
q71992787.txt, other than the very last filename, which was shortened by 4 characters.
This differs from your result "it just doesnt do anything". This is why I mention it.
So - to your solution:
I can see no reason for calculating the number of lines in the file. If there are 10 lines for example, then reading the same file again with 'find /v /n` will still find 10 lines.
The if %%A LSS %lines% will echo %%B (the filename) if %%A (the line number) is LESS (LSS) than 10; that is, from 1 to 9 - just show the filename.
The else clause will only be processed if %%A is 10 or greater, so only on the last line.
I'm sure this wasn't what you intended to do.
I'd advise you to use set "var1=value" for setting STRING values - this avoids problems caused by trailing spaces. Quotes are not needed for setting arithmetic values (set /a`)
Where you alter your filename, setting rec to %%B means the entire filename, %%B. You don't show us a sample from the file %1, so I'll presume it's img (1).jpg as your narrative mentions. rec will thus become img (1).jpg and then be shortened to img (1) by removing the last 4 characters of the string.
Since you seem to want to remove the last 4 characters of the name part and leave the extension, then you need to read the documentation for for (for /? from the prompt) which will tell you
set "rec=%%~nB"
will assign just the name part and you could then
set "rec=!rec:~0,-4!%%~xB"
echo !rec:~0,-4!%%~xB
to set or show the manipulated name.
As to why you get no result at all; This could be because the file encoding of %1 is unicode or is *nix format (, not line endings)
And I do hope you're not using a *nix emulator like cygwin where find becomes a file-locator, not a string-analyser.
The file renaming task for all *.jpg and *.jpeg files in the current directory of which file name ends with a space and a single digit number in round brackets can be done with a batch file with following command lines:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir "* (?).jpg" "* (?).jpeg" /A-D /B /ON 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R /C:" ([0123456789])\.jpe*g$"') do (
set "FileName=%%~nI"
setlocal EnableDelayedExpansion
ren "!FileName!%%~xI" "!FileName:~0,-4!%%~xI"
endlocal
)
endlocal
That code works also for image file names containing one or more exclamation marks and one or more round brackets before the last four characters in file name to remove.
Note: The command REN outputs an error message if the current directory contains the two files photo (1).jpg and photo (2).jpg on renaming the second file to photo.jpg because of this is not possible after first file already renamed to photo.jpg. There cannot be two files with same name in same directory.
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.
dir /?
echo /?
endlocal /?
findstr /?
ren /?
set /?
setlocal /?
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul and |. The redirection operators > and | must be escaped with caret character ^ on FOR command line to be interpreted as literal characters when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line with using a separate command process started in background.
Doing it in PowerShell would be much easier
ls *.jpg |% { ren $_ -ne ($_.BaseName.Substring(0, $_.BaseName.Length - 4) + $_.Extension) -wi }
-wi or -WhatIf is the option for dry running. After checking that the new names are OK you can remove that option. Full command line without alias:
Get-ChildItem -File *.* | ForEach-Object { Rename-Item -WhatIf -Path $_ `
-NewName ($_.BaseName.Substring(0, $_.BaseName.Length - 4) + $_.Extension) }

Counting files in a folder in a batch file doesn't work as expected

I'm having trouble when counting files in a specific folder in a batch file.
My folder at C:\logs contains seven different log files.
When I run the batch file with the following content, I'm always getting "1" as the value for COUNT:
SETLOCAL ENABLEDELAYEDEXPANSION
SET LOGS_LOCAL=C:\logs
SET COUNT=0
REM Count logs
for %%A in ("%LOGS_LOCAL%") do set /a COUNT+=1
echo !COUNT!
pause
As you can see I used the snippet of an answer from this question but it still doesn't work.
What am I doing wrong? Do I need to change any other settings?
just to show you a different way:
for /f %%a in ('dir /b /a-d ^|find /c /v ""') do set count=%%a
echo %count%
dir parameters:
/b use simple format (names only, no summary, no header)
/a-d exclude folders (show files only)
find /c /v "" then just counts the entries (/c = count, /v "" = every non-empty line)
I experimented a bit and it seems that one must specify the wanted files exactly. I changed the path of my log folder from
SET LOGS_LOCAL=C:\Backups\weekly\logs
to
SET LOGS_LOCAL=C:\Backups\weekly\logs\*.log
Now, my batch file returns "7" for COUNT.
You're SETting 1 single directory, C:\logs, as your variable, LOGS_LOCAL, so the COUNT is working correctly with its output result of 1.
What I think you intended to do count files within that directory like this:
#ECHO OFF
SET "LOGS_LOCAL=C:\logs"
SET "COUNT=0"
REM Count logs
FOR %%A IN ("%LOGS_LOCAL%\*.log") DO SET/A COUNT+=1
ECHO %COUNT%
PAUSE

batch for loop in file

I have a Batch script :
#echo off
setlocal enableDelayedExpansion
SET /P UserInput=Please Enter a Number:
SET /A number=UserInput
ECHO number=%number%
for %%i in (*.jpeg) do call :JPG %%~ni %%i
goto :end
:JPG
set str=%1
set /a str2=%str:_color=%
set /a newnamej=%str2%+%number%
echo %1 ==> I can see the problem with it
set lastnamej=%newnamej%_color.jpeg
ren %2 %lastnamej%
goto :eof
:end
The goal of this script is to take all file in a folder. They are all named after a number (1_color.jpeg, 2_color.jpeg, 3_color.jpeg,..) and I want to rename them with an additionnal number (if user input is 5, 1_color.jpeg will become 6_color.jpeg, and so on).
I have a problem with this script.
if I use a number such as 555, the first file will pass in the for loop 2 times.
(little example : 1_color.jpeg and 2_color.jpeg,
I use my script with 5 so 1_color.jpeg => 6_color.jpeg and 2_color.jpeg => 7_color.jpeg but then, 6_color.jpeg will be read again once, and will become 11_color.jpeg, so my result will be 11_color.jpeg and 7_color.jpeg).
Do someone know how to fix this issue?
Thanks for all!
The problem have two parts: the for %%i in (*.jpeg) ... command may be dinamically affected by the position that a renamed file will occupy in the whole file list, so some files may be renamed twice and, in certain particular cases with many files, up to three times.
The solution is to use a for /F %%i in ('dir /B *.jpeg') ... command instead, that first get the list of all files, and then start the renaming process.
Also, the rename must be done from last file to first one order, to avoid duplicate numbers.
However, in this case the use of for /F combined with "tokens=1* delims=_" option also allows to process the first underscore-separated number in the file names in a simpler way:
#echo off
setlocal EnableDelayedExpansion
SET /P number=Please Enter a Number:
ECHO number=%number%
for /F "tokens=1* delims=_" %%a in ('dir /O:-N /B *.jpeg') do (
set /A newNum=%%a+number
ren "%%a_%%b" "!newNum!_%%b"
)
User Aacini provided a nice solution in his answer, pointing out both issues at hand, namely the fact that for does not fully enumerate the directory in advance (see this thread: At which point does for or for /R enumerate the directory (tree)?) and the flaw in the logic concerning the sort order of the processed files.
However, there is still a problem derived from the purely (reverse-)alphabetic sort order of dir /B /O:-N *.jpeg, which can still cause collisions, as the following example illustrates:
9_color.jpeg
8_color.jpeg
7_color.jpeg
6_color.jpeg
5_color.jpeg
4_color.jpeg
3_color.jpeg
2_color.jpeg
10_color.jpeg
1_color.jpeg
So if the entered number was 1, file 9_color.jpeg is tried to be renamed to 10_color.jpeg, which fails because that file already exists as it has not yet been processed (hence renamed to 11_color.jpeg).
To overcome this problem, you need to correctly sort the items in reverse alpha-numeric order. This can be achieved by left-zero-padding the numbers before sorting them, because then, alphabetic and alpha-numeric sort orders match. Here is a possible implementation:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_LOCATION=." & rem // (directory containing the files to rename)
set "_PATTERN=*_*.jpeg" & rem // (search pattern for the files to rename)
set "_REGEX1=^[0-9][0-9]*_[^_].*\.jpeg$" & rem // (`findstr` filter expression)
set "_TEMPFILE=%TEMP%\%~n0_%RANDOM%.tmp" & rem // (path to temporary file)
rem // Retrieve numeric user input:
set "NUMBER="
set /P NUMBER="Please Enter a number: "
set /A "NUMBER+=0"
if %NUMBER% GTR 0 (set "ORDER=/R") else if %NUMBER% LSS 0 (set "ORDER=") else exit /B
rem /* Write `|`-separated list of left-zero-padded file prefixes, original and new
rem file names into temporary file: */
> "%_TEMPFILE%" (
for /F "tokens=1* delims=_" %%E in ('
dir /B "%_LOCATION%\%_PATTERN%" ^| findstr /I /R /C:"%_REGEX1%"
') do (
set "NAME=%%F"
setlocal EnableDelayedExpansion
set "PADDED=0000000000%%E"
set /A "NUMBER+=%%E"
echo !PADDED:~-10!^|%%E_!NAME!^|!NUMBER!_!NAME!
endlocal
)
)
rem /* Read `|`-separated list from temporary file, sort it by the left-zero-padded
rem prefixes, extract original and new file names and perform actual renaming: */
< "%_TEMPFILE%" (
for /F "tokens=2* delims=|" %%K in ('sort %ORDER%') do (
ECHO ren "%%K" "%%L"
)
)
rem // Clean up temporary file:
del "%_TEMPFILE%"
endlocal
exit /B
After having successfully verified the correct output of the script, to not forget to remove the upper-case ECHO command in front of the ren command line.
The script uses a temporary file that receives a |-separated table with the padded numeric prefix in the first, the original file name in the second and the new file name in the third column, like this:
0000000010|10_color.jpeg|11_color.jpeg
0000000001|1_color.jpeg|2_color.jpeg
0000000002|2_color.jpeg|3_color.jpeg
0000000003|3_color.jpeg|4_color.jpeg
0000000004|4_color.jpeg|5_color.jpeg
0000000005|5_color.jpeg|6_color.jpeg
0000000006|6_color.jpeg|7_color.jpeg
0000000007|7_color.jpeg|8_color.jpeg
0000000008|8_color.jpeg|9_color.jpeg
0000000009|9_color.jpeg|10_color.jpeg
The temporary file is read and sorted by the sort command. The strings from the second and third columns are extracted and passed over to the ren command.

Read the first word on a specific line from a text file using batch script then feed into an if statement

Sorry I know similar things have been asked here but basically I'm trying to read a text file within a batch script and evaluate what has been written to the file.
The job is a print job that sends a file to a printer, I have it echoing the output from the command to a log file. I then want to read in what the output was and if there was an error I will then send an email so we know when things stop working.
It always appends to the end of the file so I know if there's an error the 4th from last line will begin with "Error:". So my question is how can I read that in to a variable so I can perform an IF statement. I've got the emailing part sorted it's just reading from the file that I'm struggling with.
Any help would be much appreciated. Here's an example of the content of the file when there's an error:
----
C:\XG1\DGS01\prints\000000398200001.XG1
19/03/2013
15:02
1 file(s) copied.
Error: print server unreachable or specified printer does not exist.
1 file(s) moved.
It leaves one blank line at the end of the file so I'm going with the last line minus 4.
Thank you
as a line in your batch file:
for /f "tokens=1*delims=:" %%i in (thenameofyourfile) do if "%%i"=="Error" set message=%%j
echo message was "%message%"
Actually, that will report if ANY lines are in the format you describe.
#ECHO OFF
SETLOCAL
(SET message=)
FOR /f "tokens=1,2*delims=[]:" %%i IN (
' TYPE thenameofyourfile^|find /n /v "" '
) DO (
SET lastline=%%i
IF "%%j"=="Error" SET errorline=%%i&SET message=%%k
)
SET /a target=%errorline% + 3
IF %target% neq %lastline% (SET message=)
IF DEFINED message ECHO error found %message%
should get the line ONLY if it's the fourth last line in the file - the "+ 3" being the line-count required (well, minus 1)
BUT - remember that if this is, as it seems, a log file that it's possible (I'd imagine) that further entries may appear AFTER the error (for further jobs) so the target for the Line beginning "Error:" may not be the fourth-last...
OTOH, using the line(s) I first posted, once an "Error:..." line appears, it will be detected EVERY time - you'd need to reset the logfile in your mail-send procedure (save existing & recreate empty?)
Solution without any loop (does test if any error):
#findstr Error: printer.log >nul 2>&1
#if %errorlevel% equ 0 echo Send email now!
And this code does only test the fourth line before the last line for an error:
#echo off &setlocal enabledelayedexpansion
for /f %%i in ('findstr /n "^" printer.log') do (
set line4=!line3!&set line3=!line2!&set line2=!line1!&set line1=%%i)
echo %line4%|findstr Error: >nul 2>&1
if %errorlevel% equ 0 echo Send email now!
If you want to start reading the fourth line from the bottom of your log file, you can count the number of lines in your log file, subtract 4, then more +%count% to get the tail of the log.
#echo off
setlocal
set logfile=printerlog.log
for /f "tokens=2 delims=:" %%I in ('find /c /v "" "%logfile%"') do set lines=%%I
set /a "tail4=%lines% - 4"
for /f %%I in ('more +%tail4% "%logfile%" ^| find /i "Error:"') do (
rem do your email voodoo here
)
If your log file will exceed 65,535 lines, I recommend looping through the file using JScript or VBScript instead of a batch loop. Skipping that many lines with more will cause more to crash, so you'd have to loop line-by-line and increment a counter. However, batch for loops are terribly slow.
Here's the same script that replaces more with JScript-ish file reading.
#if (#a==#b) #end /*
:: batch portion
#echo off
setlocal
set logfile=printerlog.log
for /f "tokens=2 delims=:" %%I in ('find /c /v "" "%logfile%"') do set lines=%%I
set /a "tail4=%lines% - 4"
for /f "delims=" %%I in ('cscript /nologo /e:jscript "%~f0" %tail4% "%logfile%" ^| find /i "Error:"') do (
rem do your email voodoo here
rem %%I contains the matching line.
rem After one match, stop processing further
goto :EOF
)
goto :EOF
::JScript portion */
var i=0, fso = new ActiveXObject("Scripting.FileSystemObject").OpenTextFile(WSH.Arguments(1),1);
while (!fso.AtEndOfStream) {
if (i++ < WSH.Arguments(0)) fso.SkipLine();
else WSH.Echo(fso.ReadLine());
}
fso.Close();
I believe the fastest and least resource-intensive way to check for errors is with GNU tail. If you can, get the .zip binaries and put tail.exe in your path or where your batch script can access it. Then:
#echo off
setlocal
tail -n 4 printerlog.log | find /i "Error:" >NUL && (
echo Error found. Sending email.
rem do email stuff
)
Or if you wish to capture the text of the error for your email:
#echo off
setlocal
for /f "delims=" %%I in ('tail -n 4 printerlog.log ^| find /i "Error:"') do (
echo Error found: %%I
rem do email stuff
goto :EOF
)
tail is much more efficient than all these other methods of counting the number of lines in the log file and looping through the log file line-by-line, whether looping in batch or in JScript.

Resources