Skip to content

Instantly share code, notes, and snippets.

@mlevkov
Created February 5, 2026 01:48
Show Gist options
  • Select an option

  • Save mlevkov/4238d302fb54e40e342f9219ca6b268a to your computer and use it in GitHub Desktop.

Select an option

Save mlevkov/4238d302fb54e40e342f9219ca6b268a to your computer and use it in GitHub Desktop.
Batch 'cargo clean' for multiple Rust projects - safely removes target directories with progress bar and space savings summary
#!/bin/bash
# Safely run 'cargo clean' on all Rust project roots
#
# SAFETY: This script ONLY executes 'cargo clean' command.
# It does NOT use rm, rmdir, or any direct deletion commands.
# Don't use set -e as we handle errors manually and want to continue on failures
ROOT_DIR="${1:-.}"
# Function to check if a directory is inside another Cargo project
is_nested_project() {
local dir="$1"
local check_dir=$(dirname "$dir")
local abs_root=$(realpath "$ROOT_DIR")
while [[ "$check_dir" != "." && "$check_dir" != "/" ]]; do
[[ "$(realpath "$check_dir")" == "$abs_root" ]] && break
if [[ -f "$check_dir/Cargo.toml" ]]; then
return 0
fi
check_dir=$(dirname "$check_dir")
done
return 1
}
# Convert size string (e.g., "5.0GiB", "100MiB") to bytes
size_to_bytes() {
local size_str="$1"
local num unit
# Extract number and unit
num=$(echo "$size_str" | grep -oE '[0-9]+\.?[0-9]*')
unit=$(echo "$size_str" | grep -oE '[A-Za-z]+')
# Convert to bytes using awk for floating point
case "$unit" in
B) echo "$num" | awk '{printf "%.0f", $1}' ;;
KiB) echo "$num" | awk '{printf "%.0f", $1 * 1024}' ;;
MiB) echo "$num" | awk '{printf "%.0f", $1 * 1024 * 1024}' ;;
GiB) echo "$num" | awk '{printf "%.0f", $1 * 1024 * 1024 * 1024}' ;;
TiB) echo "$num" | awk '{printf "%.0f", $1 * 1024 * 1024 * 1024 * 1024}' ;;
*) echo "0" ;;
esac
}
# Convert bytes to human-readable format
bytes_to_human() {
local bytes=$1
if (( bytes >= 1099511627776 )); then
awk "BEGIN {printf \"%.2f TiB\", $bytes / 1099511627776}"
elif (( bytes >= 1073741824 )); then
awk "BEGIN {printf \"%.2f GiB\", $bytes / 1073741824}"
elif (( bytes >= 1048576 )); then
awk "BEGIN {printf \"%.2f MiB\", $bytes / 1048576}"
elif (( bytes >= 1024 )); then
awk "BEGIN {printf \"%.2f KiB\", $bytes / 1024}"
else
echo "$bytes B"
fi
}
# Collect all project roots
echo "Scanning for Rust projects in: $(realpath "$ROOT_DIR")"
echo ""
projects=()
while IFS= read -r cargo_file; do
project_dir=$(dirname "$cargo_file")
if ! is_nested_project "$project_dir"; then
projects+=("$project_dir")
fi
done < <(find "$ROOT_DIR" -name "target" -prune -o -name "Cargo.toml" -print 2>/dev/null)
total=${#projects[@]}
if [[ $total -eq 0 ]]; then
echo "No Rust projects found."
exit 0
fi
# Display all locations that will be cleaned
echo "Found $total Rust project(s) to clean:"
echo "========================================"
for i in "${!projects[@]}"; do
printf " %3d. %s\n" "$((i + 1))" "${projects[$i]}"
done
echo "========================================"
echo ""
# Ask for confirmation
read -p "Proceed with 'cargo clean' on all $total projects? [y/N] " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 0
fi
echo ""
# Progress bar function
show_progress() {
local current=$1
local total=$2
local width=40
local percent=$((current * 100 / total))
local filled=$((current * width / total))
local empty=$((width - filled))
printf "\r["
printf "%${filled}s" | tr ' ' '#'
printf "%${empty}s" | tr ' ' '-'
printf "] %3d%% (%d/%d)" "$percent" "$current" "$total"
}
# Run cargo clean on each project
cleaned=0
failed=0
total_files=0
total_bytes=0
for i in "${!projects[@]}"; do
project="${projects[$i]}"
show_progress "$((i + 1))" "$total"
# ONLY cargo clean - no other deletion commands
# Capture output to parse removed files/size
output=$(cd "$project" && cargo clean 2>&1) || true
if [[ "$output" == *"Removed"* ]]; then
((cleaned++)) || true
# Parse: "Removed 21708 files, 5.0GiB total"
files=$(echo "$output" | grep -oE 'Removed [0-9]+' | grep -oE '[0-9]+')
size=$(echo "$output" | grep -oE '[0-9]+\.?[0-9]*[KMGT]?i?B')
if [[ -n "$files" ]]; then
total_files=$((total_files + files))
fi
if [[ -n "$size" ]]; then
bytes=$(size_to_bytes "$size")
total_bytes=$((total_bytes + bytes))
fi
elif [[ -z "$output" ]]; then
# No output means nothing to clean (already clean)
((cleaned++)) || true
else
((failed++)) || true
echo ""
echo " Warning: cargo clean issue in $project: $output"
fi
done
echo ""
echo ""
echo "========================================"
echo "Complete: $cleaned cleaned, $failed failed"
echo "Removed $total_files files, $(bytes_to_human $total_bytes) total"
echo "========================================"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment