#!/usr/bin/env bash # verify-find-largest-files.sh # Falsifies the claims on https://heatware.net/linux/find-largest-files/ # # Self-contained. Builds its own scratch tree (including a sparse file and a hard link), # removes it via an EXIT trap, and exits 0 when run alone in an empty directory. # Anything untestable here is SKIP, never FAIL. # Executed checks are counted in PASS/FAIL; greps of the article are counted in CONTENT. set -u PASS=0; FAIL=0; SKIP=0; CONTENT=0 pass() { echo "PASS $*"; PASS=$((PASS+1)); } fail() { echo "FAIL $*"; FAIL=$((FAIL+1)); } skip() { echo "SKIP $*"; SKIP=$((SKIP+1)); } cpass() { echo "CONTENT-OK $*"; CONTENT=$((CONTENT+1)); } # Claude Code and some shells alias `find`; always use the real binary. FIND="" for c in /usr/bin/find /bin/find; do [ -x "$c" ] && { FIND=$c; break; }; done [ -n "$FIND" ] || FIND=$(command -v find 2>/dev/null || true) DD="" for c in /bin/dd /usr/bin/dd; do [ -x "$c" ] && { DD=$c; break; }; done [ -n "$DD" ] || DD=$(command -v dd 2>/dev/null || true) if [ -z "$FIND" ] || [ -z "$DD" ] || ! command -v du >/dev/null 2>&1; then echo "SKIP every check (need find, dd and du; find=${FIND:-none} dd=${DD:-none})" echo echo "PASS=0 FAIL=0 SKIP=1 CONTENT=0" exit 0 fi ORIG_PWD=$(pwd) SCRIPT_DIR=$(cd "$(dirname "$0")" 2>/dev/null && pwd) || SCRIPT_DIR=$ORIG_PWD WORK=$(mktemp -d "${TMPDIR:-/tmp}/hw_w14_biggest.XXXXXX") || exit 1 trap 'chmod -R u+rwX "$WORK" 2>/dev/null; rm -rf "$WORK"' EXIT cd "$WORK" || exit 1 echo "# environment" echo "# uname: $(uname -s) $(uname -m)" echo "# find: $FIND" echo "# du: $(command -v du)" echo mkdir -p tree/sub tree/other "$DD" if=/dev/urandom of=tree/sub/big1.bin bs=1m count=8 >/dev/null 2>&1 || \ "$DD" if=/dev/urandom of=tree/sub/big1.bin bs=1048576 count=8 >/dev/null 2>&1 "$DD" if=/dev/urandom of=tree/other/big2.bin bs=1m count=4 >/dev/null 2>&1 || \ "$DD" if=/dev/urandom of=tree/other/big2.bin bs=1048576 count=4 >/dev/null 2>&1 printf 'tiny\n' > tree/small.txt if [ ! -s tree/sub/big1.bin ]; then echo "SKIP every check (could not create the scratch files with dd)" echo echo "PASS=0 FAIL=0 SKIP=1 CONTENT=0" exit 0 fi # ------------------------------------------ 1. du -a lists directories, not just files top=$(du -a tree | sort -n -r | head -1 | awk '{print $2}') if [ -d "$top" ]; then pass "the top entry of 'du -a | sort -nr' is a directory ($top), not a file" else fail "expected a directory at the top of du -a output; got $top" fi ndirs=$(du -a tree | awk '{print $2}' | while read -r p; do [ -d "$p" ] && echo d; done | wc -l | tr -d ' ') if [ "$ndirs" -ge 3 ]; then pass "du -a listed $ndirs directories alongside the files" else fail "du -a listed only $ndirs directories; expected at least 3" fi # ---------------------------------------------- 2. du block units, and -k as the fix bsdk=$(du -ak tree/sub/big1.bin | awk '{print $1}') if command -v gdu >/dev/null 2>&1; then gnuk=$(gdu -ak tree/sub/big1.bin | awk '{print $1}') raw_bsd=$(du -a tree/sub/big1.bin | awk '{print $1}') raw_gnu=$(gdu -a tree/sub/big1.bin | awk '{print $1}') if [ "$bsdk" = "$gnuk" ]; then pass "du -k agrees across implementations ($bsdk KiB for the same file)" else fail "du -k disagreed: this du says $bsdk, gdu says $gnuk" fi if [ "$raw_bsd" != "$raw_gnu" ]; then pass "without -k the two disagree: $raw_bsd vs $raw_gnu blocks for one file - the unit is implementation-defined" else skip "block-unit difference (both dus report $raw_bsd here, so this build uses the same default)" fi pc=$(POSIXLY_CORRECT=1 gdu -a tree/sub/big1.bin | awk '{print $1}') if [ "$pc" != "$raw_gnu" ]; then pass "POSIXLY_CORRECT flips GNU du from $raw_gnu to $pc for the same file" else skip "POSIXLY_CORRECT block-size switch (no change on this build)" fi else skip "GNU/BSD du unit comparison (no gdu on this machine; only one implementation available)" fi # ------------------------------------- 3. sparse file: find says huge, du says tiny "$DD" if=/dev/zero of=sparse.bin bs=1 count=1 seek=100m >/dev/null 2>&1 || \ "$DD" if=/dev/zero of=sparse.bin bs=1 count=1 seek=104857600 >/dev/null 2>&1 if [ -f sparse.bin ]; then apparent=$(wc -c < sparse.bin | tr -d ' ') allocated=$(du -k sparse.bin | awk '{print $1}') hit=$("$FIND" . -type f -size +50M -name sparse.bin | wc -l | tr -d ' ') if [ "$apparent" -gt 100000000 ] && [ "$allocated" -lt 1024 ] && [ "$hit" -eq 1 ]; then pass "sparse file: $apparent bytes apparent, ${allocated}K allocated, and find -size +50M still matches it" elif [ "$apparent" -gt 100000000 ] && [ "$allocated" -ge 1024 ]; then skip "sparse-file check (this filesystem allocated ${allocated}K - it does not support sparse files)" else fail "sparse-file check: apparent=$apparent allocated=${allocated}K find_hits=$hit" fi else skip "sparse-file check (dd could not create it)" fi # --------------------------------------------------------- 4. hard links mkdir -p hl "$DD" if=/dev/urandom of=hl/original.bin bs=1m count=5 >/dev/null 2>&1 || \ "$DD" if=/dev/urandom of=hl/original.bin bs=1048576 count=5 >/dev/null 2>&1 if ln hl/original.bin hl/hardlink.bin 2>/dev/null; then listed=$(du -a hl | grep -c 'hardlink.bin') listed_l=$(du -al hl | grep -c 'hardlink.bin') total=$(du -sk hl | awk '{print $1}') total_l=$(du -slk hl | awk '{print $1}') found=$("$FIND" hl -type f | wc -l | tr -d ' ') if [ "$listed" -eq 0 ] && [ "$listed_l" -eq 1 ]; then pass "du -a omitted the second hard-linked name entirely; du -al listed it" else fail "hard-link listing: du -a hits=$listed, du -al hits=$listed_l; article says 0 then 1" fi if [ "$total_l" -gt "$total" ]; then pass "du -sl double-counted the inode: ${total_l}K vs ${total}K for the same 5 MiB of data" else fail "expected du -sl (${total_l}K) to exceed du -s (${total}K)" fi if [ "$found" -eq 2 ]; then pass "find counted both names ($found files) where du counted one inode" else fail "find saw $found files in the hard-link directory, expected 2" fi else skip "hard-link checks (this filesystem would not create a hard link)" fi # --------------------------------------- 5. ls -lS is not recursive biggest_ls=$(ls -lS tree | awk 'NR>1 {print $NF; exit}') if [ "$biggest_ls" != "big1.bin" ]; then pass "ls -lS in the parent directory reported '$biggest_ls' and never saw the 8 MiB file one level down" else fail "ls -lS unexpectedly surfaced the nested file" fi # ------------------------- 6. the awk pipeline truncates names containing spaces mkdir -p sp "$DD" if=/dev/urandom of="sp/my big video.mov" bs=1m count=6 >/dev/null 2>&1 || \ "$DD" if=/dev/urandom of="sp/my big video.mov" bs=1048576 count=6 >/dev/null 2>&1 awkname=$("$FIND" sp -type f -size +5M -exec ls -lh {} \; | awk '{print $9}' | head -1) realname=$("$FIND" sp -type f -size +5M | head -1) if [ -n "$realname" ] && [ "$awkname" != "$realname" ]; then pass "the awk '{print \$9}' pipeline reported [$awkname] for a file actually named [$realname]" else fail "space-in-filename check: awk gave [$awkname], find gave [$realname]" fi statout="" if stat -f '%z %N' "$realname" >/dev/null 2>&1; then statout=$(stat -f '%z %N' "$realname") elif stat -c '%s %n' "$realname" >/dev/null 2>&1; then statout=$(stat -c '%s %n' "$realname") fi if [ -n "$statout" ] && printf '%s' "$statout" | grep -q "my big video.mov"; then pass "the stat form kept the whole name: $statout" else skip "stat size form (neither BSD -f nor GNU -c worked on this stat)" fi # --------------------------------------------- 7. -exec \; forks once per file mkdir -p c; for i in 1 2 3 4 5; do : > "c/f$i"; done n_semi=$("$FIND" c -type f -exec sh -c 'echo x' \; 2>/dev/null | wc -l | tr -d ' ') n_plus=$("$FIND" c -type f -exec sh -c 'echo x' {} + 2>/dev/null | wc -l | tr -d ' ') if [ "$n_semi" -eq 5 ] && [ "$n_plus" -eq 1 ]; then pass "-exec ... \\; ran $n_semi times over 5 files; -exec ... {} + ran $n_plus" else fail "-exec batching: \\; -> $n_semi, + -> $n_plus; article says 5 and 1" fi # ------------------------------------------------------ article content guards ART="" for cand in "$SCRIPT_DIR/find-largest-files.html" "$ORIG_PWD/find-largest-files.html"; do [ -f "$cand" ] && { ART=$cand; break; } done if [ -n "$ART" ]; then grep -q "sparse" "$ART" && cpass "article covers the sparse-file trap" \ || { echo "FAIL sparse-file section missing"; FAIL=$((FAIL+1)); } grep -q "hardlink.bin" "$ART" && cpass "article keeps the hard-link demonstration" \ || { echo "FAIL hard-link demonstration missing"; FAIL=$((FAIL+1)); } grep -q "/linux/fix-no-space-left-on-device-error/" "$ART" && cpass "article links to the ENOSPC page instead of rebuilding it" \ || { echo "FAIL internal link to the ENOSPC page missing"; FAIL=$((FAIL+1)); } grep -q "/linux/find-command-tutorial/" "$ART" && cpass "article links to the find tutorial for -size units" \ || { echo "FAIL internal link to find-command-tutorial missing"; FAIL=$((FAIL+1)); } grep -q "my big video.mov" "$ART" && cpass "article keeps the awk field-splitting failure" \ || { echo "FAIL awk field-splitting failure missing"; FAIL=$((FAIL+1)); } else skip "article self-checks (find-largest-files.html not next to this script)" fi echo echo "PASS=$PASS FAIL=$FAIL SKIP=$SKIP CONTENT=$CONTENT" [ "$FAIL" -eq 0 ] || exit 1 exit 0