Not able to output to file in the Windows command line

Posted on

QUESTION :

In the following code, I need to take the path and size of folder and subfolders into a file. But when the loop runs for the second time, path and size are not getting printed to the file. size.txt only contains the path and size of the 1st folder. Please somebody help me.

@echo off

SETLOCAL EnableDelayedExpansion
SET xsummary=
SET xsize=

for /f "tokens=1,2 delims=C" %%i IN ('"dir /s /-c /a | find "Directory""') do (echo    C%%j >> abcd.txt)
for /f "tokens=*" %%q IN (abcd.txt) do (
cd "%%q"
For /F "tokens=*" %%h IN ('"dir /s /-c /a | find "bytes" | find /v "free""') do Set    xsummary=%%h
For /f "tokens=1,2 delims=)" %%a in ("!xsummary!") do set xsize=%%b
Set xsize=!xsize:bytes=!
Set xsize=!xsize: =!
echo.%%q >> size.txt
echo.!xsize! >> size.txt

)

ANSWER :

The problem is the cd — you are creating a two-line size.txt file in each directory.  Try

for ... %%h in ('"dir /s /-c /a  %%q  | find "bytes" | find /v "free""') ...

and remove the cd.

By the way, do you realize that you are getting cumulative totals?  For example, if your directory structure looks like

a   100K
ab 200K
ac 400K

then your result will say

a
700000
ab
200000
ac
400000

That’s because the second dir has the /s option.  That’s fine if that’s what you want; I just thought it might have been an oversight.

If you can manage it, avoid this mess by grabbing the Sysinternals du utility and running du -v <directoryname>. Output will be kilobytes. The -v option means output the size of all subdirectories.


With your current script, the problem seems to be appending text while in a loop. An easy workaround is to output to the command prompt window, like so:

echo %%q
echo !xsize!

and when you run the batch file, sent its output to a text file: test.bat > size.txt


A quick tip: the first bit can be simplified with:

for /f "tokens=*" %%q in ('dir /a:d /s /b') do (
    cd "%%q"
    ...
    echo !xsize!
)

Basically, it’s replacing the use of find with the /a:d option (list directories only) and /b (use bare format, no heading or summary). This also gets rid of the temporary file.


Modified version to work on folders with exclamation marks in the name. Using Scott’s answer. See comments below.

Note that REM means it’s a comment.

@echo off

REM clear the file
type NUL > size.txt

for /f "tokens=*" %%q IN ('dir /a:d /s /b') do (
    echo %%q >> size.txt
    REM Run :getsize, passing "%%q" including quotes as the first parameter
    call :getsize "%%q" >> size.txt
)

REM go to the end of the file, i.e. exit
goto :eof

:getsize
REM %1 is the  first parameter. It should already be quoted.
for /f "tokens=*" %%h IN ('"dir /s /-c /a %1 | find "bytes" | find /v "free""') do set xsummary=%%h
for /f "tokens=1,2 delims=)" %%a in ("%xsummary%") do set xsize=%%b
set xsize=%xsize:bytes=%
set xsize=%xsize: =%
echo %xsize%

goto :eof

An alternative is to use PowerShell:

Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | %{$_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0; $_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum; $_} | Sort-Object DirSize -Descending | Select-Object -First 10 FullName,DirSize | Format-Table -AutoSize | Out-String -Width 4096 | Out-File 'sizes.txt';

This is overly complex for the purpose of being as complete as possible – also, most of those commands have shorter aliases; this is just more readable. And if you separate certain sections out into functions/their own line, it becomes much more readable and possibly shorter.

Expanding it:

  • Get-ChildItem -Recurse

    Recursively list all subdirectories and files

  • ?{ $_.PSIsContainer }

    Filter so only directories are kept (? is an alias for Where-Object). $_ refers to the object(s) piped to this – since Get-ChildItem passes a list/array of objects, this processes them one at a time.

  • %{$_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0; $_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum; $_}

    Calculate size of each directory (% is an alias for Foreach-Object, so run what is between { } on each individual object. Each object is a directory, as per the previous parts.)

    Expanding it:

    • $_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0;

      A semicolon (;) denotes the end of a line/statement, and does not pass the output on, as opposed to a pipe (|). This particular bit has no output, and it’s just adding the DirSize property to the directory object and initialising it to 0. This is done for easier filtering and neater output later.

    • $_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum;

      Set DirSize to the Sum property of the output of that command. $() means execute what is inside the parentheses and substitute that in.

      Expanding it:

      • Get-ChildItem -Recurse $_.Fullname

        Get all the contents of the current directory object ($_, Fullname just means the full path). -Recurse means count the contents of subdirectories as well.

      • ?{ -not $_.PSIsContainer }

        Filter so only files are kept. Directories have a Length property of 0, so we need to use sum the files within – essentially what your batch script does, except we’re dealing with actual numbers, objects and properties instead of grabbing a ‘number’ as text using find.

      • Measure-Object -Sum -Property Length

        Calculate the sum of all the Length properties passed to it. This returns an object containing the property Sum, which is used above (.Sum)

    • $_

    Essentially echoing the object to pass it on to the next section. It now has a DirSize property.

  • Sort-Object DirSize -Descending

    Sort descending by DirSize

  • Select-Object -First 10 FullName,DirSize

    Only output the first 10, and only the name and size

  • Format-Table -AutoSize | Out-String -Width 4096 | Out-File 'sizes.txt';

    Output to sizes.txt. The Format-Table and Out-String bits are to prevent truncating to console window width (normally 80 characters). 4096 should be more than enough characters for any Windows path and directory size, considering a Windows path is typically limited to 255 characters (IIRC). The AutoSize just means make it as short as possible without truncating, instead of padding with a whole lot of whitespace to fit 4096 characters.

And the command as a one liner to run in cmd.exe (you can run the above version within PowerShell):

powershell -c "Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | %{$_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0; $_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum; $_} | Sort-Object DirSize -Descending | Select-Object -First 10 FullName,DirSize | Format-Table -AutoSize | Out-String -Width 4096 | Out-File 'sizes.txt';"