Skip to content

Instantly share code, notes, and snippets.

@fonic
Last active June 19, 2020 21:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fonic/647011012b48d6dcd65e8725fed48b0c to your computer and use it in GitHub Desktop.
Save fonic/647011012b48d6dcd65e8725fed48b0c to your computer and use it in GitHub Desktop.
Erase/wipe, partition and format device on Linux
#!/usr/bin/env bash
# -------------------------------------------------------------------------
# -
# Erase/wipe, partition and format device -
# -
# Created by Fonic <https://github.com/fonic> -
# Date: 06/18/20 - 06/08/20 -
# -
# -------------------------------------------------------------------------
# ------------------------------------
# -
# Functions -
# -
# ------------------------------------
# Convert bytes value to human-readable string [$1: bytes value, $2: target variable]
# Based on: https://unix.stackexchange.com/a/259254/399674
function btos() {
local i=${1:-0} d="" s=0 S=("Bytes" "KiB" "MiB" "GiB" "TiB" "PiB" "EiB" "YiB" "ZiB")
while ((i > 1024 && s < ${#S[@]}-1)); do
printf -v d ".%02d" $((i % 1024 * 100 / 1024))
i=$((i / 1024))
s=$((s + 1))
done
printf -v "${2}" "%s%s %s" "${i}" "${d}" "${S[s]}"
}
# Convert seconds value to human-readable string [$1: seconds value, $2: target variable]
function stos() {
local v=${1:-0} h=0 m=0 s=0
h=$((v / 3600))
v=$((v % 3600))
m=$((v / 60))
v=$((v % 60))
s=${v}
printf -v "${2}" "%02d:%02d:%02d" "${h}" "${m}" "${s}"
}
# Handler for error trap [no arguments]
function error_trap() {
echo
echo -e "\e[1;31mAn error occurred, aborting.\e[0m"
echo
exit 1
}
# Handler for erase/wipe interrupt trap [no arguments, relies on global variables]
function erase_int_trap() {
[[ -z "${filesystem_label}" ]] && filesystem_label="\"\""
echo
echo
echo -e "\e[1;33mAborting by user request. To continue, use the following command line:\e[0m"
echo "# ${script} ${device} ${block_size} ${partition_type} ${filesystem_type} ${filesystem_label} ${bytes_written}"
echo
exit
}
# ------------------------------------
# -
# Main Program -
# -
# ------------------------------------
# Process command line
if (( $# < 5 )); then
echo
echo -e "\e[1mUsage:\e[0m"
echo "$(basename "$0") <device> <block-size> <partition-type> <filesystem-type> <filesystem-label> [<skip-bytes>]"
echo
echo "Block size (used by dd when erasing/wiping device):"
echo "1K = 1024 bytes, 2K = 2048 bytes, 4K = 4096 bytes, 8K = 8192 bytes, 16K = 16384 bytes, 32K = 32768 bytes, 64K = 65536 bytes, 128K = 131072 bytes, 256K = 262144 bytes, 512K = 524288 bytes"
echo "1M = 1048576 bytes, 2M = 2097152 bytes, 4M = 4194304 bytes, 8M = 8388608 bytes, 16M = 16777216 bytes, 32M = 33554432 bytes, 64M = 67108864 bytes, 128M = 134217728 bytes"
echo
echo "Partition type:"
echo "gpt, dos"
echo
echo "Filesystem type:"
echo "ext4, ntfs"
echo
echo "Filesystem label:"
echo "String (use empty string to leave unset)"
echo
echo "Skip bytes (leading bytes to skip when erasing/wiping device, may be used to continue previously interrupted session):"
echo "1 GB = 1073741824 bytes, 100 GB = 107374182400 bytes, 1 TB = 1099511627776 bytes, 2 TB = 2199023255552 bytes, 4 TB = 4398046511104 bytes, 8 TB = 8796093022208 bytes"
echo
exit 2
fi
script="$0"
device="$1"
block_size="$2"
partition_type="${3,,}"
filesystem_type="${4,,}"
filesystem_label="$5"
if [[ -n "${6:+set}" ]]; then
skip_bytes="$6"
skip_blocks=$((skip_bytes / block_size))
fi
if [[ ! -b "${device}" ]]; then
echo -e "\e[1;31mError: device '${device}' does not exist, aborting.\e[0m"
exit 2
fi
if [[ "${partition_type}" != "gpt" && "${partition_type}" != "dos" ]]; then
echo -e "\e[1;31mError: unsupported partition type '${partition_type}', aborting.\e[0m"
exit 2
fi
if [[ "${filesystem_type}" != "ext4" && "${filesystem_type}" != "ntfs" ]]; then
echo -e "\e[1;31mError: unsupported filesystem type '${filesystem_type}', aborting.\e[0m"
exit 2
fi
# Setup error handling
set -e
trap "error_trap" ERR
# Print warning message
echo
printf "\e[1;33m!!! %-60s !!!\e[0m\n" ""
printf "\e[1;33m!!! %-60s !!!\e[0m\n" "DEVICE '${device}' WILL BE COMPLETELY ERASED/WIPED"
printf "\e[1;33m!!! %-60s !!!\e[0m\n" "DOUBLE-CHECK THAT YOU SPECIFIED THE CORRECT DEVICE"
printf "\e[1;33m!!! %-60s !!!\e[0m\n" ""
echo
fdisk -l "${device}"
echo
if [[ -n "${skip_bytes:+set}" ]]; then
btos ${skip_bytes} s1
echo -e "\e[1mNOTE:\e[0m"
echo "Will skip first ${skip_bytes} bytes (${s1}, ${skip_blocks} blocks of ${block_size} bytes) when erasing device"
echo
fi
# Ask for user confirmation
echo -en "\e[1mType 'OK' to continue: \e[0m"
read input
if [[ "${input}" != "OK" ]]; then
echo
exit
fi
echo
# Erase/wipe device
# NOTE: using wipefs to make sure no partition table and/or filesystem ids remain even if skipping leading bytes when erasing
# NOTE: dd will fail with 'no space left on device'; since we're not using pipefail, there's no need to mask this error
echo -e "\e[1mErasing/wiping device...\e[0m"
echo "Started: $(date)"
#wipefs --all "${device}" # not working as expected; it would seem this wipes only the partition table, but not filesystem signatures; using dd instead
dd if=/dev/zero of=${device} bs=1M count=16 oflag=direct status=none
dd_opts=("if=/dev/zero" "of=${device}" "bs=${block_size}" "oflag=direct" "status=progress")
[[ -n "${skip_bytes:+set}" ]] && dd_opts+=("seek=${skip_blocks}") # https://utcc.utoronto.ca/~cks/space/blog/unix/DdSkipVersusSeek
bytes_total=$(blockdev --getsize64 "${device}")
bytes_written=${skip_bytes:-0}
bytes_left=$((bytes_total - bytes_written))
bytes_per_second=0
seconds_start=${SECONDS}
seconds_total=0
seconds_elapsed=0
seconds_left=0
break_loop=0
re_dd="([0-9]+) bytes" # dd progress output: '146577293312 bytes (147 GB, 137 GiB) copied, 2065 s, 71,0 MB/s'
trap "erase_int_trap" INT
while (( ${break_loop} == 0 )); do # done like this to make sure final dd output message ('no space left on device' + stats) is processed within loop (see below)
if ! read -d $'\r' -r line; then # will fail on final dd output due to missing delimiter '\r' + EOF
readarray -t lines <<< "${line}" # final dd output consists of multiple lines containing 2x stats among other stuff; we want the second stats line, i.e. last line of output
line="${lines[-1]}"
break_loop=1
fi
seconds_elapsed=$((SECONDS - seconds_start))
if [[ "${line}" =~ ${re_dd} ]]; then
dd_bytes=${BASH_REMATCH[1]}
bytes_written=$((${skip_bytes:-0} + dd_bytes))
bytes_left=$((bytes_total - bytes_written))
(( ${bytes_left} < 0 )) && bytes_left=0 # can happen on final iteration, not sure why exactly
(( ${seconds_elapsed} > 0 )) && bytes_per_second=$((dd_bytes / seconds_elapsed))
(( ${bytes_per_second} > 0 )) && { seconds_left=$((bytes_left / bytes_per_second)); seconds_total=$((bytes_total / bytes_per_second)); }
btos ${bytes_written} s1
btos ${bytes_left} s2
btos ${bytes_total} s3
stos ${seconds_elapsed} s4
stos ${seconds_left} s5
stos ${seconds_total} s6
btos ${bytes_per_second} s7
printf "\r\e[2KWritten: %s, Left: %s, Total: %s, Elapsed: %s, Left: %s, Total: %s, Speed: %s/s" "${s1}" "${s2}" "${s3}" "${s4}" "${s5}" "${s6}" "${s7}"
else
printf "\r\e[2K\e[1;33mWarning: failed to match regular expression\e[0m"
fi
done < <(dd "${dd_opts[@]}" 2>&1)
trap - INT
echo
echo "Finished: $(date)"
echo
# Sync and partprobe
echo -e "\e[1mSyncing and partprobing...\e[0m"
sync
partprobe "${device}"
echo
# Create partition table
# NOTE: fdisk might fail with 'rereading partition table failed', using '|| true' to mask error
echo -e "\e[1mCreating partition table...\e[0m"
if [[ "${partition_type}" == "gpt" ]]; then
{
echo "g" # Create new empty GPT partition table
echo "n" # Add new partition
echo "" # Partition number (default: 1)
echo "" # First sector (default: 2048)
echo "" # Last sector (default: last sector of device)
echo "t" # Change partition type (will automatically select partition 1 if only one partition present)
if [[ "${filesystem_type}" == "ext4" ]]; then
echo "20" # Type '20 Linux filesystem'
elif [[ "${filesystem_type}" == "ntfs" ]]; then
echo "11" # Type '11 Microsoft basic data'
fi
echo "w" # Write changes to disk and quit
} | fdisk "${device}" || true
elif [[ "${partition_type}" == "dos" ]]; then
{
echo "o" # Create new empty DOS partition table
echo "n" # Add new partition
echo "" # Partition type (default: 'primary' if less than 4 partitions present, else 'extended')
echo "" # Partition number (default: 1)
echo "" # First sector (default: 2048)
echo "" # Last sector (default: last sector of device)
echo "t" # Change partition type (will automatically select partition 1 if only one partition present)
if [[ "${filesystem_type}" == "ext4" ]]; then
echo "83" # Type '83 Linux'
elif [[ "${filesystem_type}" == "ntfs" ]]; then
echo "07" # Type '07 HPFS/NTFS/exFAT'
fi
echo "w" # Write changes to disk and quit
} | fdisk "${device}" || true
fi
# Sync and partprobe
echo -e "\e[1mSyncing and partprobing...\e[0m"
sync
partprobe "${device}"
echo
# Create filesystem
echo -e "\e[1mCreating filesystem...\e[0m"
if [[ "${filesystem_type}" == "ext4" ]]; then
if [[ -n "${filesystem_label}" ]]; then
mkfs.ext4 -L "${filesystem_label}" "${device}1"
else
mkfs.ext4 "${device}1"
fi
elif [[ "${filesystem_type}" == "ntfs" ]]; then
if [[ -n "${filesystem_label}" ]]; then
mkfs.ntfs -Q -L "${filesystem_label}" "${device}1"
else
mkfs.ntfs -Q "${device}1"
fi
fi
echo
# Sync
echo -e "\e[1mSyncing...\e[0m"
sync
echo
# Print results
echo -e "\e[1;32mSuccess! Results:\e[0m"
echo
echo -e "\e[1;32mfdisk:\e[0m"
fdisk -l "${device}"
echo
echo -e "\e[1;32mblkid:\e[0m"
blkid "${device}"*
echo
@fonic
Copy link
Author

fonic commented Jun 17, 2020

Erase, partition and format device on Linux

I created this script to facilitate erasing/wiping used devices before selling them.

Dependencies:
Bash >= 4.0, fdisk, wipefs, dd, sync, partprobe, mkfs.ext4, mkfs.ntfs, blkid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment