A cleanup job for a folder of camera clips had one line meant to remove the broken stubs a recorder leaves behind when it stops mid-write: find clips -size -1M -delete. Anything under a megabyte, gone. I tested that line on six files with exact byte counts, and it selected one of them: the empty one. A 500-byte stub survived it.
The same test turned up two more ways a size in a shell script is not the number it looks like. ls -lh and ls -l --si printed the same letter for different units, and both rounded up. And a storage estimate that mixes the two kinds of gigabyte comes out about 7% short. Below are the files, what each tool printed, what the GNU manuals say about it, and the lines I would use instead.
Disclosure: I make an Android camera app. Nothing below uses its code or describes how it records or stores anything. The files are random bytes made on a Linux machine, and every output is pasted from the scripts shown. Versions:
find (GNU findutils) 4.8.0 ls (GNU coreutils) 8.32 Python 3.10.12 Linux 6.8.0-138-generic Six files with known sizes
The sizes are picked to sit on both sides of the two meanings of a megabyte: 1,000,000 bytes and 1,048,576 bytes.
#!/bin/sh # Six stand-in clips with exact byte counts, from empty to just over one MiB. set -e rm -rf clips && mkdir clips && cd clips truncate -s 0 empty.mp4 head -c 500 /dev/urandom > stub_500.mp4 head -c 1000000 /dev/urandom > mb_1000000.mp4 head -c 1048576 /dev/urandom > mib_1048576.mp4 head -c 1048577 /dev/urandom > mib_1048577.mp4 head -c 1500000 /dev/urandom > mb_1500000.mp4 Three ways to print one size
For each file: the byte count from stat, then the size column from ls -lh and from ls -l --si.
#!/bin/sh # The same six files in three units: bytes, ls -h (1024) and ls --si (1000). cd clips for f in *.mp4; do printf '%-16s %8s -h %5s --si %5s\n' "$f" "$(stat -c %s "$f")" \ "$(ls -lh "$f" | awk '{print $5}')" "$(ls -l --si "$f" | awk '{print $5}')" done empty.mp4 0 -h 0 --si 0 mb_1000000.mp4 1000000 -h 977K --si 1.0M mb_1500000.mp4 1500000 -h 1.5M --si 1.5M mib_1048576.mp4 1048576 -h 1.0M --si 1.1M mib_1048577.mp4 1048577 -h 1.1M --si 1.1M stub_500.mp4 500 -h 500 --si 500 Read down the two right-hand columns:
- The letter does not say which unit.
-hcounts in 1024s and--siin 1000s. The coreutils manual describes-has the format that "uses powers of 1024" and the SI format as one that "uses powers of 1000". Both printed a bareMhere, so a column of1.1Mvalues from someone else's terminal does not tell you which one you are looking at. - Both round up. The file of exactly 1,048,576 bytes shows as
1.1Munder--si, and the file one byte larger shows as1.1Munder-h. The manual says so in general terms: "Fractional block counts are rounded up to the nearest integer." A displayed size is a ceiling, not a floor. - A decimal megabyte is 977K under
-h. Anyone checking by eye that a file is at least a decimal megabyte withls -lhwill think the 1,000,000-byte file is under.
None of this is wrong. It is just three numbers for one file, and a script that copies one of them into a threshold inherits whatever the display did to it.
Which files each find -size test picks
#!/bin/sh # Which files each -size test selects. cd clips for t in -1M 1M +1M -1024k -1048576c +999999c; do printf '%-11s %s\n' "$t" "$(find . -type f -size "$t" -printf '%f\n' | sort | tr '\n' ' ')" done -1M empty.mp4 1M mb_1000000.mp4 mib_1048576.mp4 stub_500.mp4 +1M mb_1500000.mp4 mib_1048577.mp4 -1024k empty.mp4 mb_1000000.mp4 stub_500.mp4 -1048576c empty.mp4 mb_1000000.mp4 stub_500.mp4 +999999c mb_1000000.mp4 mb_1500000.mp4 mib_1048576.mp4 mib_1048577.mp4 -size -1M matched only empty.mp4. -size 1M, which reads like exactly one megabyte, matched the 500-byte stub along with the two files that really are about a megabyte. And -size +1M left out the file of exactly 1,048,576 bytes.
The findutils manual explains all three with one rule. The test is "True if the file uses n units of space, rounding up", and later: "Bear in mind that the size is rounded up to the next unit." So with M as the unit, a 500-byte file counts as one MiB, and so does a 1,000,000-byte file. Nothing non-empty is less than one unit, which is why the manual's own example says of -size -1M against -size -1048576c: "The former only matches empty files, the latter matches files from 0 to 1,048,575 bytes."
One detail did not match my reading of the manual. It says a plus sign means the test succeeds "if the file uses at least n units of storage". The 1,048,576-byte file uses exactly one unit, and on findutils 4.8.0 -size +1M did not select it. What I saw is more than one unit, after rounding up. The same paragraph says of the minus sign that "an exact size of n units does not match", and on this build the plus sign behaved the same way at the boundary. If your script depends on that edge, test it on your own build rather than trusting either reading.
The two rows that worked are the ones with a small unit. -1048576c counts bytes, so there is no rounding at all, and -1024k rounds to the next KiB, which made no difference for these files. Both selected the empty file, the stub and the decimal megabyte, and left the 1,048,576-byte file alone.
The gigabyte on the card and the G in df -h
The same mismatch shows up in the arithmetic people do before leaving a phone recording: how many hours fit on the card. A card sold as 64 GB is counted in decimal gigabytes. df -h reports in powers of 1024 and prints a G. A stream bitrate is usually quoted in decimal megabits. Mix them and the estimate shifts:
# A card sold as 64 GB, in the units df -h prints, and hours of a stream at 8 Mbit/s. card = 64 * 10**9 # bytes, decimal gigabytes gib = card / 2**30 per_hour = 8 * 10**6 / 8 * 3600 # bytes per hour at 8 Mbit/s print("64 GB card = %d bytes = %.2f GiB" % (card, gib)) print("8 Mbit/s for 1h = %d bytes = %.2f GB = %.2f GiB" % (per_hour, per_hour / 10**9, per_hour / 2**30)) print("hours, same units : %.2f" % (card / per_hour)) print("hours, GiB / GB/h : %.2f (%.1f%% short)" % (gib / (per_hour / 10**9), 100 * (1 - (gib / (per_hour / 10**9)) / (card / per_hour)))) 64 GB card = 64000000000 bytes = 59.60 GiB 8 Mbit/s for 1h = 3600000000 bytes = 3.60 GB = 3.35 GiB hours, same units : 17.78 hours, GiB / GB/h : 16.56 (6.9% short) These numbers are arithmetic on a made-up stream, not a measurement of any recorder, and a real card also loses space to the file system, which I did not model. The point is the 6.9%: taking the figure df -h shows and dividing by an hourly size worked out in decimal gigabytes understates the hours by that much. The opposite mix, a card's label divided by an hourly size in GiB, overstates them, by 7.4% on these numbers. Neither error is visible in the output, because both sides print a plain G.
What I would write instead
- Give
findthresholds in bytes.find clips -type f -size -1048576cselects files under 1,048,576 bytes and nothing else. If you want the decimal megabyte, write-size -1000000c. Either way, check with-printbefore adding-delete. - Do not use
-size -1Mor-size -1Gto mean small files. They only match empty files. If you do want empty files,-emptysays so directly. - Read sizes with
stat -c %sin scripts, not from thels -lhcolumn. The display rounds up and drops the unit. - Pick one unit for capacity math and say which. Convert the card's label to bytes, convert the bitrate to bytes per hour, and divide. If you start from
df, usedf -B1ordf --output=avail -B1so the number is in bytes.
Things I did not test
- BSD and macOS
findandls. Everything here is GNU findutils and coreutils on Linux. I have not checked whether other implementations round the same way. - Sparse files and allocated size.
-sizeandls -lboth use the file's apparent length. Disk usage tools that count allocated blocks can disagree with both, which is a separate question. - Real recordings. The six files are random bytes with chosen lengths. A stub left by a real recorder may be larger or smaller than 500 bytes; the rounding rule is the same either way.
Sources. find -size quotes are from the GNU findutils manual, section 2.6 Size. Block size quotes are from the GNU coreutils manual, section on block size. Outputs are pasted unedited from the scripts above.
If you want an old Android phone recording in the background so there is a folder of clips for a job like this to clean up, the app I make is Background Camera RemoteStream on Google Play. Nothing above depends on it.
In a cleanup script, a size written in megabytes is rounded before it is compared, so write the threshold in bytes and check what it selects before it deletes anything.



