如何在 Windows 批处理文件中捕获未找到文件消息
How to catch a File Not Found message within a Windows batch file
我在一个名为 upload_filenames.txt
的文件中有大约 600 个文件名的列表,我想找出它们在树中的位置,该树在 9K 个子目录中有大约 .7M 个文件。
这个(来自 this question)完成了工作:
for /F "usebackq delims=" %%i in (upload_filenames.txt) do (
for /F "delims=" %%b in ('dir /B /S /A:-D "%%i"') do (
echo %%~nxb;"%%~fb" >> exists.txt
)
)
现在,在同一个循环中,我还想用找到的所有 not 文件填充第二个文件。 (我可以从两个列表中手动获取它,但我更喜欢自动方式。)
到目前为止,我了解到 FOR
循环和 if exist
return errorlevel 0
成功时,但只有 'File Not Found' 并且没有错误级别不是。所以我不能使用那些。那么有什么办法可以批量处理吗?
旁白:我不关心效率。上面的脚本大约需要 10 个小时才能完成。就这样吧-暂时。
Windows 10 或服务器 2008
如果我得到你想要的正确,那么这应该可以解决问题。 未经测试!以后只能测试这个:
@echo off
for /F "usebackq delims=" %%a in ("upload_filenames.txt") do (
for /f "delims=" %%i in ('dir /b /s /a:d') do (
pushd "%%~i"
dir /b /a-d | findstr /i "%%~a">nul && echo %%~a;"%%~i%%~a">>exists.txt || echo %%~a not found in "%%~i">>not_exist.txt
popd
)
)
或者如果您不想使用条件运算符:
@echo off
for /F "usebackq delims=" %%a in ("upload_filenames.txt") do (
for /f "delims=" %%i in ('dir /b /s /a:d') do (
pushd "%%~i"
dir /b /a-d | findstr /i "%%~a">nul
if not errorlevel 1 (
echo %%~a;"%%~i\%%~a">> exists.txt
) else (
echo %%~a not found in "%%~i">>not_exist.txt
)
popd
)
)
所以思路是一个一个取文件名,然后dir
递归找到每个目录,pushd
到目录,然后做一个dir: of the file
一次重定向整个循环比逐行编写要快得多。此外,重复的 dir
需要大量 time.Do 和 dir
一次(进入文件)并使用该结果。 findstr
非常有效,所以我想在 for
循环中进行后处理而不是 if
会更快。
@echo off
setlocal
dir /b /s /a-d * > "files.txt"
(for /F "usebackq delims=" %%i in ("upload_filenames.txt") do (
for /f "delims=" %%b in ('findstr /iec:"\%%i" "files.txt" ^|^| echo ~') do (
echo %%i;%%b
)
)) > "result.txt"
findstr /ev ";~" "result.txt" > "existing.txt"
findstr /e ";~" "result.txt" > "missing.txt"
rem del "result.txt"
关键行是findstr /iec:"\%%i" files.txt || echo ~
,以文件名结尾的行输出,如果找不到文件,findstr
什么都不输出。在那种情况下(||
充当“如果先前的命令失败则”(source)),echo
命令将执行并输出 ~
(更改为您想要的任何字符串,但它必须是 anything,因为 for
循环会跳过空行)
和其他人一样,我会使用 findstr
,因为它比使用嵌套 for
循环快得多。但是,我会扭转局面,让实际存在的文件列表成为搜索字符串,并使用它们来搜索输入列表文件 upload_filenames.txt
。虽然我无法绕过一个 for
循环来从文件路径中导出纯文件名。无论如何,这是代码:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=." & rem // (path to target root directory)
set "_MASK=*.*" & rem // (file pattern, usually `*.*` for all)
set "_LIST=%~dp0upload_filenames.txt" & rem // (path to file containing name list)
set "_PASS=%~dp0found.txt" & rem // (path to positive result file)
set "_FAIL=%~dp0missing.txt" & rem // (path to negative result file)
set "_FULL=%~dpn0_all.tmp" & rem // (path to a temporary file)
set "_NAME=%~dpn0_names.tmp" & rem // (path to another temporary file)
rem // Put list of full paths of all files in the target directory tree to a file:
dir /S /B /A:-D "%_ROOT%\%_MASK%" > "%_FULL%"
rem // Reduce list of paths by maintaining the only pure file names:
> "%_NAME%" (
for /F "usebackq delims= eol=|" %%L in ("%_FULL%") do (
echo(%%~nxL
)
)
rem /* Let `findstr` twice do the search, using the file names from the target
rem directory tree as search strings against the original list file: */
findstr /I /X /L /G:"%_NAME%" "%_LIST%" > "%_PASS%"
findstr /I /X /V /L /G:"%_NAME%" "%_LIST%" > "%_FAIL%"
rem // Clean up temporary files:
del "%_FULL%" "%_NAME%"
endlocal
exit /B
这里有一个稍微不同的方法,它 returns 现有文件的完整路径,当文件名可能在给定目录树中多次出现时,这变得很重要:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=." & rem // (path to target root directory)
set "_MASK=*.*" & rem // (file pattern, usually `*.*` for all)
set "_LIST=%~dp0upload_filenames.txt" & rem // (path to file containing name list)
set "_PASS=%~dp0found.txt" & rem // (path to positive result file)
set "_FAIL=%~dp0missing.txt" & rem // (path to negative result file)
set "_AUGM=%_LIST%.tmp" & rem // (path to temporary list file)
set "_FULL=%~dpn0_all.tmp" & rem // (path to a temporary file)
set "_NAME=%~dpn0_names.tmp" & rem // (path to another temporary file)
rem // Create augmented copy of list file with each line preceded by `\`:
> "%_AUGM%" (
for /F "usebackq delims= eol=|" %%L in ("%_LIST%") do (
echo(\%%~L
)
)
rem // Put list of full paths of all files in the target directory tree to a file:
dir /S /B /A:-D "%_ROOT%\%_MASK%" > "%_FULL%"
rem // Reduce list of paths by maintaining the only pure file names:
> "%_NAME%" (
for /F "usebackq delims= eol=|" %%L in ("%_FULL%") do (
echo(%%~nxL
)
)
rem /* Let `findstr` do a search, using the augmented list file against the file
rem containing the list of full paths in order to eventually get full paths,
rem which is particularly important if file names are not unique in the tree: */
findstr /I /E /L /G:"%_AUGM%" "%_FULL%" > "%_PASS%"
rem /* Let `findstr` do another search, using the file names from the target
rem directory tree as search strings against the original list file this time: */
findstr /I /X /V /L /G:"%_NAME%" "%_LIST%" > "%_FAIL%"
rem // Clean up temporary files:
del "%_AUGM%" "%_FULL%" "%_NAME%"
endlocal
exit /B
N。 B.: 幸运的是 nasty flaw of findstr
with multiple literal search strings 在这里不适用,因为我们正在执行不区分大小写的搜索。也没有意外转义的问题,因为纯文件名不能包含\
,这是findstr
.
的转义字符
我在一个名为 upload_filenames.txt
的文件中有大约 600 个文件名的列表,我想找出它们在树中的位置,该树在 9K 个子目录中有大约 .7M 个文件。
这个(来自 this question)完成了工作:
for /F "usebackq delims=" %%i in (upload_filenames.txt) do (
for /F "delims=" %%b in ('dir /B /S /A:-D "%%i"') do (
echo %%~nxb;"%%~fb" >> exists.txt
)
)
现在,在同一个循环中,我还想用找到的所有 not 文件填充第二个文件。 (我可以从两个列表中手动获取它,但我更喜欢自动方式。)
到目前为止,我了解到 FOR
循环和 if exist
return errorlevel 0
成功时,但只有 'File Not Found' 并且没有错误级别不是。所以我不能使用那些。那么有什么办法可以批量处理吗?
旁白:我不关心效率。上面的脚本大约需要 10 个小时才能完成。就这样吧-暂时。
Windows 10 或服务器 2008
如果我得到你想要的正确,那么这应该可以解决问题。 未经测试!以后只能测试这个:
@echo off
for /F "usebackq delims=" %%a in ("upload_filenames.txt") do (
for /f "delims=" %%i in ('dir /b /s /a:d') do (
pushd "%%~i"
dir /b /a-d | findstr /i "%%~a">nul && echo %%~a;"%%~i%%~a">>exists.txt || echo %%~a not found in "%%~i">>not_exist.txt
popd
)
)
或者如果您不想使用条件运算符:
@echo off
for /F "usebackq delims=" %%a in ("upload_filenames.txt") do (
for /f "delims=" %%i in ('dir /b /s /a:d') do (
pushd "%%~i"
dir /b /a-d | findstr /i "%%~a">nul
if not errorlevel 1 (
echo %%~a;"%%~i\%%~a">> exists.txt
) else (
echo %%~a not found in "%%~i">>not_exist.txt
)
popd
)
)
所以思路是一个一个取文件名,然后dir
递归找到每个目录,pushd
到目录,然后做一个dir: of the file
一次重定向整个循环比逐行编写要快得多。此外,重复的 dir
需要大量 time.Do 和 dir
一次(进入文件)并使用该结果。 findstr
非常有效,所以我想在 for
循环中进行后处理而不是 if
会更快。
@echo off
setlocal
dir /b /s /a-d * > "files.txt"
(for /F "usebackq delims=" %%i in ("upload_filenames.txt") do (
for /f "delims=" %%b in ('findstr /iec:"\%%i" "files.txt" ^|^| echo ~') do (
echo %%i;%%b
)
)) > "result.txt"
findstr /ev ";~" "result.txt" > "existing.txt"
findstr /e ";~" "result.txt" > "missing.txt"
rem del "result.txt"
关键行是findstr /iec:"\%%i" files.txt || echo ~
,以文件名结尾的行输出,如果找不到文件,findstr
什么都不输出。在那种情况下(||
充当“如果先前的命令失败则”(source)),echo
命令将执行并输出 ~
(更改为您想要的任何字符串,但它必须是 anything,因为 for
循环会跳过空行)
和其他人一样,我会使用 findstr
,因为它比使用嵌套 for
循环快得多。但是,我会扭转局面,让实际存在的文件列表成为搜索字符串,并使用它们来搜索输入列表文件 upload_filenames.txt
。虽然我无法绕过一个 for
循环来从文件路径中导出纯文件名。无论如何,这是代码:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=." & rem // (path to target root directory)
set "_MASK=*.*" & rem // (file pattern, usually `*.*` for all)
set "_LIST=%~dp0upload_filenames.txt" & rem // (path to file containing name list)
set "_PASS=%~dp0found.txt" & rem // (path to positive result file)
set "_FAIL=%~dp0missing.txt" & rem // (path to negative result file)
set "_FULL=%~dpn0_all.tmp" & rem // (path to a temporary file)
set "_NAME=%~dpn0_names.tmp" & rem // (path to another temporary file)
rem // Put list of full paths of all files in the target directory tree to a file:
dir /S /B /A:-D "%_ROOT%\%_MASK%" > "%_FULL%"
rem // Reduce list of paths by maintaining the only pure file names:
> "%_NAME%" (
for /F "usebackq delims= eol=|" %%L in ("%_FULL%") do (
echo(%%~nxL
)
)
rem /* Let `findstr` twice do the search, using the file names from the target
rem directory tree as search strings against the original list file: */
findstr /I /X /L /G:"%_NAME%" "%_LIST%" > "%_PASS%"
findstr /I /X /V /L /G:"%_NAME%" "%_LIST%" > "%_FAIL%"
rem // Clean up temporary files:
del "%_FULL%" "%_NAME%"
endlocal
exit /B
这里有一个稍微不同的方法,它 returns 现有文件的完整路径,当文件名可能在给定目录树中多次出现时,这变得很重要:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=." & rem // (path to target root directory)
set "_MASK=*.*" & rem // (file pattern, usually `*.*` for all)
set "_LIST=%~dp0upload_filenames.txt" & rem // (path to file containing name list)
set "_PASS=%~dp0found.txt" & rem // (path to positive result file)
set "_FAIL=%~dp0missing.txt" & rem // (path to negative result file)
set "_AUGM=%_LIST%.tmp" & rem // (path to temporary list file)
set "_FULL=%~dpn0_all.tmp" & rem // (path to a temporary file)
set "_NAME=%~dpn0_names.tmp" & rem // (path to another temporary file)
rem // Create augmented copy of list file with each line preceded by `\`:
> "%_AUGM%" (
for /F "usebackq delims= eol=|" %%L in ("%_LIST%") do (
echo(\%%~L
)
)
rem // Put list of full paths of all files in the target directory tree to a file:
dir /S /B /A:-D "%_ROOT%\%_MASK%" > "%_FULL%"
rem // Reduce list of paths by maintaining the only pure file names:
> "%_NAME%" (
for /F "usebackq delims= eol=|" %%L in ("%_FULL%") do (
echo(%%~nxL
)
)
rem /* Let `findstr` do a search, using the augmented list file against the file
rem containing the list of full paths in order to eventually get full paths,
rem which is particularly important if file names are not unique in the tree: */
findstr /I /E /L /G:"%_AUGM%" "%_FULL%" > "%_PASS%"
rem /* Let `findstr` do another search, using the file names from the target
rem directory tree as search strings against the original list file this time: */
findstr /I /X /V /L /G:"%_NAME%" "%_LIST%" > "%_FAIL%"
rem // Clean up temporary files:
del "%_AUGM%" "%_FULL%" "%_NAME%"
endlocal
exit /B
N。 B.: 幸运的是 nasty flaw of findstr
with multiple literal search strings 在这里不适用,因为我们正在执行不区分大小写的搜索。也没有意外转义的问题,因为纯文件名不能包含\
,这是findstr
.