1 (изменено: Rumata, 2019-08-25 10:25:05)

Тема: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

В соседнем для меня форуме один товарищ спросил о возможности реализовать следующий функционал:

[1] Вывод размера в килобайтах
[2] Использовать разделитель тысяч

функционал требовалось реализовать исключительно на батниках.

Я решил размяться и попробовать свои силы. Кое-что получилось, но решение не полноценное, так как решение:

1. несколько громоздко
2. ограничено размером файлов не более 2 гигабайта
3. попытки улучшить, чтобы вычислять размеры любых файлов, скорее всего приведет к существенному усложнению скрипта

Публикую здесь скорее как курьез.

Вот сам скрипт. Он вычисляет размер cmd.exe и выводит его в байтах, кибибайтах и килобайтах.


@echo off

setlocal

set "DIGIT_GROUPING=,"

call :fsize "%COMSPEC%"
call :fsize "%COMSPEC%" KiB
call :fsize "%COMSPEC%" KB

endlocal
goto :EOF

:: SYNOPSIS
::
:: call :fsize [/v VARNAME] FILENAME [UNIT]
::
:: DESCRIPTION
::
:: Estimate the file size and scale it by UNIT.
:: If the option /v VARNAME is specified the result will be stored to
:: the variable VARNAME instead of printing.
::
:: UNIT is an optional unit prefix to represent big numbers in the
:: human-friendly format.
:: Units are of power 1000: KB, MB, GB
:: Units are of power 1024: KiB, MiB, GiB
::
:: ENVIRONMENT
::
:: DIGIT_GROUPING a character to be used as a delimiter of thousands.
:fsize
setlocal

set "fs_var="

if /i "%~1" == "/v" (
	set "fs_var=%~2"
	shift /1
	shift /1
)

set /a "fs_KiB=1024"
set /a "fs_MiB=fs_KiB * 1024"
set /a "fs_GiB=fs_MiB * 1024"

set /a "fs_KB=1000"
set /a "fs_MB=fs_KB * 1000"
set /a "fs_GB=fs_MB * 1000"

if not "%~2" == "" (
	if not defined fs_%~2 (
		echo:Unknown unit: "%~2">&2
		exit /b 1
	)
	rem The whitespace after "=" is mandatory
	set    "fs_name= %~2"
	set /a "fs_unit=fs_%~2"
)

set /a "fs_size=%~z1"
if defined fs_unit set /a "fs_size /= fs_unit"

if not defined DIGIT_GROUPING goto :fsize_2
if %fs_size% lss 1000 goto :fsize_2

set "fs_result="
set /a "fs_dividend=fs_size"

:fsize_1
set /a "fs_remainder=fs_dividend %% 1000"
set /a "fs_dividend=fs_dividend / 1000"

set "fs_remainder=000%fs_remainder%"
set "fs_result=%fs_remainder:~-3%%DIGIT_GROUPING:~0,1%%fs_result%"

if %fs_dividend% gtr 1000 goto :fsize_1

if %fs_dividend% gtr 0 (
	set "fs_result=%fs_dividend%%DIGIT_GROUPING:~0,1%%fs_result%"
)

set "fs_size=%fs_result:~0,-1%"

:fsize_2

if not defined fs_var (
	echo:%fs_size%%fs_name%
	goto :EOF
)

endlocal && set "%fs_var%=%fs_size%%fs_name%"
goto :EOF

Ниже в сообщениие #9 приведен улучшенный вариант.

( 2 * b ) || ! ( 2 * b )

2

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

В Коллекцию, полагаю.

3

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

alexii, Вы так считаете? Но ведь есть существенное ограничение в 2**31-1, о котором я говорил в п.2.

( 2 * b ) || ! ( 2 * b )

4

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

Ну, и про это упомянуть тоже.

5 (изменено: wisgest, 2019-08-25 03:32:42)

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

Для отделения трёх последних цифр находится остаток от деления на 1000, потом к нему слева приписываются нули (т.к. он может быть меньше 100) и затем от него откусывается справа три символа. Почему не откусывать по три символа справа от исходной строки без всякой арифметики?

6

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

wisgest, без арифметики совсем не обойтись - надо переводить в кратные байты. А если конкретно про разделение по тысячам... А будет ли от простого откусывания код проще?

( 2 * b ) || ! ( 2 * b )

7 (изменено: wisgest, 2019-08-25 03:38:07)

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

Rumata пишет:

без арифметики совсем не обойтись

Только в одном месте, где деление на fs_unit.

Rumata пишет:

А если конкретно про разделение по тысячам... А будет ли от простого откусывания код проще?

Будет немного проще, понятнее и быстрее.
Вот это:

if %fs_size% lss 1000 goto :fsize_2

set "fs_result="
set /a "fs_dividend=fs_size"

:fsize_1
set /a "fs_remainder=fs_dividend %% 1000"
set /a "fs_dividend=fs_dividend / 1000"

set "fs_remainder=000%fs_remainder%"
set "fs_result=%fs_remainder:~-3%%DIGIT_GROUPING:~0,1%%fs_result%"

if %fs_dividend% gtr 1000 goto :fsize_1

if %fs_dividend% gtr 0 (
	set "fs_result=%fs_dividend%%DIGIT_GROUPING:~0,1%%fs_result%"
)

по-моему, можно заменить на:

set "fs_result="

:fsize_1
	set "fs_result=%fs_size:~-3%%DIGIT_GROUPING:~0,1%%fs_result%"
	set "fs_size=%fs_size:~0,-3%"
if defined fs_size goto :fsize_1

Или я что-то не учёл?

8 (изменено: Rumata, 2019-08-25 08:27:51)

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

Выглядит хорошо. А как оно работает для групп чисел менее трех цифр? Например, 12, 1234, 12345.

Update

Сам спросил, сам ответил. Нормально работает. Сработал стереотип: если работаем с числовой информацией, то и применяем числовые операции. Спасибо, коллега, за подсказку.

( 2 * b ) || ! ( 2 * b )

9

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

Давайте я пока оставлю обновленную версию с улучшением от wisgest, пусть "дозреет". А потом можно будет перенести в Коллекцию


@echo off

setlocal

set "DIGIT_GROUPING=,"

call :fsize "%COMSPEC%"
call :fsize "%COMSPEC%" KiB
call :fsize "%COMSPEC%" KB

endlocal
goto :EOF

:: SYNOPSIS
::
:: call :fsize [/v VARNAME] FILENAME [UNIT]
::
:: DESCRIPTION
::
:: Estimate the file size and scale it by UNIT.
:: If the option /v VARNAME is specified the result will be stored to
:: the variable VARNAME instead of printing.
::
:: UNIT is an optional unit prefix to represent big numbers in the
:: human-friendly format.
:: Units are of power 1000: KB, MB, GB
:: Units are of power 1024: KiB, MiB, GiB
::
:: ENVIRONMENT
::
:: DIGIT_GROUPING a character to be used as a delimiter of thousands.
:fsize
setlocal

set "fs_var="

if /i "%~1" == "/v" (
	set "fs_var=%~2"
	shift /1
	shift /1
)

set /a "fs_KiB=1024"
set /a "fs_MiB=fs_KiB * 1024"
set /a "fs_GiB=fs_MiB * 1024"

set /a "fs_KB=1000"
set /a "fs_MB=fs_KB * 1000"
set /a "fs_GB=fs_MB * 1000"

if not "%~2" == "" (
	if not defined fs_%~2 (
		echo:Unknown unit: "%~2">&2
		exit /b 1
	)
	rem The whitespace after "=" is mandatory
	set    "fs_name= %~2"
	set /a "fs_unit=fs_%~2"
)

set /a "fs_size=%~z1"

if defined fs_unit set /a "fs_size /= fs_unit"

if not defined DIGIT_GROUPING goto :fsize_2

set "fs_result="

:fsize_1
set "fs_result=%fs_size:~-3%%DIGIT_GROUPING:~0,1%%fs_result%"
set "fs_size=%fs_size:~0,-3%"
if defined fs_size goto :fsize_1

set "fs_size=%fs_result:~0,-1%"

:fsize_2

if not defined fs_var (
	echo:%fs_size%%fs_name%
	goto :EOF
)

endlocal && set "%fs_var%=%fs_size%%fs_name%"
goto :EOF
( 2 * b ) || ! ( 2 * b )

10 (изменено: Rumata, 2019-08-31 01:13:54)

Re: CMD/BAT: Вывод размера файла с приставками и разделителями тысяч

А вот этот код


if                         1 gtr 9 echo:1e00 is greater
if                        10 gtr 9 echo:1e01 is greater
if                      1000 gtr 9 echo:1e03 is greater (K)
if                   1000000 gtr 9 echo:1e06 is greater (M)
if                1000000000 gtr 9 echo:1e09 is greater (G)
if             1000000000000 gtr 9 echo:1e12 is greater (T)
if          1000000000000000 gtr 9 echo:1e15 is greater (P)
if       1000000000000000000 gtr 9 echo:1e18 is greater (E)
if    1000000000000000000000 gtr 9 echo:1e21 is greater (Z)
if 1000000000000000000000000 gtr 9 echo:1e24 is greater (Y)

логично выводит


1e01 is greater
1e03 is greater (K)
1e06 is greater (M)
1e09 is greater (G)
1e12 is greater (T)
1e15 is greater (P)
1e18 is greater (E)
1e21 is greater (Z)
1e24 is greater (Y)

Потому что сказано:

IF /? пишет:

<...>.  These comparisons are generic, in that if both string1 and
string2 are both comprised of all numeric digits, then the strings are
converted to numbers and a numeric comparison is performed.

Хотя два последних сравнения в контексте задачи лишние, так как ограничение на размер файла в NTFS - 16 эксбибайт - 1 кибибайт.

Используя эту возможность команды IF, можно обойти выше упомянутое ограничение.

update:

Наверно можно. Но получается как-то ужасно.

( 2 * b ) || ! ( 2 * b )