问题描述
我在一个名为upload_filenames.txt
的文件中有大约600个文件名列表,我想找出它们在树中的位置,该树在9K子目录中有大约0.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
)
)
现在,在同一循环中,我还想用找到的所有 文件填充第二个文件。 (我可以从两个列表中手动获取它,但我更喜欢自动方式。)
到目前为止,我了解到FOR
循环和if exist
成功时返回errorlevel 0
,但成功时仅返回“找不到文件”,而没有错误级别。所以我不能使用那些。那么有什么办法可以批量进行此操作吗?
此外:我不在乎效率。上面的脚本花费了大约10个小时来完成。就是这样-现在。
Windows 10或Server 2008
解决方法
一次重定向整个循环比逐行写入要快得多。同样,重复的dir
也要花费很多时间。只需将dir
一次(写入一个文件)并处理该结果。 findstr
非常有效,因此我想在if
循环中进行后处理而不是for
更快。
@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
命令将执行并输出~
(更改为所需的任何字符串)。 ,但必须是任何内容,因为for
循环会跳过空行)
如果我正确地获得了您想要的东西,那么这应该可以解决问题。 未经测试!以后只能进行测试:
@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:
与其他人一样,我将使用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
这是一种略有不同的方法,它返回现有文件的完整路径,当文件名在给定目录树中可能多次出现时,这一点变得很重要:
@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
。